Sync subtitles with alass #194

Closed
opened 2026-08-24 21:28:32 +01:00 by naps62-yolo · 1 comment
Owner

Wrap alass in arr-subs and run it over every subtitle arr obtains, fetched
or translated (DESIGN.md §15, added by #183). It is a single small binary
invoked and discarded like ffprobe, so it adds nothing at rest.

alass reports no confidence value, so trust is bounded structurally rather
than by a score:

  • Accept its output unless the result is implausible: any cue moved more than
    60 seconds from where it started, or cues lost between input and output.
  • On rejection, keep the unsynced original and record that sync was rejected on
    the subtitle row (#186), so the missing-subtitles queue can surface it and the
    operator can re-run it by hand.
  • The reference is the media file itself; the subtitle and the video are always
    both on disk when this runs.

The binary path is configurable like ffprobe's, and its absence is a health
condition (#200), not a panic.

Depends on: #184, #212

Wrap `alass` in `arr-subs` and run it over every subtitle arr obtains, fetched or translated (DESIGN.md §15, added by #183). It is a single small binary invoked and discarded like `ffprobe`, so it adds nothing at rest. `alass` reports no confidence value, so trust is bounded structurally rather than by a score: - Accept its output unless the result is implausible: any cue moved more than 60 seconds from where it started, or cues lost between input and output. - On rejection, keep the unsynced original and record that sync was rejected on the subtitle row (#186), so the missing-subtitles queue can surface it and the operator can re-run it by hand. - The reference is the media file itself; the subtitle and the video are always both on disk when this runs. The binary path is configurable like `ffprobe`'s, and its absence is a health condition (#200), not a panic. Depends on: #184, #212
naps62-yolo added this to the Subtitles milestone 2026-08-24 21:28:32 +01:00
naps62-yolo added the area/subsdifficulty/moderatephase/9-subtitlestype/feature labels 2026-08-24 21:28:32 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:27 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:27 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:28 +01:00
naps62-yolo added a new dependency 2026-08-24 22:37:27 +01:00
naps62-yolo added a new dependency 2026-08-25 00:41:34 +01:00
Author
Owner

Implemented the alass wrapper as arr_subs::sync (crates/arr-subs/src/sync.rs), in the same shape as arr-probe's ffprobe wrapper: a Syncer with a configurable binary path (with_binary, default alass, already wired to alass_path in the daemon config) and a timeout. It runs alass <sub> <video> <output> against a temp output file, parses both input and output back through srt::parse, and judges plausibility structurally per DESIGN.md §15: any cue moved more than 60 seconds from its start, or cues lost between input and output, yields Outcome::Rejected(Rejection) so the caller keeps the unsynced original and records SubtitleSync::Rejected on the row (#186's columns and mark_synced already exist). A plausible result returns Outcome::Accepted { content } with the synced SRT. New error variants: Spawn (missing binary — a #200 health condition, non-transient), Timeout (transient), SyncFailed (non-zero exit, carries status + trimmed stderr) and NotSrt. The daemon-side invocation lands with #195/#196, which own writing sidecars to disk; nothing calls the syncer yet.

Branch: subtitles/194-alass-sync

just ci: passed via the gate (569 tests, lint, fmt, web-check all green).

No fixtures involved; all sync tests generate fake alass shell scripts at runtime.

Findings

  • Non-SRT downloads (ASS/VTT) reach sync() only after #213 converts them; until then the wrapper fails them with Error::NotSrt. That is the correct failure mode but means ASS fetches will be recorded as failed attempts if wired before #213 merges.
Implemented the `alass` wrapper as `arr_subs::sync` (crates/arr-subs/src/sync.rs), in the same shape as `arr-probe`'s ffprobe wrapper: a `Syncer` with a configurable binary path (`with_binary`, default `alass`, already wired to `alass_path` in the daemon config) and a timeout. It runs `alass <sub> <video> <output>` against a temp output file, parses both input and output back through `srt::parse`, and judges plausibility structurally per DESIGN.md §15: any cue moved more than 60 seconds from its start, or cues lost between input and output, yields `Outcome::Rejected(Rejection)` so the caller keeps the unsynced original and records `SubtitleSync::Rejected` on the row (#186's columns and `mark_synced` already exist). A plausible result returns `Outcome::Accepted { content }` with the synced SRT. New error variants: `Spawn` (missing binary — a #200 health condition, non-transient), `Timeout` (transient), `SyncFailed` (non-zero exit, carries status + trimmed stderr) and `NotSrt`. The daemon-side invocation lands with #195/#196, which own writing sidecars to disk; nothing calls the syncer yet. Branch: `subtitles/194-alass-sync` `just ci`: passed via the gate (569 tests, lint, fmt, web-check all green). No fixtures involved; all sync tests generate fake `alass` shell scripts at runtime. ## Findings - Non-SRT downloads (ASS/VTT) reach `sync()` only after #213 converts them; until then the wrapper fails them with `Error::NotSrt`. That is the correct failure mode but means ASS fetches will be recorded as failed attempts if wired before #213 merges.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: yolo/arr#194