0014 — seeking & time ranges¶
Status: IMPLEMENTED (Phase 1 of the implementation roadmap;
shipped at vocabulary version 3: inputs[].seek {start, mode} (fast default / accurate
opt-in), outputs[].duration | end (mutually exclusive), outputs[].copy_ts (zero-base default).
Fast seek lands on the keyframe at-or-before start and the output zero-bases on it; accurate seek
decode-and-discards to the exact frame; the window is enforced on both the encode and copy paths,
with early input-EOF once every consuming output's window has passed. D-0014-F enforced both
Go-side (Command validation) and engine-side (typed error). Probe gained start_sec so a
preserved (copy_ts) timeline is observable. Q1 resolved: under copy_ts, end is an absolute
source position; otherwise the output timeline (where it coincides with duration, matching
ffmpeg's -ss + -to behaviour). Q2: seek precedes the graph — trim operates on the rebased
timeline. Q3: all-keyframe streams collapse fast/accurate harmlessly. Q4: per-input duration out
of scope. Verified end-to-end incl. 0013's deferred keyframe-accurate copy-trim.)
Date: 2026-06-30
Parent: 0012 §4F/§7; 0007 (the engine this extends)
Owns: R-PARITY-SEEK (input fast/accurate seek + output duration/end + PTS rebasing)
1. Why¶
Clip extraction — "give me 5 seconds from 0:12" — is one of the most-asked media operations, and
today the engine can only do it the expensive way: the trim/atrim filter, which decodes from
the start of the file and throws away everything before the window. For a 2-hour source that is
minutes of wasted decode to emit a 5-second clip. FFmpeg's -ss solves this by seeking the demuxer
to the nearest keyframe before decoding; -t/-to stop early. Both are cheap, single-threaded,
file-based — squarely inside the WASI envelope (0012 §2) — and
pair with stream copy (0013) to make fast remux-trim possible. This
is the highest-leverage time-domain gap alongside 0013.
2. Scope¶
In: input-level fast seek (keyframe, no decode before the target) and accurate seek
(keyframe seek + decode-and-discard to the exact frame); output duration (-t) and end
(-to); PTS rebasing (zero-base the output vs preserve source timestamps, -copyts); the rule
for trimming a copied stream (0013). Out: the in-graph trim/atrim filter (stays available,
unchanged — §3); per-frame select/thumbnail extraction (→ 0021);
input loop/-stream_loop; reverse/-itsoffset. No new codec, lib, or build flag (§5).
3. Approach / design (libav mechanics)¶
Fast seek (default). Before the read loop, for each input that requests a start, call
avformat_seek_file(fmt, -1, INT64_MIN, ts, ts, 0) (seconds → AV_TIME_BASE), landing on the
keyframe at-or-before ts. Demuxing then begins there — the packets before it are never read or
decoded. This is the whole cost saving.
Accurate seek. Fast-seek as above, then in the feed path drop decoded frames whose
best_effort_timestamp (rescaled to a common base) is < start_pts, per graph input, until the
first frame ≥ start_pts — from there frames flow into the buffersrc normally. Cost = decoding the
keyframe-to-target gap (a fraction of a GOP), not the whole file.
- Duration / end. Track an end cutoff per input: `end_pts = (mode==duration) ? start + duration
- end
. In the read loop, once an input's packet PTS reachesend_pts, mark that input EOF (flush its decoders, close its buffersrc) — the existing multi-input EOF path inprocess.calready drains and trailers cleanly.durationandend` are mutually exclusive on one output.
PTS rebasing. Default reset: subtract start_pts from frame PTS as it enters the buffersrc
so the output starts at t=0 (what almost every consumer wants from a clip). copy_ts: true
preserves source timestamps (output begins at the seek point's PTS) — needed when timelines must
stay aligned across a multi-output split.
Composition with the filtergraph. Seek happens at the demuxer, before decode, so the
filtergraph sees an already-windowed stream — seek and filters compose without interaction. The
trim/atrim filter is therefore not replaced: it remains the only tool for trimming one
stream among several to a different window, or trimming mid-graph (after a concat/overlay). The
rule of thumb: input seek+duration for cheap whole-input clipping; trim for in-graph surgery.
Composition with stream copy (0013). A copied stream is muxed packet-for-packet with no
re-encode, so it can only cut on keyframes. Therefore: on a copied stream, seek always behaves
as fast (start snaps to the keyframe at-or-before the request) and an explicit mode:"accurate"
on a copied stream is a hard error (frame-accuracy requires the decode-discard the copy path
skips). duration/end on a copy likewise snap to the last keyframe at-or-before the cutoff. This
constraint is stated to the consumer, not silently fudged.
4. Job-spec & build impact¶
New vocabulary (owned here):
{
"op": "process",
"inputs": [ { "path": "in.mp4",
"seek": { "start": 12.5, "mode": "fast" } } ], // mode: fast (default) | accurate
"outputs": [ { "path": "out.mp4", "video_codec": "libx264",
"duration": 5.0, // -t (seconds) ─┐ mutually
"end": 17.5, // -to (seconds) ─┘ exclusive
"copy_ts": false } ] // false (default) → zero-base; true → preserve source PTS
}
inputs[].seek—{start: seconds, mode: "fast"|"accurate"}.startalone with nomode⇒ fast. Per-input (each input seeks independently).outputs[].duration/outputs[].end— the output's time window; measured on the output (rebased) timeline unlesscopy_ts(then source timeline). Output-level (not global) so a multi-output split can window each file differently.outputs[].copy_ts— the PTS-rebasing directive (defaultfalse= reset to zero).- afmpeg Go model:
InputgainsSeek *Seek{Start float64; Mode string};OutputgainsDuration/End float64+CopyTS bool;JobSpec()emits the above. Mirrors the existingcommand.goshape (additive, no redesign). - Vocabulary version bump (0007 §4, 0012 §6): an older afmpeg must fail cleanly on these fields rather than silently ignore a requested window.
- Build: none.
avformat_seek_fileand packet-PTS handling are core libavformat — no--enable-*, no new dep.
5. Licensing & size impact¶
Negligible. Seeking, -t/-to, and timestamp handling are in libavformat/libavutil already
linked by every variant — no new decoder, library, or build flag, so the .wasm is unchanged
and the LGPL default is unaffected. This is pure driver + vocabulary work; it lands identically
in lean and full builds.
6. Decisions¶
- D-0014-A — fast seek is the default; accurate is opt-in. Keyframe seek is the cheap, common
case; the decode-discard cost of frame-accuracy is paid only when
mode:"accurate"is asked for. - D-0014-B — one
seekobject with amodeenum, not parallelfast_seek/accurate_seekfields — keeps the surface small and the fast/accurate choice explicit and local to the input. - D-0014-C —
starton inputs,duration/endon outputs. Matches FFmpeg's mental model (input seek vs output window) and lets a multi-output split window each file independently. - D-0014-D — PTS reset to zero is the default. A clip that starts at t=0 is what consumers
expect;
copy_ts:trueis the opt-in for timeline preservation. - D-0014-E —
trim/atrimis retained, not deprecated. Demux-seek and in-graph trim are complementary (§3); seek is the cheap whole-input path,trimthe mid-graph/per-stream scalpel. - D-0014-F — accurate seek on a copied stream (0013) is a hard error; copy trims snap to keyframes. A copy cannot be frame-accurate without re-encoding; the contract states the constraint rather than silently producing an off-by-a-GOP cut.
Open questions.
- Q1 — end timeline under copy_ts. When timestamps are preserved, is end an absolute
source PTS or still relative to the seek point? (Proposed: absolute source PTS — -to semantics.)
- Q2 — combining input start with a graph trim. Define the precedence/offset when both
window the same stream (seek first, then trim on the rebased timeline) — pin in impl.
- Q3 — streams without keyframes (e.g. some PCM/image2): fast seek is exact there; confirm the
mode distinction collapses harmlessly.
- Q4 — per-input duration (rare; cap one input of several). Out of scope for v1 unless a
consumer needs it; output duration covers the common case.
7. Requirements¶
- R-PARITY-SEEK — The engine extracts a time window without decoding from the file start:
input-level fast seek (keyframe, cheap) and opt-in accurate seek (frame-exact via
decode-discard); output duration/end cutoffs; PTS rebasing (zero-base default,
copy_tsopt-in). Composes with the filtergraph (seek precedes decode) and obeys the keyframe-only rule when trimming a copied stream (0013). Surfaced through the versioned job-spec and afmpeg'sCommandmodel.
8. Test surface¶
- Unit (afmpeg):
JobSpec()emitsseek/mode/duration/end/copy_tscorrectly;duration+endtogether is a validation error;mode:"accurate"on a copy-mapped output errors. - Integration (real driver,
AFMPEG_TEST_FFMPEG_WASI): from a synthetic multi-keyframe fixture, (a) fast seek lands on the keyframe ≤ target and the output duration is correct; (b) accurate seek starts at the exact frame; ©durationandendeach cut to the expected length; (d) default output PTS starts at 0,copy_ts:truepreserves source PTS (assert viaprobe); (e) accurate seek on a copied stream is rejected. Fixtures stay dependency-free (synthetic media, 0012 §6).
9. Dependencies & sequencing¶
- Independent of 0013 for the re-encode path — fast/accurate seek + duration/end work on the existing decode→filter→encode loop and can land first.
- Shares the copy semantics with 0013 — the keyframe-only trim rule (D-0014-F) is meaningful only once stream copy exists; sequence the copy-trim tests after 0013, or land the rule as a validation guard that 0013 wires up.
- Vocabulary-version coordination with the rest of the 0013–0023 batch (one bump per landed field set, 0012 §6).
10. Definition of done¶
The job-spec gains inputs[].seek{start,mode}, outputs[].duration|end, and outputs[].copy_ts
(versioned); the engine seeks each input before its read loop, supports fast and accurate modes,
enforces the end cutoff, and rebases (or preserves) PTS per copy_ts; the copied-stream trim rule
(D-0014-F) is enforced with a clear error; afmpeg's Command/Input/Output model and JobSpec()
carry the fields; the relationship to trim/atrim and to 0013's copy path is documented; and the
§8 unit + integration tests (incl. the keyframe-snap and copy-reject cases) pass against the real
driver. The decisions (D-0014-A…F) are recorded; the open questions (Q1–Q4) are resolved in impl.