feat: bring the ripping and tagging tools into the repo #5

Merged
nugget merged 3 commits from tools/ripping-and-tagging into main 2026-08-21 13:12:19 -05:00
Collaborator

Moves the two remaining tools out of scratch and into the repo, and adds the
verification that would have caught this week's worst failure.

feat: add rip_disc, which verifies it ripped the title it asked for

makemkvcon mkv does not always enumerate the same titles as makemkvcon info. With identical flags on one disc, info offered 29 titles and mkv
offered 26 — it silently dropped the episode's two playlists because their first
sector was unreadable. Every index then referred to a different title, one or
more positions along.

Twenty titles were ripped that way. Every completion message reported success,
every output file existed and was non-empty, and not one was the title
requested
— four were duplicates of each other and the episode was absent
entirely. The existing checks (did it save something; does the file exist) were
both satisfied by all twenty, which is exactly why the failure was invisible.

The check that catches it is comparing each output's duration against the
duration the scan recorded for that title. Replayed against the real logs it
aborts on title #1:

### WRONG TITLE t3: expected 3616s, got 1094s
###   scan title 3 = 00800.mpls | rip title 3 = 00304.mpls
###   scan enumerated 29 titles, this rip enumerated 26
### ABORTING: every remaining index is suspect.

It does not false-positive: a title that genuinely matched passes at 236s vs 236s.
Treat a divergence as a reason to stop rather than an off-by-one to correct —
the dropped titles need not be contiguous.

feat: add tag_episode for naming and flagging an episode's tracks

The operator supplies the judgement calls — episode title, who speaks on each
commentary. Everything mechanical is measured, because track position has
been wrong in every way it can be:

  • Commentaries are the trailing run of English tracks. Episodes without one
    end on a foreign dub; some carry two, and Hardhome carries three.
  • Companion cores are found structurally — a lossless track of the same
    language immediately before — since one file can hold an English TrueHD/AC-3
    pair and a Russian DTS-HD MA/DTS pair.
  • Forced subtitles are found against the file's own median cue count.
    Absolute thresholds break both ways: full tracks run 380–1500 cues depending
    on runtime, and a forced track can span nearly a whole episode on four cues.
  • Every flag is written explicitly as 0 or 1, since mkvpropedit changes
    only what it is told and a re-run otherwise leaves stale flags under corrected
    names.

Cue counts are cached beside the media file rather than beside the script:
title numbers restart at t00 on every disc, so any coarser key collides across
discs of one set and serves another disc's data.


Verification

AGENTS.md gate: syntax clean, --help passes on both new scripts. Then against
real media:

  • rip_disc.py replayed on the failing disc's logs — aborts on title 1 with the
    diagnostic above; passes the title that genuinely matched
  • tag_episode.py — byte-identical output to the scratch version on a known file
    (S01E01: same track names, same commentary, same flags)

Both were generalised on the way in: rip_disc.py takes a destination directory
and honours $MAKEMKVCON; tag_episode.py takes --series. Neither carries a
hardcoded path any more.

Still failing on main: pickthumb.py, probe_mkv.py and scan_titles.py
exit non-zero from --help, so the repo does not pass its own documented gate.
Untouched here — worth a separate PR.

Moves the two remaining tools out of scratch and into the repo, and adds the verification that would have caught this week's worst failure. ### `feat: add rip_disc, which verifies it ripped the title it asked for` **`makemkvcon mkv` does not always enumerate the same titles as `makemkvcon info`.** With identical flags on one disc, `info` offered 29 titles and `mkv` offered 26 — it silently dropped the episode's two playlists because their first sector was unreadable. Every index then referred to a *different* title, one or more positions along. Twenty titles were ripped that way. Every completion message reported success, every output file existed and was non-empty, and **not one was the title requested** — four were duplicates of each other and the episode was absent entirely. The existing checks (did it save something; does the file exist) were both satisfied by all twenty, which is exactly why the failure was invisible. The check that catches it is comparing each output's **duration** against the duration the scan recorded for that title. Replayed against the real logs it aborts on title #1: ``` ### WRONG TITLE t3: expected 3616s, got 1094s ### scan title 3 = 00800.mpls | rip title 3 = 00304.mpls ### scan enumerated 29 titles, this rip enumerated 26 ### ABORTING: every remaining index is suspect. ``` It does not false-positive: a title that genuinely matched passes at 236s vs 236s. Treat a divergence as a reason to stop rather than an off-by-one to correct — the dropped titles need not be contiguous. ### `feat: add tag_episode for naming and flagging an episode's tracks` The operator supplies the judgement calls — episode title, who speaks on each commentary. Everything mechanical is **measured**, because track position has been wrong in every way it can be: - **Commentaries are the trailing run of English tracks.** Episodes without one end on a foreign dub; some carry two, and *Hardhome* carries three. - **Companion cores are found structurally** — a lossless track of the same language immediately before — since one file can hold an English TrueHD/AC-3 pair *and* a Russian DTS-HD MA/DTS pair. - **Forced subtitles are found against the file's own median cue count.** Absolute thresholds break both ways: full tracks run 380–1500 cues depending on runtime, and a forced track can span nearly a whole episode on four cues. - **Every flag is written explicitly as 0 or 1**, since `mkvpropedit` changes only what it is told and a re-run otherwise leaves stale flags under corrected names. Cue counts are cached beside the media file rather than beside the script: title numbers restart at `t00` on every disc, so any coarser key collides across discs of one set and serves another disc's data. --- ### Verification `AGENTS.md` gate: syntax clean, `--help` passes on both new scripts. Then against real media: - `rip_disc.py` replayed on the failing disc's logs — aborts on title 1 with the diagnostic above; passes the title that genuinely matched - `tag_episode.py` — byte-identical output to the scratch version on a known file (S01E01: same track names, same commentary, same flags) Both were generalised on the way in: `rip_disc.py` takes a destination directory and honours `$MAKEMKVCON`; `tag_episode.py` takes `--series`. Neither carries a hardcoded path any more. **Still failing on `main`:** `pickthumb.py`, `probe_mkv.py` and `scan_titles.py` exit non-zero from `--help`, so the repo does not pass its own documented gate. Untouched here — worth a separate PR.
makemkvcon's 'mkv' mode does not always enumerate the same titles as its
'info' mode. With identical flags on one disc, info offered 29 titles and mkv
offered 26: it silently dropped the episode's two playlists because their
first sector was unreadable. Every index then referred to a different title,
one or more positions along.

Twenty titles were ripped that way. Every completion message reported success,
every output file existed and was non-empty, and not one was the title that
had been requested. Four were duplicates of each other and the episode was
absent entirely. The existing checks -- did it save something, does the file
exist -- were both satisfied by all twenty.

The check that catches it is comparing each output's duration against the
duration the scan recorded for that title. rip_disc.py does that and aborts on
the first mismatch, reporting the playlist each index resolved to under both
enumerations so the divergence is visible rather than inferred.

A divergence is a reason to stop, not an off-by-one to correct: the dropped
titles need not be contiguous.
The operator supplies the judgement calls -- the episode title, and who speaks
on each commentary. Everything mechanical is measured from the file rather
than inferred from track position, because position has been wrong in every
way it can be:

- Commentaries are the trailing run of English tracks. Episodes without one
  end on a foreign dub; some carry two, and one carries three.
- Companion cores are found by looking for a lossless track of the same
  language immediately before, since a file can hold an English TrueHD/AC-3
  pair and a Russian DTS-HD MA/DTS pair at once.
- Forced subtitles are found against the file's own median cue count. Absolute
  thresholds break both ways: a full track runs 380-1500 cues depending on
  runtime, and a forced track can span nearly the whole episode on four cues.
- Every flag is written explicitly as 0 or 1, since mkvpropedit changes only
  what it is told and a re-run otherwise leaves stale flags under corrected
  names.

Cue counts are cached beside the media file, not beside the script: title
numbers restart at t00 on every disc, so any coarser key collides across discs
of one set and silently serves another disc's data.
Caching beside the media file made the key unique per file, which is what the
collision fix required -- but it drops a .subcues directory into every season
folder of the user's library. That is their data, not ours; on a five-season
set it left a megabyte of scratch scattered through the tree.

Hashing the file's absolute path into a per-user cache directory satisfies
both constraints: still unique per file, so discs of one set cannot serve each
other's packet data, and nothing is written inside the library.
nugget merged commit 260aa9bdbc into main 2026-08-21 13:12:19 -05:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
nugget/media-import-tooling!5
No description provided.