Skip to content

Chapter Insertion

Chapter Inserter writes chapter metadata into an existing video. It can use fixed timestamps, detect fade boundaries with commercial-detector-ml, or combine both in one ordered plan. The final write is an FFmpeg stream-copy remux, so media streams are not re-encoded.

Job shape

Each job has one input, one output, and at least one plan segment:

json
{
  "input_source": "file:///media/episode.mkv",
  "output_source": "file:///media/episode-with-chapters.mkv",
  "segments": [
    {"type": "fixed", "at": "00:01:00"},
    {"type": "auto"},
    {"type": "fixed", "at": "end-00:01:00"}
  ]
}

Input and output may name the same source. The service stages the full input before writing the result, so in-place chapter updates are supported.

Segment types

TypeFieldsBehavior
fixedrequired atAdds exactly one user-selected boundary; invalid geometry fails the job.
autooptional start, endSearches a region for fades; unusable detected boundaries are reported as dropped.

There is no separate insertion mode. Detect everything with one auto segment:

json
{"segments": [{"type": "auto"}]}

Use only fixed segments to write a manual plan.

Timestamp formats

at, start, and end accept:

  • HH:MM:SS with optional milliseconds;
  • bare seconds, with optional milliseconds;
  • an end-relative value such as end-00:01:00 or -60.

An auto segment inherits an omitted start from the previous fixed mark and an omitted end from the next fixed mark. At the plan edges it falls back to the configured start and end margins.

An explicit auto boundary may narrow its inherited window but cannot cross it. Adjacent auto segments require the first segment's end and the second segment's start so the seam is unambiguous.

Detection and output options

FieldDefaultMeaning
anchormidUse the mid, start, or end of a detected fade.
start_margin5 secondsIgnore the beginning of the file.
end_margin20 secondsIgnore the end of the file.
min_gap30 secondsMinimum spacing between boundaries.
thresholdmodel defaultOverride the trained detection threshold; must be between 0 and 1.
title_formatAct {i}Format generated chapter titles.
existing_chaptersskipskip, replace, compare, or fail.
dry_runfalseDetect and report without writing a file.

Plan limits

The service configuration caps submitted plans:

yaml
chapter_inserter:
  plan_limits:
    max_segments: 200
    max_fixed_marks: 100

The limits catch accidental values such as milliseconds supplied as seconds and are passed to the Python process with every request.

Runtime

The Go service owns gRPC, Kafka, River, source staging, and status reporting. A Python subprocess performs ML detection and remuxing. Configure the Python executable, model path, and device (auto, cpu, or cuda) under chapter_inserter in config.yaml.

The default River configuration uses one worker because detection is CPU/GPU intensive and a 120-minute job timeout.

Start both halves in development with:

bash
mise run dev:chapter-inserter

Released under the MIT License.