2025-08-20 11:39:34 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
|
|
|
|
|
VideoLingo 非图形界面版本
|
|
|
|
|
|
支持通过参数赋值的方式处理视频
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import shutil
|
2025-09-05 14:41:59 +08:00
|
|
|
|
import time
|
|
|
|
|
|
from datetime import datetime, timedelta
|
2025-08-20 11:39:34 +08:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
# 设置路径
|
|
|
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
os.environ['PATH'] += os.pathsep + current_dir
|
|
|
|
|
|
sys.path.append(current_dir)
|
|
|
|
|
|
|
|
|
|
|
|
# 导入核心模块
|
|
|
|
|
|
from core.utils.config_utils import load_key, update_key
|
|
|
|
|
|
from core.utils.onekeycleanup import cleanup
|
|
|
|
|
|
from core.utils.delete_retry_dubbing import delete_dubbing_files
|
|
|
|
|
|
from translations.translations import translate as t
|
|
|
|
|
|
from core import (
|
2025-09-05 14:41:59 +08:00
|
|
|
|
_2_asr, _3_split_semantic, _4_1_summarize, _4_2_translate,
|
|
|
|
|
|
_6_gen_sub, _7_sub_into_vid, _8_1_audio_task, _8_2_dub_chunks,
|
|
|
|
|
|
_9_refer_audio, _10_gen_audio,
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_11_merge_audio, _12_dub_to_vid
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VideoLingoProcessor:
|
|
|
|
|
|
"""VideoLingo 视频处理器"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, input_path, output_dir="output"):
|
|
|
|
|
|
"""
|
|
|
|
|
|
初始化处理器
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
input_path (str): 输入视频文件路径
|
|
|
|
|
|
output_dir (str): 输出目录路径,默认为 "output"
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.input_path = Path(input_path)
|
|
|
|
|
|
self.output_dir = Path(output_dir)
|
|
|
|
|
|
|
|
|
|
|
|
# 验证输入文件
|
|
|
|
|
|
if not self.input_path.exists():
|
|
|
|
|
|
raise FileNotFoundError(f"输入文件不存在: {self.input_path}")
|
|
|
|
|
|
|
|
|
|
|
|
# 创建输出目录
|
|
|
|
|
|
self.output_dir.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置输出文件路径
|
|
|
|
|
|
self.sub_video = self.output_dir / "output_sub.mp4"
|
|
|
|
|
|
self.dub_video = self.output_dir / "output_dub.mp4"
|
|
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 初始化计时器
|
|
|
|
|
|
self.start_time = None
|
|
|
|
|
|
self.step_times = {}
|
|
|
|
|
|
self.total_time = 0
|
|
|
|
|
|
|
2025-08-20 11:39:34 +08:00
|
|
|
|
print(f"📹 输入视频: {self.input_path}")
|
|
|
|
|
|
print(f"📁 输出目录: {self.output_dir}")
|
|
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
def format_time(seconds):
|
|
|
|
|
|
"""
|
|
|
|
|
|
格式化时间显示
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
seconds (float): 秒数
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
str: 格式化的时间字符串
|
|
|
|
|
|
"""
|
|
|
|
|
|
if seconds < 60:
|
|
|
|
|
|
return f"{seconds:.2f}秒"
|
|
|
|
|
|
elif seconds < 3600:
|
|
|
|
|
|
minutes = int(seconds // 60)
|
|
|
|
|
|
secs = seconds % 60
|
|
|
|
|
|
return f"{minutes}分{secs:.2f}秒"
|
|
|
|
|
|
else:
|
|
|
|
|
|
hours = int(seconds // 3600)
|
|
|
|
|
|
minutes = int((seconds % 3600) // 60)
|
|
|
|
|
|
secs = seconds % 60
|
|
|
|
|
|
return f"{hours}小时{minutes}分{secs:.2f}秒"
|
|
|
|
|
|
|
|
|
|
|
|
def start_timer(self, step_name):
|
|
|
|
|
|
"""
|
|
|
|
|
|
开始计时
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
step_name (str): 步骤名称
|
|
|
|
|
|
"""
|
|
|
|
|
|
current_time = time.time()
|
|
|
|
|
|
if self.start_time is None:
|
|
|
|
|
|
self.start_time = current_time
|
|
|
|
|
|
|
|
|
|
|
|
self.step_times[step_name] = {'start': current_time}
|
|
|
|
|
|
print(f"⏱️ 开始 {step_name} - {datetime.now().strftime('%H:%M:%S')}")
|
|
|
|
|
|
|
|
|
|
|
|
def end_timer(self, step_name):
|
|
|
|
|
|
"""
|
|
|
|
|
|
结束计时
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
step_name (str): 步骤名称
|
|
|
|
|
|
"""
|
|
|
|
|
|
if step_name in self.step_times:
|
|
|
|
|
|
end_time = time.time()
|
|
|
|
|
|
self.step_times[step_name]['end'] = end_time
|
|
|
|
|
|
duration = end_time - self.step_times[step_name]['start']
|
|
|
|
|
|
self.step_times[step_name]['duration'] = duration
|
|
|
|
|
|
print(f"✅ 完成 {step_name} - 耗时: {self.format_time(duration)}")
|
|
|
|
|
|
|
|
|
|
|
|
def print_timing_summary(self):
|
|
|
|
|
|
"""打印计时总结"""
|
|
|
|
|
|
if self.start_time is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.total_time = time.time() - self.start_time
|
|
|
|
|
|
|
|
|
|
|
|
print("\n" + "="*60)
|
|
|
|
|
|
print("📊 处理时间统计")
|
|
|
|
|
|
print("="*60)
|
|
|
|
|
|
|
|
|
|
|
|
for step_name, times in self.step_times.items():
|
|
|
|
|
|
if 'duration' in times:
|
|
|
|
|
|
print(f"🔹 {step_name:<20} : {self.format_time(times['duration'])}")
|
|
|
|
|
|
|
|
|
|
|
|
print("-"*60)
|
|
|
|
|
|
print(f"🕐 总处理时间: {self.format_time(self.total_time)}")
|
|
|
|
|
|
print(f"🕐 开始时间: {datetime.fromtimestamp(self.start_time).strftime('%Y-%m-%d %H:%M:%S')}")
|
|
|
|
|
|
print(f"🕐 结束时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
|
|
|
|
print("="*60)
|
|
|
|
|
|
|
2025-08-20 11:39:34 +08:00
|
|
|
|
def setup_video_file(self):
|
|
|
|
|
|
"""设置视频文件到指定位置"""
|
|
|
|
|
|
# 将输入视频复制到 "output" 目录
|
|
|
|
|
|
output_dir = Path("output")
|
|
|
|
|
|
output_dir.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
target_video = output_dir / self.input_path.name
|
|
|
|
|
|
if not target_video.exists():
|
|
|
|
|
|
print(f"📋 复制视频文件到: {target_video}")
|
|
|
|
|
|
shutil.copy2(self.input_path, target_video)
|
|
|
|
|
|
|
|
|
|
|
|
return target_video
|
|
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
def process_all(self):
|
|
|
|
|
|
"""处理完整的视频翻译和配音流程"""
|
|
|
|
|
|
print("\n🎬 开始VideoLingo重构版处理流程...")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
try:
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 设置视频文件
|
|
|
|
|
|
self.start_timer("视频文件设置")
|
|
|
|
|
|
self.setup_video_file()
|
|
|
|
|
|
self.end_timer("视频文件设置")
|
|
|
|
|
|
|
|
|
|
|
|
# 语音转录
|
|
|
|
|
|
self.start_timer("Whisper语音转录")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_2_asr.transcribe()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("Whisper语音转录")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 语义切割
|
|
|
|
|
|
self.start_timer("语义切割")
|
|
|
|
|
|
_3_split_semantic.split_semantic()
|
|
|
|
|
|
self.end_timer("语义切割")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 总结
|
|
|
|
|
|
self.start_timer("内容总结")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_4_1_summarize.get_summary()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("内容总结")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
# 检查是否需要暂停编辑术语
|
|
|
|
|
|
if load_key("pause_before_translate"):
|
2025-09-05 14:41:59 +08:00
|
|
|
|
print("⚠️ 暂停以便编辑术语。请前往 'output/log/terminology.json' 编辑术语表...")
|
|
|
|
|
|
input("按回车继续...")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 翻译
|
|
|
|
|
|
self.start_timer("文本翻译")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_4_2_translate.translate_all()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("文本翻译")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 音频任务生成
|
|
|
|
|
|
self.start_timer("音频任务生成")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_8_1_audio_task.gen_audio_task_main()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("音频任务生成")
|
|
|
|
|
|
|
|
|
|
|
|
# 生成配音片段
|
|
|
|
|
|
self.start_timer("配音片段生成")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_8_2_dub_chunks.gen_dub_chunks()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("配音片段生成")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 提取参考音频
|
|
|
|
|
|
self.start_timer("参考音频提取")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_9_refer_audio.extract_refer_audio_main()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("参考音频提取")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 生成音频
|
|
|
|
|
|
self.start_timer("音频生成")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_10_gen_audio.gen_audio()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("音频生成")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 合并音频
|
|
|
|
|
|
self.start_timer("音频合并")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_11_merge_audio.merge_full_audio()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("音频合并")
|
|
|
|
|
|
|
|
|
|
|
|
# 字幕对齐
|
|
|
|
|
|
self.start_timer("字幕时间对齐")
|
|
|
|
|
|
_6_gen_sub.align_timestamp_main()
|
|
|
|
|
|
self.end_timer("字幕时间对齐")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 字幕合并到视频
|
|
|
|
|
|
self.start_timer("字幕合并到视频")
|
|
|
|
|
|
_7_sub_into_vid.merge_subtitles_to_video()
|
|
|
|
|
|
self.end_timer("字幕合并到视频")
|
|
|
|
|
|
|
|
|
|
|
|
# 配音合并到视频
|
|
|
|
|
|
self.start_timer("配音合并到视频")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
_12_dub_to_vid.merge_video_audio()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("配音合并到视频")
|
|
|
|
|
|
|
|
|
|
|
|
# 打印计时总结
|
|
|
|
|
|
self.print_timing_summary()
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
print("\n🎉 处理完成! 🎉")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-09-05 14:41:59 +08:00
|
|
|
|
print(f"❌ 处理失败: {str(e)}")
|
|
|
|
|
|
# 即使失败也显示已完成步骤的计时信息
|
|
|
|
|
|
self.print_timing_summary()
|
2025-08-20 11:39:34 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
def cleanup_files(self):
|
|
|
|
|
|
"""清理临时文件"""
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.start_timer("清理临时文件")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
cleanup()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("清理临时文件")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
def delete_dubbing_files(self):
|
|
|
|
|
|
"""删除配音相关文件"""
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.start_timer("删除配音文件")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
delete_dubbing_files()
|
2025-09-05 14:41:59 +08:00
|
|
|
|
self.end_timer("删除配音文件")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
"""主函数 - 示例用法"""
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 配置参数 ====================
|
|
|
|
|
|
# 请在这里修改您的参数
|
|
|
|
|
|
|
|
|
|
|
|
# 输入视频路径(必需)
|
2025-09-05 14:41:59 +08:00
|
|
|
|
INPUT_VIDEO_PATH = "composed_video_20250729_183303_no_sub.mp4"
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
# 输出目录(可选,默认为 "output")
|
|
|
|
|
|
OUTPUT_DIR = "output"
|
|
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 重构版本已统一处理流程,不再需要分离字幕和配音处理
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
# ==================== 可选配置覆盖 ====================
|
|
|
|
|
|
# 您可以在这里覆盖 config.yaml 中的设置
|
|
|
|
|
|
|
|
|
|
|
|
# 配置中文转英文翻译
|
|
|
|
|
|
update_key("whisper.language", "zh") # 原始语言为中文
|
|
|
|
|
|
update_key("target_language", "English") # 目标语言为英文
|
|
|
|
|
|
update_key("tts_method", "edge_tts") # 使用 Edge TTS
|
|
|
|
|
|
update_key("burn_subtitles", True) # 烧录字幕到视频
|
2025-08-21 18:09:57 +08:00
|
|
|
|
update_key("reflect_translate", True) # 启用 Expressiveness 阶段进行翻译润色
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
# ===================================================
|
|
|
|
|
|
|
2025-09-05 14:41:59 +08:00
|
|
|
|
# 记录整体开始时间
|
|
|
|
|
|
overall_start_time = time.time()
|
|
|
|
|
|
print(f"🚀 VideoLingo 处理开始 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
|
|
|
|
|
2025-08-20 11:39:34 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 创建处理器实例
|
|
|
|
|
|
processor = VideoLingoProcessor(
|
|
|
|
|
|
input_path=INPUT_VIDEO_PATH,
|
|
|
|
|
|
output_dir=OUTPUT_DIR
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 执行处理
|
2025-09-05 14:41:59 +08:00
|
|
|
|
success = processor.process_all()
|
|
|
|
|
|
|
|
|
|
|
|
# 计算总体运行时间
|
|
|
|
|
|
overall_end_time = time.time()
|
|
|
|
|
|
overall_duration = overall_end_time - overall_start_time
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
if success:
|
2025-09-05 14:41:59 +08:00
|
|
|
|
print(f"\n🎉 VideoLingo 处理成功完成!")
|
|
|
|
|
|
print(f"🎯 总体运行时间: {VideoLingoProcessor.format_time(overall_duration)}")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
else:
|
2025-09-05 14:41:59 +08:00
|
|
|
|
print(f"\n❌ 处理过程中出现错误")
|
|
|
|
|
|
print(f"⏱️ 运行时间: {VideoLingoProcessor.format_time(overall_duration)}")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
except FileNotFoundError as e:
|
2025-09-05 14:41:59 +08:00
|
|
|
|
overall_duration = time.time() - overall_start_time
|
2025-08-20 11:39:34 +08:00
|
|
|
|
print(f"❌ 文件错误: {e}")
|
|
|
|
|
|
print("请检查输入视频路径是否正确")
|
2025-09-05 14:41:59 +08:00
|
|
|
|
print(f"⏱️ 运行时间: {VideoLingoProcessor.format_time(overall_duration)}")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
except Exception as e:
|
2025-09-05 14:41:59 +08:00
|
|
|
|
overall_duration = time.time() - overall_start_time
|
2025-08-20 11:39:34 +08:00
|
|
|
|
print(f"❌ 处理失败: {e}")
|
2025-09-05 14:41:59 +08:00
|
|
|
|
print(f"⏱️ 运行时间: {VideoLingoProcessor.format_time(overall_duration)}")
|
2025-08-20 11:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|