Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Export sessions let the browser stream encoded H.264 chunks to the server as
they are produced instead of keeping the whole elementary stream in memory.
The session's total encoded bytes stay capped by the existing 2 GB limit
across all appends, including appends that overlap. Cancelling an export still
deletes the session immediately, and abandoned sessions are expired
server-side.
across all appends, including appends that overlap. Cancelling an export
deletes the session, and abandoned sessions are expired server-side; both wait
for any upload or mux still using the session to finish before its files are
removed.
- Set `FEEDBACK_PUBLIC_ORIGIN` (for example `http://127.0.0.1:5173`) to let the
server fetch a same-host song mix itself, so the browser no longer downloads
and re-uploads it. The server tells the browser whether it can resolve the
Expand Down Expand Up @@ -74,6 +75,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **Export session operations are serialized on one lifecycle lock.** The
previous `append_lock` only protected concurrent video appends: a mux could
run while an upload was still streaming, so FFmpeg read an incomplete
elementary stream, and the session could be consumed (and its work dir
removed) before the upload finished. Two overlapping mux requests could also
both pass the audio-resolution step that runs before the session is popped.
Every operation that touches a session's work dir — video and audio uploads,
mux, cancel, and the TTL/size sweep — now takes the session's lifecycle
lock: mux waits for in-flight uploads so FFmpeg only ever reads finalized
media, only a single mux can own a session, and cancel/eviction wait for or
skip a session in use instead of deleting files underneath it.
- **An interrupted session upload no longer leaves partial bytes behind.** A
client disconnect (`ClientDisconnect`) or a cancelled task
(`CancelledError`) bypassed the `OSError`/`ValueError` handlers, so a
Expand Down
23 changes: 18 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ it is the whole reason runs are cut: a live canvas must be composited between
the static pixels above and below it.

**Layer bounds are snapshot-based, under the lock.** `video_bytes` is the
file's size, so it may only be read *inside* `append_lock`. Snapshotting before
the acquire gives every in-flight append the same stale base, each gets a full
`MAX_VIDEO_BYTES` of headroom, and a rollback can truncate below the accepted
prefix. A rejected append truncates back to that snapshot — never unlink, or a
single over-limit chunk destroys the whole render.
file's size, so it may only be read *inside* the session's lifecycle lock
(`state["lock"]`). Snapshotting before the acquire gives every in-flight append
the same stale base, each gets a full `MAX_VIDEO_BYTES` of headroom, and a
rollback can truncate below the accepted prefix. A rejected append truncates
back to that snapshot — never unlink, or a single over-limit chunk destroys the
whole render.

**Fixed: Staff View (alphaTab) notation no longer goes stale.** Staff View
renders sheet music as `<svg>`, swapped out by alphaTab as playback scrolls to
Expand Down Expand Up @@ -141,6 +142,18 @@ misses `ClientDisconnect` (a client that drops mid-stream) and `CancelledError`
re-raise is what keeps cancellation working, don't swallow it to return a
response.

**Every operation on a session's work dir takes that session's single lifecycle
lock** (`state["lock"]`), so ownership of the temp dir is unambiguous. Video and
audio uploads hold it for their whole stream, mux holds it across audio
resolution and the FFmpeg run, and cancel plus the TTL/size sweep take it before
deleting. That is what makes mux wait for in-flight uploads (FFmpeg only reads
finalized media), admits a single mux owner, and stops cleanup from deleting
files an operation still has open; the sweep skips a locked session rather than
block. A waiter re-checks `sessions.get(session_id) is state` under the lock, so
an operation that lost the race against a consume/evict returns 404 instead of
acting on a stale reference. Don't narrow this back to a video-append-only lock
— that is exactly how mux came to read a half-written stream.

**Server-side audio resolution is opt-in and origin-pinned.** The browser only
ever sends a site-relative `audio_url`, but that constrains the path, not the
destination: `urlopen` follows cross-origin 30x, and `request.base_url` is built
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "visual_export",
"name": "Visual Export",
"version": "0.3.2",
"version": "0.3.3",
"description": "Renders the current player layout to a video file for the entire song, without a live performance.",
"type": "tools",
"script": "screen.js",
Expand Down
Loading
Loading