mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-27 01:02:16 +01:00
Fix Steam clip export collisions across headset and session identities
This commit is contained in:
1 parent
d79efb4730
commit
c49afdc625
7 files changed
+311
-24
No files matched your search
@@ -0,0 +1,70 @@
|
||||
# Steam clip export collision fixtures, 25 October 2026
|
||||
|
||||
Base: `d79efb4` (Frame Control v0.4.30). Issue:
|
||||
[#212](https://github.com/saphid/frame-control/issues/212).
|
||||
Host: Linux 6.8.0-139-generic, Python 3.12.3, ffmpeg 6.1.1.
|
||||
|
||||
## Baseline reproduction
|
||||
|
||||
Loaded the unmodified `ui/frame_clips.py` from `d79efb4` into a temporary
|
||||
Python module and exported three fake sources into a temporary directory:
|
||||
|
||||
```
|
||||
1/clips/clip_620/video/bg
|
||||
2/clips/clip_620/video/bg
|
||||
1/clips/clip_620/video/other
|
||||
```
|
||||
|
||||
Each source's payload was its own ID. All three exports returned the same
|
||||
destination, `clip_620.mp4`; listing marked all three saved. The final file
|
||||
contained only the last source ID. No SSH or headset was used.
|
||||
|
||||
## Regression coverage
|
||||
|
||||
`tests/test_clips.py` exports five IDs on each of two fake headsets, including
|
||||
equal clip names across accounts, nested sessions with equal leaf names,
|
||||
different leaf names under one clip and the background/clip distinction.
|
||||
All ten payloads survive at distinct destinations. Listing marks only the
|
||||
identities already exported. A pre-existing flat MP4 retains its original
|
||||
bytes and does not mark any identity saved. Normal export still returns
|
||||
`message` and `file`, removes its remote temporary export and clears local
|
||||
staging; a failed re-export preserves the completed file.
|
||||
|
||||
`tests/test_clip_copy.py` exercises the server's list and manual export
|
||||
functions and its actual auto-sync callbacks with fake transport. A manual
|
||||
copy is recognised by auto-sync, all other account/session identities copy,
|
||||
and switching to a second headset copies its own sources without overwriting
|
||||
the first. A delayed manual job refuses a changed headset with status 409.
|
||||
Both local phone mode and local headset runtime reject Steam clip list,
|
||||
export and auto-sync requests with status 400. The separate `Recorder` path
|
||||
is unchanged.
|
||||
|
||||
## Validation
|
||||
|
||||
- `PYTHONPATH=tests python3 -m unittest test_clips test_clip_copy test_autosync test_shot_copy test_headset_actions -v`
|
||||
— 66 tests passed in 17.288 seconds, no skips. Includes real ffmpeg/ffprobe
|
||||
export of a generated 65-second DASH fixture and recording stop/cleanup.
|
||||
- `PYTHONPATH=tests python3 -m unittest test_server -v`
|
||||
— 44 tests passed in 12.315 seconds, no skips.
|
||||
- `node --test site/test/*.test.mjs` — 2 tests passed.
|
||||
- `python3 -m unittest discover -s tests -v` — 1,518 tests run in
|
||||
181.025 seconds; OK, 16 skipped for unavailable platform/tool prerequisites.
|
||||
- `python3 -m py_compile ui/frame_clips.py ui/server.py tests/test_clips.py tests/test_clip_copy.py`
|
||||
and `git diff --check` — passed.
|
||||
|
||||
The server and full Python suites require localhost sockets and were run
|
||||
outside the network sandbox. Tests import `tests/sandbox.py` to isolate app
|
||||
data, SSH settings, home directories and service URLs.
|
||||
|
||||
## Limits and upgrade behaviour
|
||||
|
||||
No real headset was accessed: the shared device lock is held by other work.
|
||||
This is Linux fixture and server coverage, not a fresh packaged-app or
|
||||
Windows/macOS verification. Existing real-headset export evidence in the
|
||||
recording guide predates this destination change.
|
||||
|
||||
Old flat exports cannot prove headset/account/session identity and are left
|
||||
untouched. Previously remembered auto-sync IDs retain their existing
|
||||
watermark/history behaviour; **Copy everything now** or manual **Export**
|
||||
creates an identified copy. No release, version bump, merge or social post
|
||||
is included. PR #113 is outside this task.
|
||||
+28
-5
@@ -69,11 +69,31 @@ yes-or-no answer about passthrough pixels. See the
|
||||
|
||||
**Steam clips** lists what Steam's game recording has kept on the Frame: saved
|
||||
clips and background recordings, newest first, with the game, length and size.
|
||||
**Export** joins one into a single `.mp4` in `~/Videos/SteamFrame`, named after
|
||||
its folder (for example `clip_620_20261020_202134.mp4`), however long it is.
|
||||
**Export** joins one into a single `.mp4` under `~/Videos/SteamFrame`, named after
|
||||
its clip folder (for example `clip_620_20261020_202134.mp4`), however long it is.
|
||||
Steam's own export is reported to stop at about 60 seconds. Exports run in the
|
||||
background; ones already on your computer are marked.
|
||||
|
||||
Each export keeps its headset, Steam account and complete recording session ID:
|
||||
|
||||
```
|
||||
~/Videos/SteamFrame/headset-<id>/<account>/clips/<clip>/video/<session>/<clip>.mp4
|
||||
~/Videos/SteamFrame/headset-<id>/<account>/video/<session>/<session>.mp4
|
||||
```
|
||||
|
||||
The headset folder uses the same short device-identity hash as screenshots.
|
||||
All folders in the Steam clip ID are kept, including nested sessions, so equal
|
||||
clip names across accounts, headsets or sessions keep separate files. Manual
|
||||
**Export** and automatic copying use the same path and saved marker. Exporting
|
||||
the same identified session again replaces only that session's export.
|
||||
|
||||
Existing exports in the old flat `~/Videos/SteamFrame/<clip>.mp4` layout stay
|
||||
untouched. Their source identity cannot be proved, so they do not mark a clip
|
||||
as saved. Export again or use **Copy everything now** to make an identified
|
||||
copy. Automatic copying still remembers IDs copied before the upgrade; use
|
||||
**Copy everything now** to copy those again. This destination change has local
|
||||
fixture coverage; it has not been tested on a real headset.
|
||||
|
||||
How it works: Steam keeps each recording as MPEG-DASH segments, one folder per
|
||||
recording session.
|
||||
|
||||
@@ -147,8 +167,10 @@ and the last problem it had. The screenshot and clip lists say when it's on.
|
||||
`ui/frame_clips.py` holds the logic, so other features can reuse it (automatic
|
||||
copying, in `ui/frame_autosync.py`, does):
|
||||
|
||||
- `list_clips(run_frame)` and `export_clip(id, run_frame, run, fetch)` are the
|
||||
computer's side; the server passes how to reach the Frame.
|
||||
- `list_clips(run_frame, device_id=...)` and
|
||||
`export_clip(id, run_frame, run, fetch, device_id=...)` are the computer's
|
||||
side; the server passes how to reach the Frame and its pinned device identity.
|
||||
Both use `clip_local_path(id, device_id, folder=None)` for the destination.
|
||||
- `list_recordings(userdata)` and `export(userdata, id)` are the Frame's side,
|
||||
run as `python3 - list` / `python3 - export ID` with the file on stdin.
|
||||
- `Recorder` runs one recording from Start to the copy.
|
||||
@@ -171,7 +193,8 @@ The API, with the same guards as every other request:
|
||||
They aren't available when the server runs on the Frame itself (the iPhone
|
||||
app), since they save to a computer.
|
||||
|
||||
Tests: `tests/test_clips.py`, and `tests/test_autosync.py` for automatic
|
||||
Tests: `tests/test_clips.py`, `tests/test_clip_copy.py` for server destinations
|
||||
and collision coverage, and `tests/test_autosync.py` for automatic
|
||||
copying (with a fake headset). They run the recording's shell under bash with a
|
||||
stand-in `ffmpeg`, so Stop, the time limit and the clean-up are tested as they
|
||||
run on the Frame. Where `ffmpeg` is installed, they also make a 65-second DASH
|
||||
|
||||
Reference in new issue
Block a user