manus-skills/.kortix/opencode/skills/media/youtube-clipper/scripts/analyze_subtitles.py
zhukang 1c7abd81d1 All 310 Manus skill-hub community skills as a Kortix marketplace registry
- 310 registry:skill items, 1528 files, 7 categories (hub tags)
- registry.json generated by @kortix/registry buildRegistry
- nested SKILL.md in sn-da-* renamed SUBSKILL.md (no phantom skills)
- provenance + licenses: SOURCES.md
2026-09-15 13:37:20 +08:00

252 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
分析字幕并生成章节
解析 VTT 字幕文件,准备数据供 Claude AI 分析
"""
import sys
import re
import json
from pathlib import Path
from typing import List, Dict
from utils import (
time_to_seconds,
seconds_to_time,
get_video_duration_display
)
def parse_vtt(vtt_path: str) -> List[Dict]:
"""
解析 VTT 字幕文件
Args:
vtt_path: VTT 文件路径
Returns:
List[Dict]: 字幕列表,每项包含 {start, end, text}
Example:
[
{'start': 0.0, 'end': 3.5, 'text': 'Hello world'},
{'start': 3.5, 'end': 7.2, 'text': 'This is a test'},
...
]
"""
vtt_path = Path(vtt_path)
if not vtt_path.exists():
raise FileNotFoundError(f"Subtitle file not found: {vtt_path}")
print(f"📊 解析字幕文件: {vtt_path.name}")
subtitles = []
with open(vtt_path, 'r', encoding='utf-8') as f:
content = f.read()
# 移除 WEBVTT 头部和样式信息
content = re.sub(r'^WEBVTT.*?\n\n', '', content, flags=re.DOTALL)
content = re.sub(r'STYLE.*?-->', '', content, flags=re.DOTALL)
# 分割字幕块
blocks = content.strip().split('\n\n')
for block in blocks:
lines = block.strip().split('\n')
if len(lines) < 2:
continue
# 查找时间戳行
timestamp_line = None
text_lines = []
for line in lines:
# 匹配时间戳格式: 00:00:00.000 --> 00:00:03.000
if '-->' in line:
timestamp_line = line
elif line and not line.isdigit(): # 跳过序号
text_lines.append(line)
if not timestamp_line or not text_lines:
continue
# 解析时间戳
try:
# 移除可能的位置信息(如 align:start position:0%)
timestamp_line = re.sub(r'align:.*|position:.*', '', timestamp_line).strip()
times = timestamp_line.split('-->')
start_str = times[0].strip()
end_str = times[1].strip()
start = time_to_seconds(start_str)
end = time_to_seconds(end_str)
# 合并文本行
text = ' '.join(text_lines)
# 清理 HTML 标签(如果有)
text = re.sub(r'<[^>]+>', '', text)
# 清理特殊字符
text = text.strip()
if text:
subtitles.append({
'start': start,
'end': end,
'text': text
})
except Exception as e:
# 跳过无法解析的字幕块
continue
print(f" 找到 {len(subtitles)} 条字幕")
if subtitles:
total_duration = subtitles[-1]['end']
print(f" 总时长: {get_video_duration_display(total_duration)}")
return subtitles
def prepare_analysis_data(subtitles: List[Dict], target_chapter_duration: int = 180) -> Dict:
"""
准备数据供 Claude AI 分析
Args:
subtitles: 字幕列表
target_chapter_duration: 目标章节时长(秒),默认 180 秒(3 分钟)
Returns:
Dict: {
'subtitle_text': 带时间戳的完整字幕文本,
'total_duration': 总时长,
'subtitle_count': 字幕条数,
'target_chapter_duration': 目标章节时长,
'estimated_chapters': 预估章节数
}
"""
if not subtitles:
raise ValueError("No subtitles to analyze")
print(f"\n📝 准备分析数据...")
# 将字幕合并为带时间戳的完整文本
full_text_lines = []
for sub in subtitles:
time_str = seconds_to_time(sub['start'], include_hours=True, use_comma=False)
full_text_lines.append(f"[{time_str}] {sub['text']}")
full_text = '\n'.join(full_text_lines)
total_duration = subtitles[-1]['end']
estimated_chapters = max(1, int(total_duration / target_chapter_duration))
print(f" 总时长: {get_video_duration_display(total_duration)}")
print(f" 字幕条数: {len(subtitles)}")
print(f" 目标章节时长: {target_chapter_duration} 秒 ({target_chapter_duration // 60} 分钟)")
print(f" 预估章节数: {estimated_chapters}")
return {
'subtitle_text': full_text,
'total_duration': total_duration,
'subtitle_count': len(subtitles),
'target_chapter_duration': target_chapter_duration,
'estimated_chapters': estimated_chapters,
'subtitles_raw': subtitles # 保留原始数据供后续使用
}
def save_analysis_data(data: Dict, output_path: str):
"""
保存分析数据到 JSON 文件
Args:
data: 分析数据
output_path: 输出文件路径
"""
output_path = Path(output_path)
# 创建输出目录
output_path.parent.mkdir(parents=True, exist_ok=True)
# 保存为 JSON
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(f"✅ 分析数据已保存: {output_path}")
def main():
"""命令行入口"""
if len(sys.argv) < 2:
print("Usage: python analyze_subtitles.py <vtt_file> [target_duration] [output_json]")
print("\nArguments:")
print(" vtt_file - VTT 字幕文件路径")
print(" target_duration - 目标章节时长(秒),默认 180")
print(" output_json - 输出 JSON 文件路径(可选)")
print("\nExample:")
print(" python analyze_subtitles.py video.en.vtt")
print(" python analyze_subtitles.py video.en.vtt 240")
print(" python analyze_subtitles.py video.en.vtt 240 analysis.json")
sys.exit(1)
vtt_file = sys.argv[1]
target_duration = int(sys.argv[2]) if len(sys.argv) > 2 else 180
output_json = sys.argv[3] if len(sys.argv) > 3 else None
try:
# 解析字幕
subtitles = parse_vtt(vtt_file)
if not subtitles:
print("❌ 未找到有效字幕")
sys.exit(1)
# 准备分析数据
analysis_data = prepare_analysis_data(subtitles, target_duration)
# 输出字幕文本(供 Claude 分析)
print("\n" + "="*60)
print("字幕文本(前 50 行预览):")
print("="*60)
lines = analysis_data['subtitle_text'].split('\n')
preview_lines = lines[:50]
print('\n'.join(preview_lines))
if len(lines) > 50:
print(f"\n... (还有 {len(lines) - 50} 行)")
# 保存到文件(如果指定)
if output_json:
save_analysis_data(analysis_data, output_json)
# 输出摘要信息
print("\n" + "="*60)
print("分析摘要:")
print("="*60)
print(json.dumps({
'total_duration': analysis_data['total_duration'],
'total_duration_display': get_video_duration_display(analysis_data['total_duration']),
'subtitle_count': analysis_data['subtitle_count'],
'target_chapter_duration': analysis_data['target_chapter_duration'],
'estimated_chapters': analysis_data['estimated_chapters']
}, indent=2, ensure_ascii=False))
print("\n💡 提示:现在可以使用 Claude AI 分析上述字幕文本,生成精细章节")
except Exception as e:
print(f"\n❌ 错误: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()