Skip to content

Instantly share code, notes, and snippets.

@ramiabraham
Created July 24, 2026 18:14
Show Gist options
  • Select an option

  • Save ramiabraham/4aa0fa85f2121e4387ae6777526ef647 to your computer and use it in GitHub Desktop.

Select an option

Save ramiabraham/4aa0fa85f2121e4387ae6777526ef647 to your computer and use it in GitHub Desktop.
A script using pyscenedetect to detect scene cuts in mp4 video files within any directory, and split scenes to separate files. Uses adaptive detection method.
#!/usr/bin/env bash
#
# extract_scenes.sh
#
# Batch-detect scene cuts in a directory (and all its subdirectories) of
# .mp4 files using PySceneDetect, and save the results (split clips + scene
# list CSVs) to an output directory.
#
# Requirements:
# pip install scenedetect[opencv]
# ffmpeg must be installed and on PATH (used by scenedetect for splitting)
#
# Usage:
# ./extract_scenes.sh <input_dir> <output_dir> [detector] [threshold]
#
# Arguments:
# input_dir Directory to search recursively for .mp4 files (required)
# output_dir Directory where results will be written (required)
# detector Scene detector to use: content, threshold, adaptive (default: adaptive)
# threshold Detector sensitivity threshold (default: 4.0)
#
# Output:
# The input directory's subfolder structure is mirrored under output_dir.
# For each input video "sub/path/name.mp4", creates:
# <output_dir>/sub/path/name/name-Scene-001.mp4, name-Scene-002.mp4, ...
# <output_dir>/sub/path/name/name-Scenes.csv (detected scene cut timecodes)
#
# Example:
# ./extract_scenes.sh ./videos ./scenes content 30
set -euo pipefail
# ---- Argument parsing -------------------------------------------------
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <input_dir> <output_dir> [detector] [threshold]" >&2
echo " detector: content | threshold | adaptive (default: adaptive)" >&2
echo " threshold: numeric sensitivity value (default: 4.0)" >&2
exit 1
fi
INPUT_DIR="$1"
OUTPUT_DIR="$2"
DETECTOR="${3:-adaptive}"
THRESHOLD="${4:-4.0}"
# ---- Sanity checks ------------------------------------------------------
if [[ ! -d "$INPUT_DIR" ]]; then
echo "Error: input directory '$INPUT_DIR' does not exist." >&2
exit 1
fi
if ! command -v scenedetect >/dev/null 2>&1; then
echo "Error: 'scenedetect' command not found." >&2
echo "Install it with: pip install scenedetect[opencv]" >&2
exit 1
fi
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "Error: 'ffmpeg' not found on PATH. Install ffmpeg to enable video splitting." >&2
exit 1
fi
case "$DETECTOR" in
content|threshold|adaptive) ;;
*)
echo "Error: unknown detector '$DETECTOR'. Use content, threshold, or adaptive." >&2
exit 1
;;
esac
mkdir -p "$OUTPUT_DIR"
# ---- Collect input files (recursively, including subdirectories) --------
mp4_files=()
while IFS= read -r -d '' f; do
base="$(basename "$f")"
if [[ "$base" == ._* ]]; then
continue
fi
mp4_files+=("$f")
done < <(find "$INPUT_DIR" -type f -iname '*.mp4' -print0 | sort -z)
if [[ ${#mp4_files[@]} -eq 0 ]]; then
echo "No .mp4 files found in '$INPUT_DIR' or its subdirectories." >&2
exit 1
fi
echo "Found ${#mp4_files[@]} .mp4 file(s) in '$INPUT_DIR' (including subdirectories)."
echo "Detector: $DETECTOR Threshold: $THRESHOLD"
echo "Output directory: $OUTPUT_DIR"
echo "-----------------------------------------------------"
fail_count=0
# ---- Process each file ---------------------------------------------------
for filepath in "${mp4_files[@]}"; do
filename="$(basename "$filepath")"
name="${filename%.*}"
# Preserve the input's relative subdirectory structure in the output dir,
# so files with the same name in different subfolders don't collide.
rel_dir="$(dirname "$filepath")"
rel_dir="${rel_dir#"$INPUT_DIR"}"
rel_dir="${rel_dir#/}"
video_out_dir="$OUTPUT_DIR/$rel_dir/$name"
mkdir -p "$video_out_dir"
echo ""
echo "Processing: ${rel_dir:+$rel_dir/}$filename"
echo " -> $video_out_dir"
if scenedetect \
-i "$filepath" \
-o "$video_out_dir" \
detect-"$DETECTOR" -t "$THRESHOLD" \
list-scenes \
split-video ; then
echo " Done: $filename"
else
echo " FAILED: $filename" >&2
fail_count=$((fail_count + 1))
fi
done
echo ""
echo "-----------------------------------------------------"
if [[ $fail_count -gt 0 ]]; then
echo "Completed with $fail_count failure(s). See messages above."
exit 1
else
echo "All videos processed successfully. Results in '$OUTPUT_DIR'."
fi
@ramiabraham

Copy link
Copy Markdown
Author

The adaptive detection method with a threshold of 4.0 works especially well for scene detection in highly dynamic video content containing CGI, such as sci-fi like Star Trek TNG.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment