Skip to content

Releases: xwitty/CutlistDude

1.1.2

Choose a tag to compare

@xwitty xwitty released this 29 Sep 20:05

[1.1.2] - 2026-09-24 (build 96)

Fixed

  • The missing-dependency error now prints a ready-to-paste sudo apt install
    command with the correct package names, instead of only the binary names
    (which are not installable as-is: mkvmerge is in mkvtoolnix, ffprobe in
    ffmpeg). The ffmsindex package is named ffmsindex on current Ubuntu/Debian
    (older releases shipped it in libffms2-utils); both the error message and the
    README now lead with ffmsindex.
  • The working directory now honours $TMPDIR instead of being hard-coded to
    /tmp. On systems where /tmp is a small RAM-backed tmpfs — notably WSL2,
    where it defaults to roughly half of RAM — a full HD recording's intermediate
    files (segment cuts, MPEG-TS parts, the joined video) could overflow /tmp
    and fail with "No space left on device" even when the real disk had hundreds
    of GB free. Set TMPDIR to a directory on a roomy filesystem to relocate all
    scratch files: TMPDIR=~/cutlisttmp ./CutlistDude.sh ...

1.1.1

Choose a tag to compare

@xwitty xwitty released this 28 Sep 14:19

[1.1.1] - 2026-09-24 (build 94)

Fixed

  • OTR_DECODER set in the config file is now honoured. The config was read
    only during the decryption step, which ran after the decoder was first
    looked up — so a configured path was ignored and only a decoder on PATH or
    beside the script was found. The config is now read at startup.
  • Decrypting a .otr2 file whose decrypted form has a different extension
    (e.g. movie.HD.otr2 → movie.HD.mp4) no longer fails with "file not found".
    Instead of assuming the name by stripping .otr2, the script now discovers
    the actual decrypted file (probing .mp4/.mkv/.avi).

1.1.0

Choose a tag to compare

@xwitty xwitty released this 05 Sep 21:06

[1.1.0] - 2026-09-05 (build 93)

Added

  • Support for the modern OTR .mp4 formats — LQ (640×360, AAC only),
    HQ (720×576 anamorphic, AAC+AC3) and HD (1280×720, AAC+AC3):
    Lavf-muxed, closed-GOP x264 at 50 fps.
  • Embedded multi-track audio for the mp4 formats: all audio tracks are kept; the
    AC3 track (untranscoded broadcast original) is default when present, else AAC.
  • Support for the new OTR decoder otr_cli_decoder.py, which decrypts both the
    legacy .otrkey and the newer .otr2 format. It is auto-detected on
    PATH and next to the script, or configured via OTR_DECODER in the config
    file, and is preferred over otrtool when present. --nicht-warten is passed
    to it so it does not wait/poll.
  • .otr2 recordings are now accepted as input. Because the new decoder unlocks
    every encrypted file in the folder in one pass, the encrypted extension on the
    command line is irrelevant — the suffix is stripped and cutting proceeds on the
    decrypted file. The AC3 companion is unlocked in the same pass.

How the mp4 join works (and why it differs from .avi)

  • In .mp4, H.264 SPS/PPS live only in the container (avcC), never inline in the
    bitstream. mkvmerge's --append (used for .avi) collapses all segments to a
    single parameter-set header, so the stream-copied interior and the re-encoded
    edges cannot coexist — the result freezes or shows artefacts.
  • For .mp4 the video segments are therefore joined differently: each segment is
    remuxed to MPEG-TS with the h264_mp4toannexb bitstream filter (TS carries
    SPS/PPS inline per segment), then joined with ffmpeg's concat demuxer, which
    preserves every segment's parameter sets. The joined video is then muxed with
    the audio by mkvmerge (a plain mux, no append). Verified frame-accurate with
    clean seams on LQ/HQ/HD.

Changed

  • otrtool is now a fallback used only when otr_cli_decoder.py is absent, and
    only for .otrkey; a .otr2 file with no new decoder gives an actionable error.
  • Numeric locale forced to LC_NUMERIC=C (without LC_ALL=C) so cutlists with
    fractional seconds parse correctly under locales that use a comma decimal
    separator (de_DE, fr_FR, …), while leaving UTF-8 character handling — and
    therefore non-ASCII filenames — untouched. A stray inherited LC_ALL that
    would override this is neutralised, preserving the charset via LANG.

Fixed

  • --concat (joining two already-cut videos) now uses the same MPEG-TS +
    ffmpeg concat join as the internal segment join, instead of mkvmerge
    --append. Appending two mp4-derived _cut.mkv files with differing H.264
    parameter sets would drop the second file's SPS/PPS and freeze playback after
    the first part; the TS/concat join preserves both.
  • Audio drift on .mp4 files whose container starts at a non-zero timestamp
    (observed on LQ, ~21 ms). The snap-gap calculation now subtracts the
    first-frame PTS before converting mkvmerge split timestamps to frame indices,
    so a constant start offset no longer fakes a 1-2 frame "snap" that triggered
    snap-correction and desynced audio. LQ now takes the same clean gap=0 path as
    HD/HQ.
  • A trailing boundary encode that is legitimately skipped (computed duration
    ≤ 0 after snap-correction) no longer leaves final_mux looking for a
    non-existent segment file.
  • Re-downloading a cutlist with -c now overwrites the existing file instead of
    writing a name.cutlist.1 sibling (the download goes through a temp directory,
    so the server-provided filename is always used verbatim).
  • Removed a duplicated [INFO] Decrypted: line that appeared when decrypting with
    otrtool: the per-file detail is now verbose-only, leaving the single
    user-facing summary line emitted by the main flow.

1.0.0

Choose a tag to compare

@xwitty xwitty released this 02 Aug 14:46

Changelog

All notable changes to this project are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Versions before 1.0.0 were internal development builds and were never published;
their build numbers are kept in the (build N) suffix for continuity.

[1.0.0] - 2026-07-31 (build 83)

First public release.

Added

  • Frame-accurate smart-render cutting: lossless stream copy for the interior of
    each sequence, re-encoded frames only at the cut boundaries.
  • Automatic format detection for OTR recordings: HQ, classic HD, New HD
    (Main@L3.2) and Full HD (Lavf-muxed, 25 / 50 / 59.94 fps).
  • Automatic .avi → .mkv pre-conversion with a dense cue index for the formats
    that need it.
  • .otrkey decryption via otrtool, for both the video and the AC3 companion.
  • Automatic detection of the AC3 companion file, including its encrypted
    .ac3.otrkey form.
  • Cutlist parsing that handles blocks carrying both time-based (Start/Duration)
    and frame-based (StartFrame/DurationFrames) keys.
  • -c, --cutlist-id — download a cutlist from cutlist.at by id, with detection of
    whether it targets the same format (HQ/HD) as the video.
  • -t, --tmdb — prefix the output filename with a TMDB id.
  • --concat — losslessly join two already-cut files after a compatibility check.
  • --force-encode — re-encode in full for sources that cannot be stream-copied.
  • -n, --dry-run — build and validate the render plan without touching media.
  • Four output verbosity levels: -q, default, -v, -vv.
  • -V, --version and -h, --help, -?.
  • Post-segment frame-count verification ([CHECK] lines) comparing planned against
    actual frame counts.
  • Documented exit codes: 0 success, 1 usage/input error, 2 external tool failure.
  • x264 is a hard requirement. An earlier ffmpeg -c:v libx264 fallback was removed
    before release: it could not pass the SPS-matching options (--stitchable,
    --sps-id, --ref) that keep the encoded boundary frames compatible with the
    stream-copied interior, and it seeked by timecode rather than by frame, risking both
    seam corruption and frame-count drift. It was never exercised end to end. ffmpeg
    itself is still required for the trailing encodes and for ffprobe.

Security

  • The credentials file is now ~/.config/CutlistDude.conf only; older filenames are
    no longer read.
  • The script refuses to start if that file is group- or world-readable.
  • Credentials are masked in all logged commands at every verbosity level.

Development history (pre-release highlights)

These were the substantive problems solved while getting to 1.0.0:

  • Cutlist parser rewritten to be driven by the [CutN] section header. Earlier
    versions keyed off Duration=, which appears before DurationFrames= in each
    block and caused sequences to be emitted twice, or with values carried over from
    the previous block.
  • Frame numbering pinned down empirically: cutlist frame N is file frame
    N-1; ffmsindex/x264/ffprobe are 0-based while mkvmerge --split parts-frames is 1-based. Verified by extracting boundary frames as PNGs.
  • Duplicate frames at cut boundaries fixed — a 0-based IDR number was being
    passed into the 1-based parts-frames argument.
  • Off-by-one output length fixed: when a copy snaps to a later IDR it can end one
    frame later than planned, so the trailing encode is now derived from the copy's
    measured frame count rather than from the plan. Output length now matches the
    cutlist exactly.
  • Multi-cut file corruption fixed: per-segment copy re-runs were all being
    renamed to the first segment's filename.
  • IDR probing removed. Probing with a degenerate parts-frames:N-(N+1) range
    made mkvmerge forward-snap to the next keyframe instead of confirming N, so the
    probe advanced one GOP per iteration and never converged on ordinary Lavf HD files.
    The initial copy is its own proof: if mkvmerge produced a valid copy starting at
    frame N, then N is a usable IDR.
  • Open-GOP trailing encodes fixed with -flags +cgop.