Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TV Video Chapter Tool

A Python tool that scans video files for commercial break points (where the video goes to black and audio goes silent) and embeds chapter markers into a single output file. It can process one video or an entire directory of videos. A modern CustomTkinter desktop GUI is included, and the app can be packaged into a standalone executable with PyInstaller.

What it does

  1. Detects intervals of black frames using FFmpeg's blackdetect filter.
  2. Detects silent audio intervals using FFmpeg's silencedetect filter.
  3. Identifies commercial breaks where black video and silent audio overlap.
  4. Writes a single output file for each input with embedded chapter metadata (using FFmpeg's ffmetadata format).
  5. Optionally removes the detected break intervals and concatenates the remaining content into one file.

Requirements

  • Python 3.8+
  • FFmpeg (including ffprobe) installed and on your PATH
  • PyYAML
  • CustomTkinter (for the GUI)
  • PyInstaller (for building the standalone executable)

Installation

git clone https://github.com/kraftmatic/tv-video-chapter-tool.git
cd tv-video-chapter-tool
pip install -r requirements.txt

Desktop GUI

A single-window CustomTkinter GUI is available in gui.py. It wraps the same processing engine and streams all output to a scrollable log window while running in a background thread.

python3 gui.py

GUI features

  • Browse for a single input video or a directory of videos.
  • Optionally choose an output folder; if left blank, the /processed subfolder is used automatically.
  • Toggle remove_breaks and select chapter_boundary placement.
  • Edit black-frame, silence, and break-detection tolerances in a tabbed settings panel.
  • Live log output and progress bar.
  • Open the output folder after processing.

macOS note

On some macOS versions the system Python ships a tkinter build that aborts with macOS 15 (1507) or later required, have instead 15 (1506) !. The GUI now probes the interpreter in a child process and prints a clear error message if this happens. To actually run the GUI, use Python 3.11+ from python.org or Homebrew (brew install python@3.11).

Build a standalone executable

The project includes a PyInstaller build script that produces a single --onefile --noconsole executable.

pip install -r requirements.txt
python3 build.py

After the build completes, the executable is available at:

  • Linux: dist/TVVideoChapterTool
  • Windows: dist\\TVVideoChapterTool.exe
  • macOS: dist/TVVideoChapterTool

You can also run PyInstaller directly:

python3 -m PyInstaller --onefile --noconsole --name TVVideoChapterTool --collect-data customtkinter gui.py

FFmpeg and ffprobe are not bundled in the executable; they must still be installed and available on the system's PATH.

Configuration

Create a YAML configuration file (see config.yaml for an example):

# Path to a single video file or a directory of video files.
input: /path/to/video.mp4

# Optional output file or directory. If omitted, output goes to a "processed"
# subdirectory next to the input file(s): <input_dir>/processed/<name>_chapters.<ext>
# output: /path/to/output.mp4

# Where to place chapter boundaries relative to the detected break interval.
#   - "end":    new chapter starts right after the break (default).
#   - "start":  new chapter starts at the beginning of the break.
#   - "midpoint": new chapter starts at the middle of the break.
#   - "separate": commercial breaks are exposed as their own chapters.
chapter_boundary: end

# If true, the detected break intervals are removed and the remaining content is
# concatenated into a single file. Chapter markers are placed at each segment.
remove_breaks: false

# If true, output files keep the original source file name instead of appending _chapters.
preserve_original_filename: false

# If true, files that already contain chapter metadata are reprocessed (overwritten).
overwrite_existing_chapters: false

black:
  min_duration: 0.5        # seconds; black frames must last at least this long
  picture_threshold: 0.98  # fraction of pixels that must be black (0.0-1.0)
  pixel_threshold: 0.15    # pixel luminance below which is considered black (0.0-1.0)

silence:
  min_duration: 0.5        # seconds; audio must be silent at least this long
  noise_threshold: -60     # dB; anything at or below this is treated as silence

break:
  min_duration: 1.0        # overlap of black + silence must last this long
  merge_gap_seconds: 0.5   # merge adjacent breaks if separated by less than this
  max_duration: 300        # ignore breaks longer than this (e.g. end credits)
  edge_buffer_seconds: 5.0 # ignore breaks that start in the first N seconds or end in the last N seconds

Tuning notes

  • black.pixel_threshold is relative to the pixel luminance range (0.0 = completely black, 1.0 = maximum). Lower values are more sensitive to dark video.
  • black.picture_threshold is the fraction of the frame that must be below pixel_threshold for the frame to count as black.
  • silence.noise_threshold is in dB; anything at or below this value is considered silence.
  • break.min_duration controls how long the black+silent overlap must be to be considered a real commercial break.
  • break.edge_buffer_seconds prevents false positives at the very beginning and end of the video (e.g. intros, credits, or fade-ins/outs).

Usage

Process a single video file

python3 video_chapter_tool.py -c config.yaml

If output is omitted in config.yaml, the result is written to processed/<input_name>_chapters.<ext>. Set preserve_original_filename: true to keep the original name.

Process a whole directory

Set input to a directory instead of a file:

input: /path/to/videos

All supported video files (.mp4, .m4v, .mkv, .mov, .avi, .webm, .ts, .m2ts, .mpg, .mpeg) in the top-level of the directory are processed. Outputs are written to <input_dir>/processed/. Set preserve_original_filename: true to keep each source file's name.

Override input/output on the command line

# override input file
python3 video_chapter_tool.py -c config.yaml -i /path/to/video.mp4

# override output file
python3 video_chapter_tool.py -c config.yaml -i /path/to/video.mp4 -o /path/to/output.mp4

# override input directory and output directory
python3 video_chapter_tool.py -c config.yaml -i /path/to/videos -o /path/to/output_dir

# verbose logging
python3 video_chapter_tool.py -c config.yaml -v

# keep original file name
python3 video_chapter_tool.py -c config.yaml --preserve-original-filename

# overwrite files that already have chapters
python3 video_chapter_tool.py -c config.yaml --overwrite-existing-chapters

Remove commercial breaks instead of just marking them

Set remove_breaks: true in config.yaml. The tool will cut out the detected break intervals, concatenate the remaining segments, and embed chapter markers at each segment boundary.

Keep original output file names

Set preserve_original_filename: true in config.yaml (or check the box in the GUI). Output files will keep the original source file name instead of appending _chapters.

Overwrite files that already have chapters

By default, files that already contain chapter metadata are skipped and copied unchanged. Set overwrite_existing_chapters: true in config.yaml (or check the box in the GUI) to reprocess them instead. When enabled, existing chapters are stripped and new chapters are generated from the detected breaks.

How output is named

Input Output config preserve_original_filename Result
Single file omitted false <input_dir>/processed/<name>_chapters.<ext>
Single file omitted true <input_dir>/processed/<name>.<ext>
Single file file path - exactly that file path
Single file directory path false <output_dir>/<name>_chapters.<ext>
Single file directory path true <output_dir>/<name>.<ext>
Directory omitted false <input_dir>/processed/<name>_chapters.<ext> for each file
Directory omitted true <input_dir>/processed/<name>.<ext> for each file
Directory directory path false <output_dir>/<name>_chapters.<ext> for each file
Directory directory path true <output_dir>/<name>.<ext> for each file
Directory file path - error: a directory is required for batch input

Important notes

  • The tool copies video and audio streams by default (-c copy), so processing is fast and lossless.
  • Chapter metadata is embedded via FFmpeg's ffmetadata muxer. It works well with MP4, MKV, and MOV containers.
  • When remove_breaks is true, segment cuts are made with -c copy, which may land on the nearest keyframe. If you need frame-accurate cuts, re-encode with a different ffmpeg command instead.
  • Make sure FFmpeg and ffprobe are installed and available on your PATH.

License

This project is provided as-is for personal video-library management.

About

Add chapter markers to video files based on black + silent commercial breaks.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages