transcript-video-section-splitter
Use when transcribing a video, deriving topic-based section boundaries from the transcript, and splitting the video into named clips with ffmpeg plus a manifest, instead of cutting by fixed duration.
SKILL.mdskills/transcript-video-section-splitter/SKILL.md
GitHubTranscript Video Section Splitter
Use this skill when the user wants a long video divided into meaningful content sections based on what is said. The output should include a timestamped transcript, section metadata, and split video clips.
Core Rules
- Transcribe first; do not guess section boundaries from duration alone.
- Keep the original media unchanged. Split from a derived edited video when the user asks to section that edited version.
- If the user asks to split a regenerated or edited variant "as before", reuse the existing section JSON only when the variant preserves the same timeline and final duration.
- Use topic changes, long pauses, repeated framing phrases, and concrete action shifts to set boundaries.
- Store section boundaries in durable JSON before cutting clips.
- Preserve audio and avoid unnecessary re-encoding when exact keyframe cuts are acceptable; re-encode when frame-accurate starts matter.
- Write variant clips to a variant-specific folder such as
artifacts/.../v2/sections/so older split outputs are not overwritten. - Keep private or raw transcripts separate when the clips or section summaries will be published.
Recommended Layout
text/
source.md # timestamped transcript
artifacts/video_sections/
source_sections.json # machine-readable boundaries
source_sections.md # human-readable chapter table
clips/
01_topic-slug.mp4
manifest.json
Transcription
Use the project convention first. Common commands:
python transcribe.py \
-i video/source.mp4 \
-o text/source.md \
-m medium \
-l zh
For higher-quality alignment or speaker labels:
conda run -n whisperx whisperx video/source.mp4 \
--model large-v2 \
--language zh \
--task transcribe \
--output_dir text/source.whisperx \
--output_format json
Section Metadata
Create JSON with explicit starts, ends, slugs, titles, and summaries:
[
{
"index": 1,
"start": "00:00:00.000",
"end": "00:00:52.000",
"slug": "problem-framing",
"title": "Problem framing",
"summary": "The opening defines the key problem and constraints."
}
]
Boundary guidance:
- Use
00:00:00.000for the first start unless there is a clear pre-roll to remove. - End the final section at the media duration from
ffprobe. - Prefer section lengths that match natural content, not equal sizes.
- Avoid cutting in the middle of a sentence unless the user asks for short social clips.
- Include a Markdown table for review before cutting if the sectioning is subjective.
Splitting
Stream-copy is fast but may cut on nearby keyframes:
ffmpeg -y -ss 12.000 -i edited_video.mp4 -t 48.000 -c copy clips/01_topic.mp4
Use re-encoding for frame-accurate cuts:
ffmpeg -y -ss 12.000 -i edited_video.mp4 -t 48.000 \
-c:v libx264 -pix_fmt yuv420p -c:a aac clips/01_topic.mp4
Write a manifest.json with clip index, title, start, end, duration, and path.
When a helper script already exists, prefer it over hand-running many ffmpeg commands. For example:
python3 split_video_by_sections.py \
--input artifacts/edited_variant/source_edited.mp4 \
--sections text/source_sections.json \
--output-dir artifacts/edited_variant/sections \
--reencode \
--overwrite
For a new edited variant that shares the same transcript timeline:
- Probe the variant duration with
ffprobe. - Confirm it matches the section JSON final end time, allowing only normal container padding.
- Reuse the previous
text/*_sections.json. - Save clips under the variant output folder.
- Validate every clip with
ffprobefor both video and audio streams.
Validation helper:
python3 - <<'PY'
import json, subprocess
from pathlib import Path
manifest = json.loads(Path('artifacts/edited_variant/sections/manifest.json').read_text())
print('clips', len(manifest))
for item in manifest:
path = Path(item['path'])
probe = json.loads(subprocess.check_output([
'ffprobe', '-v', 'error',
'-show_entries', 'format=duration,size',
'-show_entries', 'stream=codec_type',
'-of', 'json',
str(path),
], text=True))
types = [s['codec_type'] for s in probe['streams']]
fmt = probe['format']
print(f"{item['index']:02d} {float(fmt['duration']):8.3f}s {int(fmt['size'])/1024/1024:7.1f} MB {types} {path.name}")
PY
Validation Checklist
- Transcript exists and includes timestamps.
- Section JSON covers the full intended duration without overlap or gaps.
- Section titles and summaries are content-based and neutral.
- Clips exist, play, and preserve audio.
- Reused section JSON is timeline-compatible with the edited variant.
- Variant clips are written to a new folder rather than overwriting previous clips.
- Manifest matches the generated clip filenames.
- At least the first, middle, and final clips are spot-checked.
- Generated sections are derived from transcript content, not private chat context.