mcpField
V
video-subtitle-cutter

video-subtitle-cutter

github.com/different-ai创意与媒体25592 次安装30 周前更新
代码仓库

关于 video-subtitle-cutter

video-subtitle-cutter 是用于 AI 辅助视频清理的技能,涵盖 Whisper 转录、依据文本选择剪辑点、使用 FFmpeg 编辑和生成字幕。它面向需要删除口语瑕疵并通过重新编码保持剪辑流畅的创作者。

主要功能

  1. 01使用 Whisper 生成时间戳转录
  2. 02借助 AI 识别剪辑片段
  3. 03用 FFmpeg 剪切并拼接视频
  4. 04指导重新编码以保持剪辑流畅
  5. 05生成 SRT 字幕

适用场景

  1. 01删除填充词和说错重来的片段
  2. 02精简播客或访谈视频
  3. 03为剪辑后的视频制作字幕

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/different-ai/agent-bank --skill video-subtitle-cutter

安装后在 Claude Code 中输入/video-subtitle-cutter确认已加载。

详细介绍

我的工作

通过以下方式自动化视频剪辑:

  1. 将视频转录为带时间戳的字幕(Whisper)
  2. 使用 AI 分析转录内容,识别需要剪切的部分(填充词、停顿、错误)
  3. 生成 FFmpeg 命令,剪切并拼接干净的片段
  4. 为最终视频生成字幕(SRT)

重要:始终重新编码(切勿使用 -c copy)

最常见的错误是使用 -c copy 剪切。 这会导致:

  • 剪切点出现画面冻结(冻结 1–8 秒)
  • 音视频不同步
  • 播放卡顿

原因是什么? H.264 视频每隔 2–10 秒会使用关键帧(I 帧)。-c copy 只能在关键帧处剪切,因此 FFmpeg 会包含额外的帧,导致画面冻结。

解决方案: 始终使用质量设置重新编码片段:

# WRONG - causes freeze frames
ffmpeg -ss 10 -i video.mp4 -t 5 -c copy segment.mp4

# CORRECT - smooth cuts at any timestamp
ffmpeg -ss 10 -i video.mp4 -t 5 \
  -c:v libx264 -preset fast -crf 18 \
  -c:a aac -b:a 192k \
  -avoid_negative_ts make_zero \
  segment.mp4

质量预设(CRF = 恒定速率因子):

  • crf 15-17 = 接近无损(文件较大)
  • crf 18-20 = 高质量(推荐)
  • crf 21-23 = 良好质量(文件较小)
  • crf 24-28 = 中等质量(文件小得多)

前提条件

# Install Whisper (choose one)
pip install openai-whisper          # Local (requires Python 3.9+)
# OR use OpenAI API (no local install needed)

# Install FFmpeg
brew install ffmpeg                  # macOS
sudo apt install ffmpeg              # Linux

快速开始

第 1 步:转录视频

选项 A:本地 Whisper(免费,速度较慢)

whisper video.mp4 --model medium --output_format json --output_dir ./

选项 B:OpenAI Whisper API(速度快,付费)

curl https://api.openai.com/v1/audio/transcriptions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -F file="@video.mp4" \
  -F model="whisper-1" \
  -F response_format="verbose_json" \
  -F timestamp_granularities[]="segment" \
  > transcript.json

选项 C:先使用 ffmpeg 提取音频(适用于大型文件)

# Extract audio (much smaller file to upload)
ffmpeg -i video.mp4 -vn -acodec libmp3lame -q:a 2 audio.mp3

# Then transcribe the audio
whisper audio.mp3 --model medium --output_format json
第 2 步:分析转录内容以确定剪切位置

使用以下提示词将转录内容提供给 AI:

Analyze this video transcript and identify segments to CUT (remove).

TRANSCRIPT:
{paste transcript.json segments here}

Identify these issues:
1. FILLER WORDS: "um", "uh", "like", "you know", "basically", "actually", "so", "right"
2. FALSE STARTS: Incomplete sentences that restart ("I think— actually, let me...")
3. LONG PAUSES: Gaps > 1.5 seconds between segments
4. REPETITIONS: Same word/phrase repeated ("really really really")
5. CORRECTIONS: "Wait, I meant...", "Sorry, let me rephrase..."
6. TANGENTS: Off-topic rambling (use judgment)

Return a JSON array of segments to KEEP (not cut):
[
  {"start": 0.0, "end": 2.5, "text": "Welcome to this video"},
  {"start": 3.1, "end": 8.4, "text": "Today we're going to cover..."},
  ...
]

Rules:
- Merge adjacent keep segments if gap < 0.3s
- Ensure cuts don't happen mid-word (check word boundaries)
- Preserve natural speech rhythm (don't over-cut)
- When in doubt, keep the segment
第 3 步:生成 FFmpeg 命令(高质量)

确定要保留的片段后,使用此 Python 脚本实现平滑剪切:

import json
import subprocess
import os

VIDEO_INPUT = "video.mp4"
VIDEO_OUTPUT = "video_clean.mp4"
SEGMENTS_FILE = "keep_segments.json"

with open(SEGMENTS_FILE) as f:
    segments = json.load(f)

segment_files = []
for i, seg in enumerate(segments):
    outfile = f"temp_seg_{i:04d}.mp4"
    segment_files.append(outfile)

    # MUST re-encode for smooth cuts (no -c copy!)
    cmd = [
        'ffmpeg', '-y',
        '-ss', str(seg['start']),      # Seek BEFORE input (fast)
        '-i', VIDEO_INPUT,
        '-t', str(seg['end'] - seg['start']),  # Duration
        '-c:v', 'libx264',
        '-preset', 'fast',              # fast/medium/slow
        '-crf', '18',                   # Quality (lower = better, 15-23 recommended)
        '-c:a', 'aac',
        '-b:a', '192k',
        '-avoid_negative_ts', 'make_zero',  # Fix timestamp issues
        '-async', '1',                  # Sync audio
        outfile
    ]
    subprocess.run(cmd, capture_output=True)
    print(f"✓ Segment {i+1}/{len(segments)}")

# Create concat file
with open('temp_concat.txt', 'w') as f:
    for sf in segment_files:
        f.write(f"file '{sf}'\n")

# Concatenate (can use -c copy here since all segments match)
subprocess.run([
    'ffmpeg', '-y', '-f', 'concat', '-safe', '0',
    '-i', 'temp_concat.txt',
    '-c', 'copy',
    VIDEO_OUTPUT
])

# Cleanup
for sf in segment_files:
    os.remove(sf)
os.remove('temp_concat.txt')
print(f"✓ Created: {VIDEO_OUTPUT}")

关键标志说明:

  • -ss 放在 -i 前:快速定位(无需解码整个视频)
  • -t:片段时长(不是结束时间)
  • -crf 18:高质量编码
  • -avoid_negative_ts make_zero:修复 concat 时间戳问题
  • -async 1:保持音频同步
第 4 步:生成字幕

创建最终视频后,使用 Whisper 重新生成字幕:

# Generate SRT subtitles for the cleaned video
whisper video_clean.mp4 --model medium --output_format srt --output_dir ./

# For higher accuracy (slower):
whisper video_clean.mp4 --model large --output_format srt --language en

# Output: video_clean.srt

将字幕烧录到视频中(可选):

# Embed subtitles permanently
ffmpeg -i video_clean.mp4 -vf "subtitles=video_clean.srt:force_style='FontSize=24,FontName=Arial,PrimaryColour=&HFFFFFF,OutlineColour=&H000000,Outline=2'" -c:a copy video_with_subs.mp4

字幕样式选项:

  • FontSize=24 - 文字大小
  • FontName=Arial - 字体
  • PrimaryColour=&HFFFFFF - 白色文字(BGR 格式)
  • OutlineColour=&H000000 - 黑色描边
  • Outline=2 - 描边粗细
  • MarginV=50 - 与底部的距离

完整工作流脚本(高质量)

#!/usr/bin/env python3
"""
video_clean.py - Clean up video by removing filler words/pauses
Uses re-encoding for smooth cuts (no freeze frames)
"""

import json
import subprocess
import os
import sys

def get_duration(filepath):
    """Get video duration in seconds"""
    result = subprocess.run([
        'ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_format', filepath
    ], capture_output=True, text=True)
    return float(json.loads(result.stdout)['format']['duration'])

def extract_segment(input_file, start, end, output_file, crf=18, preset='fast'):
    """Extract a segment with re-encoding for smooth cuts"""
    cmd = [
        'ffmpeg', '-y',
        '-ss', str(start),
        '-i', input_file,
        '-t', str(end - start),
        '-c:v', 'libx264',
        '-preset', preset,
        '-crf', str(crf),
        '-c:a', 'aac',
        '-b:a', '192k',
        '-avoid_negative_ts', 'make_zero',
        '-async', '1',
        output_file
    ]
    return subprocess.run(cmd, capture_output=True, text=True)

def concatenate_segments(segment_files, output_file):
    """Concatenate segments into final video"""
    with open('temp_concat.txt', 'w') as f:
        for sf in segment_files:
            f.write(f"file '{sf}'\n")

    subprocess.run([
        'ffmpeg', '-y', '-f', 'concat', '-safe', '0',
        '-i', 'temp_concat.txt',
        '-c', 'copy',
        output_file
    ], capture_output=True)

    os.remove('temp_concat.txt')

def generate_subtitles(video_file, model='medium'):
    """Generate SRT subtitles using Whisper"""
    subprocess.run([
        'whisper', video_file,
        '--model', model,
        '--output_format', 'srt',
        '--output_dir', './'
    ])

def main(video_input, segments, output_name, crf=18):
    """Main workflow"""
    segment_files = []

    print(f"\n{'='*50}")
    print(f"Processing: {video_input}")
    print(f"Quality: CRF {crf} (lower=better, 15-23 recommended)")
    print(f"{'='*50}\n")

    # Extract segments with re-encoding
    for i, seg in enumerate(segments):
        outfile = f"temp_seg_{i:04d}.mp4"
        segment_files.append(outfile)

        result = extract_segment(video_input, seg['start'], seg['end'], outfile, crf)
        if result.returncode == 0:
            duration = seg['end'] - seg['start']
            print(f"✓ Segment {i+1}/{len(segments)}: {duration:.1f}s")
        else:
            print(f"✗ Error on segment {i+1}")
            print(result.stderr[-500:])

    # Concatenate
    print("\nConcatenating segments...")
    concatenate_segments(segment_files, output_name)

    # Cleanup temp segments
    for sf in segment_files:
        os.remove(sf)

    # Generate subtitles
    print("\nGenerating subtitles...")
    generate_subtitles(output_name)

    # Stats
    orig_duration = get_duration(video_input)
    new_duration = get_duration(output_name)
    orig_size = os.path.getsize(video_input) / (1024*1024)
    new_size = os.path.getsize(output_name) / (1024*1024)

    print(f"\n{'='*50}")
    print(f"COMPLETE")
    print(f"{'='*50}")
    print(f"Original:  {orig_duration:.0f}s | {orig_size:.1f} MB")
    print(f"Output:    {new_duration:.0f}s | {new_size:.1f} MB")
    print(f"Removed:   {orig_duration - new_duration:.0f}s ({((orig_duration - new_duration)/orig_duration)*100:.0f}%)")
    print(f"Video:     {output_name}")
    print(f"Subtitles: {output_name.replace('.mp4', '.srt')}")

if __name__ == '__main__':
    # Example usage
    VIDEO = "input.mp4"
    SEGMENTS = [
        {"start": 0.0, "end": 10.5},
        {"start": 12.3, "end": 25.0},
        # ... add your segments
    ]
    main(VIDEO, SEGMENTS, "output_clean.mp4", crf=18)

AI 分析提示词模板

基础清理(仅去除填充词)
Remove filler words from this transcript. Return segments to KEEP.

Filler words to remove: um, uh, like, you know, basically, actually, so, right, I mean

TRANSCRIPT SEGMENTS:
{segments}

Return JSON: [{"start": float, "end": float, "text": "cleaned text"}, ...]
深度清理(播客/访谈)
Clean this podcast transcript for a tight, professional edit.

REMOVE:
- All filler words (um, uh, like, you know, basically, so, right)
- False starts and restarts
- Pauses longer than 1 second
- Repetitions
- Off-topic tangents
- "That's a great question" type filler responses
- Excessive laughter/reactions (keep some for naturalness)

KEEP:
- Core content and insights
- Natural transitions
- Important reactions that add context

TRANSCRIPT:
{segments}

Return JSON array of segments to KEEP with cleaned text.
轻度清理(保留自然感)
Lightly clean this transcript while preserving natural speech patterns.

ONLY REMOVE:
- "Um" and "uh" when standalone (not part of thinking pause)
- Obvious mistakes followed by corrections
- Technical issues (coughs, phone rings, etc.)

PRESERVE:
- Natural "like" and "you know" that add personality
- Thinking pauses that feel authentic
- Personality quirks

TRANSCRIPT:
{segments}

Return JSON array of segments to KEEP.

转录格式参考

Whisper JSON 输出
{
  "text": "Full transcript text...",
  "segments": [
    {
      "id": 0,
      "start": 0.0,
      "end": 2.5,
      "text": " Welcome to this video.",
      "tokens": [50364, 5765, ...],
      "temperature": 0.0,
      "avg_logprob": -0.25,
      "compression_ratio": 1.2,
      "no_speech_prob": 0.01
    },
    {
      "id": 1,
      "start": 2.5,
      "end": 5.8,
      "text": " Um, so today we're going to...",
      ...
    }
  ],
  "language": "en"
}
保留片段格式(用于 FFmpeg)
[
  { "start": 0.0, "end": 2.5, "text": "Welcome to this video." },
  { "start": 3.2, "end": 5.8, "text": "Today we're going to..." }
]

高级:词级时间戳

要精确去除填充词,请使用词级时间戳:

# Whisper with word timestamps
whisper video.mp4 --model medium --word_timestamps True --output_format json

这会提供:

{
  "segments": [
    {
      "start": 0.0,
      "end": 2.5,
      "text": "Um welcome to this video",
      "words": [
        { "word": "Um", "start": 0.0, "end": 0.3 },
        { "word": "welcome", "start": 0.5, "end": 0.9 },
        { "word": "to", "start": 0.9, "end": 1.0 },
        { "word": "this", "start": 1.0, "end": 1.2 },
        { "word": "video", "start": 1.2, "end": 1.6 }
      ]
    }
  ]
}

现在,你可以精准地剪掉 “Um”(0.0–0.3),并保留 “welcome to this video”(0.5–1.6)。


故障排除

剪切点画面冻结(最常见)

原因: 使用了 -c copy,它只能在关键帧处剪切。

解决方案: 始终使用 -c:v libx264 -crf 18 重新编码(参见上面的示例)。

音视频不同步

提取片段时添加以下标志:

ffmpeg -ss 10 -i video.mp4 -t 5 \
  -c:v libx264 -crf 18 \
  -c:a aac -b:a 192k \
  -avoid_negative_ts make_zero \  # Fix negative timestamps
  -async 1 \                       # Sync audio to video
  segment.mp4
剪切处听起来很突兀

为每个片段添加音频淡入/淡出:

ffmpeg -ss 10 -i video.mp4 -t 5 \
  -c:v libx264 -crf 18 \
  -af "afade=t=in:st=0:d=0.05,afade=t=out:st=4.95:d=0.05" \
  -c:a aac segment.mp4
大型文件处理耗时过长
  1. 使用 -preset fast 或 -preset veryfast(以画质换取速度)
  2. 先提取音频再转录(文件会小得多)
  3. 使用 Whisper API 替代本地模型
  4. 并行处理(同时处理多个片段)
# Faster encoding (slightly lower quality)
ffmpeg ... -preset veryfast -crf 20 ...

# Even faster for previews
ffmpeg ... -preset ultrafast -crf 23 ...
Whisper 漏掉词语
  • 使用 --model large 以提高准确度
  • 使用 --language en 强制使用英语
  • 先标准化音频:
ffmpeg -i video.mp4 -af "loudnorm=I=-16:TP=-1.5:LRA=11" -c:v copy normalized.mp4
重新编码后文件过大

增大 CRF 值(数值越高 = 文件越小,画质越低):

# Original quality (large)
-crf 18

# Good quality (medium)
-crf 22

# Acceptable quality (small)
-crf 26

与 OpenCode 集成

在 OpenCode 中使用此技能时:

  1. 提取音频(加快转录速度):

    ffmpeg -i video.mp4 -vn -acodec libmp3lame -q:a 2 temp_audio.mp3 -y
    
  2. 使用 Whisper 转录:

    whisper temp_audio.mp3 --model medium --output_format json --output_dir ./
    
  3. 读取 transcript.json 并分析片段

  4. 根据以下标准确定要保留的片段:

    • 删除填充词(um、uh、like、you know)
    • 删除较长的停顿(间隔 >1.5s)
    • 删除说错后重说的片段和重复内容
    • 对于“短视频风格”:仅保留吸引开场 + 关键要点 + 行动号召
  5. 重新编码并拼接(必须重新编码,绝不能使用 -c copy):

    # Use the Python script above with crf=18 for quality
    
  6. 为最终视频生成字幕:

    whisper output.mp4 --model medium --output_format srt
    
  7. 报告结果,附上处理前后的统计数据

质量设置参考
用途CRF预设备注
归档/母版15-17slow接近无损,文件较大
YouTube/Vimeo18-20medium高质量,推荐
社交媒体21-23fast画质良好,文件较小
预览/草稿24-28veryfast快速渲染
反面做法(切勿采用)
# WRONG: -c copy causes freeze frames
ffmpeg -ss 10 -i video.mp4 -t 5 -c copy segment.mp4

# WRONG: -to instead of -t with -ss before -i
ffmpeg -ss 10 -i video.mp4 -to 15 ...  # -to is absolute, not relative

# WRONG: Missing timestamp fix flags
ffmpeg ... -c:v libx264 ...  # Missing -avoid_negative_ts

评论

支持 Markdown