The signature had a producer and a reader but no consumer, so nothing
ever fingerprinted anything. `ManifestAligner` runs on the fetch path,
before the windows are stored.
A local alignment supersedes the server's offset. The server has never
seen this file — its offset is a runtime-difference inference at best,
while the local comparison is against the media the windows will actually
be drawn over. It also needs no round trip, so no signature leaves the
instance. This is what jRay's spec already meant by matching being a
consumer concern: the server never rewrites a manifest, so one stored
manifest serves every trim of the same cut.
The offset has two terms and only one is in the server's pseudocode. Both
windows are centred on their own file's midpoint, so unequal runtimes
start them at different absolute times; a release with 40 s of extra head
material recovers 20 s from the slide and 20 s from the anchor
difference. Using the slide alone is wrong by half the runtime difference
on every shifted release.
Degradation, never failure. Signatures off, no manifest signature, media
under the window, a `v2:` producer, a missing binary, a decode error —
each applies the server's offset rather than refusing, because a
signature is an enhancement to cut matching and must never break a fetch.
"Un-comparable" and "does not match" are kept distinct, which a test
caught: `Compare` returns null for both, and conflating them would report
a 90-second extra as content disagreeing with its own manifest. A genuine
disagreement is stored anyway — the audio may legitimately differ, a
different language track being the obvious case — and surfaced as a
caveat that outranks the tier's, since it is the stronger statement.
The applied offset, score, slide and the local file's own signature are
written beside the truth file: the offset is otherwise unrecoverable once
the windows are shifted, and the stored signature lets a later fetch
align without decoding again. Provenance is never injected into the truth
file, so the bytes served back stay the producer's (JR-004).
`docs/audio-alignment.md` documents the mechanism end to end.
TRACES: JR-047 | SR-003
`AudioSignature` is the DSP — band table, periodic Hann, radix-2 FFT,
band-mean peak, energy class, packing — and `AudioSignatureService` the
decode, running the FFmpeg binary `IMediaEncoder.EncoderPath` names. The
plugin gained no dependency.
The server specification's prose does not determine a byte stream, so the
parameters it leaves open are pinned by the fixture shared with the
extraction repo and restated at the top of `AudioSignature`: double
throughout, whole frames only, periodic Hann, unnormalised FFT, band mean
rather than sum, argmax ties to the lowest index.
`fixtures/audio/` holds the extraction repo's three files byte-identically
and the computed signature equals the recorded vector exactly. The binding
check regenerates the fixture PCM from `make_fixture.py`'s arithmetic and
verifies it against the recorded decode checksums, so it runs on a host
with no codec at all and a decode divergence stays distinguishable from a
DSP one; the two tests that drive real FFmpeg self-skip without a binary.
The workflow named "Test Plugin" until now only compiled one. A test that
is built and never run is not evidence, and a golden vector shared across
two repos exists precisely so CI fails when they drift.
TRACES: JR-042, JR-043 | SR-003
Satisfies JRay-public-server UR-007. Servers are tried in configured order and
the first result clearing the configured tier wins; first-match rather than
best-match because querying every server for every item multiplies egress and
leaks the library to more parties, and the ordering already encodes which
source the admin prefers.
Every server is untrusted, including the pre-configured community one, so a
fetched manifest is re-validated against the same rules the server applies on
upload: envelope version refused if unknown, identifiers format-checked,
windows bounds-checked against the *local* file's runtime, belief bounded to
[0, 1], control and bidi characters refused in names. Responses are capped
while streaming rather than after buffering, since a hostile server can declare
any Content-Length it likes. HTTPS is required away from loopback. A failing
server is skipped with exponential backoff so one dead server cannot stall a
sweep.
The audio-tier offset is applied once, at store time, so stored truth is always
in the local file's own timebase and no read path needs offset awareness.
Windows are shifted, never reshaped — merging adjacent ones would answer "was a
face visible" rather than "was the actor present" (SR-002).
Also records why there is no `exact` tier, which was missing and led me to
re-add one. The file-hash tier is withdrawn on legal grounds: a TMDB id
discloses "some copy of this film", but an OpenSubtitles hash discloses "this
exact release", which turns a catalogue lookup into a release-identification
service and a server's database into a mapping from file fingerprints to the
instances holding them. The reason now lives on MatchTier and in SPEC.md §JR-036,
`TitleQuery` has no VideoHash property so there is nothing to send, and a test
asserts the enum has no Exact member — the spec had still listed `exact` as a
configurable tier, which is what made the removal look like an oversight.
42 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TRACES: JR-025, JR-027, JR-028, JR-029, JR-030, JR-031, JR-036, JR-037 | PR-005, PR-006