Skip to content

feat: report finished segments during pack() and accept URL sources - #3

Merged
shimonewman merged 4 commits into
mainfrom
feat-shipped-segments
Nov 3, 2026
Merged

shimonewman merged 4 commits into
mainfrom
feat-shipped-segments

Conversation

@shimonewman

Copy link
Copy Markdown
Collaborator

Why

The Appwrite videos worker runs on an emptyDir scratch volume. Today one encode holds the whole source plus the whole rendition on that disk until the end. This lets a consumer ship each segment as soon as it is finished, and give ffmpeg the source as a URL instead of a local copy.

What changes

  • Packager::SEGMENT fires during pack() for each finished segment (Written: file, path, size). The listener may upload and delete the file; the returned Package still describes it, with that size.
    • A segment is finished once a higher-numbered sibling exists in its sequence. HLS now also writes .tmp until rename (temp_file joins the default flags).
    • Init segments and the last of each sequence are reported when the run ends. Manifests are never reported, and neither is anything in the directory, unchanged, from before the run.
    • Without a listener nothing is watched.
  • Parsers (M3u8::read(), Mpd::read()) take an optional ledger of shipped sizes, so a shipped segment is not "missing".
  • Sources may be an http(s):// URL; ffmpeg and ffprobe read it with range requests. tile(), grab() and subtitle() accept it too.
  • Security fix in the source check. is_file() follows PHP stream wrappers, so file:///etc/passwd passed the old check and ftp://… made a network call. Now only a plain local path or http(s) is accepted, and a local name starting like a protocol (concat:) is refused.
  • Error messages name a URL source without its query string, because a presigned query is a credential.

Tests

Suite Result
Unit 322 passed
E2E on native ffmpeg 9.0.1 254 passed, 1 skipped (needs Swoole)
composer lint, composer check clean
  • The new E2E tests pack a 30 s source to HLS (TS and fMP4), DASH and CMAF. They assert that segments leave the disk before ffmpeg reports it is done.
    • Seen red: with the mid-run scan removed, all four fail with "nothing left the disk before ffmpeg finished".
  • RemoteTest serves the sources from a local range-capable server and checks that ffmpeg actually sends Range requests.
    • Seen red: with URLs refused, it fails.

Not covered here

  • Not run on ffmpeg 8.1.2. Docker was unavailable locally, so the pinned 8.1.2 image that this repo's CI uses has not run these tests. CI will.
  • No real object storage. The library never talks to S3. The presigned-URL path is exercised end to end only by appwrite-labs/cloud's Videos shard against MinIO.

Release

appwrite/appwrite __stream_v8 requires dev-feat-shipped-segments until this is merged and tagged (next after 0.0.2).

🤖 Generated with Claude Code

shimonewman and others added 2 commits November 3, 2026 11:23
M3u8::read() and Mpd::read() take an optional ledger of segment sizes by
name. A segment named by the manifest but gone from disk is described with
the recorded size instead of failing the package as missing; one that is
neither on disk nor in the ledger still fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Packager::SEGMENT fires for each segment ffmpeg has finished while the
rest are still encoding, with its name, path and size. A listener may
upload and delete it; the package still describes it from that size. A
segment counts as finished once a higher-numbered sibling exists in its
sequence, and HLS now writes `.tmp` until rename (temp_file is a default
flag). Init segments and the last of each sequence are reported when the
run ends, manifests never, and nothing that was in the directory,
unchanged, before the run. Without a listener nothing is watched.

Sources may be an http(s) URL, which ffmpeg and ffprobe read with range
requests, so a presigned object storage link is probed, packed and tiled
without a local copy. Every other scheme is refused, and so is a local
name that starts like a protocol: is_file() followed stream wrappers, so
file:///etc/passwd passed the old check and ftp:// reached the network.
Messages name a URL source without its query string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shimonewman and others added 2 commits November 3, 2026 11:55
The shipping E2E records progress next to segments and asserts it stays
monotonic, reaches 100, and is still reported after the first segment
has left. Seen red with mid-run progress suppressed while watching.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ffmpeg writes out_time_us=N/A for a progress block with no output
timestamp yet, which a float cast read as 0, so progress fell from ~77%
back to 0 before reaching 100 (seen on DASH and CMAF under ffmpeg 8.1.2).
Non-numeric and negative times are now skipped. A consumer writing
progress to storage would otherwise publish that 0.

Also: tests no longer use GLOB_BRACE, which musl does not define, and the
watcher documents the one rewrite it cannot tell apart (same second, same
size, reused inode).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shimonewman
shimonewman merged commit 7ecf9bc into main Nov 3, 2026
5 of 11 checks passed
@shimonewman
shimonewman deleted the feat-shipped-segments branch November 3, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant