A release whose only subtitle is a PGS or VobSub track was stuck both ways: the track satisfied English so no English SRT was ever fetched, and bitmaps can never feed a translator. §15 is amended to separate satisfying viewing from providing a translation source, and to carve a fetch made to obtain a source out of the no-upgrade rule. Timings come from the disc rather than from alass guessing at the audio. At import, each non-forced image track's packet timestamps are paired show-to-clear into a cue skeleton and stored; the fetched source is then aligned against that skeleton, and the translation made from it skips the post-translation pass, which could only move disc-exact timings off. Pairing is validated before it is trusted — even packet count, plausible durations, sane density for the runtime — because PGS allows several composition segments per subtitle and a slipped pairing is quietly half a second out. A track that fails validation gets no skeleton and falls back to aligning against the video, as does any file imported before this: there is no backfill. Closes #268 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
35 lines
1.9 KiB
SQL
35 lines
1.9 KiB
SQL
-- #268. The cue structure of an image-format subtitle track (DESIGN.md §15).
|
|
--
|
|
-- A PGS or VobSub track carries bitmaps, so it can never feed a translator.
|
|
-- Its packet timings are still exact for the release it came off, and that is
|
|
-- what `alass` matches on: aligning a downloaded English subtitle against this
|
|
-- skeleton lands it on the disc's own cue structure instead of on a heuristic
|
|
-- read of the video's audio.
|
|
--
|
|
-- Derived once at import, where the file is being read and hardlinked anyway.
|
|
-- A track whose packets do not pair cleanly gets no row at all — the absence
|
|
-- is the fallback signal, and the lane aligns against the video instead.
|
|
CREATE TABLE subtitle_skeletons (
|
|
media_file_id INTEGER NOT NULL REFERENCES media_files (id) ON DELETE CASCADE,
|
|
-- The track's position among the file's subtitle streams, the same index
|
|
-- `ffmpeg -map 0:s:N` takes.
|
|
stream_index INTEGER NOT NULL CHECK (stream_index >= 0),
|
|
-- As `arr_core::Language` spells it. The language a source subtitle is
|
|
-- fetched in when this skeleton is the alignment reference.
|
|
language TEXT NOT NULL,
|
|
-- The cues, as a JSON array of `[start_ms, end_ms]` pairs in file order.
|
|
-- No text: there is none to read, and `alass` does not want any.
|
|
cues TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
PRIMARY KEY (media_file_id, stream_index)
|
|
) STRICT;
|
|
|
|
-- §15, as amended by #268: a subtitle aligned against a cue skeleton already
|
|
-- carries disc-exact timings, so the post-translation `alass` pass is skipped
|
|
-- for it and for anything translated from it. A second pass can only move
|
|
-- those timings off. This says which rows that applies to, durably — a crash
|
|
-- between the fetch and the translation must not lose the fact.
|
|
ALTER TABLE subtitle_files
|
|
ADD COLUMN skeleton_aligned INTEGER NOT NULL DEFAULT 0
|
|
CHECK (skeleton_aligned IN (0, 1));
|