refactor(task): 重构任务处理流程

- 修改音频合并文件名
- 优化视频裁剪结果处理
- 更新脚本处理逻辑,支持字幕路径
- 调整 TTS 多重处理函数,移除未使用的参数
This commit is contained in:
linyq 2025-05-07 11:00:22 +08:00
parent 8b5ff0658b
commit aaea6d913c
5 changed files with 115 additions and 72 deletions

View File

@ -69,7 +69,7 @@ def merge_audio_files(task_id: str, total_duration: float, list_script: list):
continue
# 保存合并后的音频文件
output_audio_path = os.path.join(utils.task_dir(task_id), "final_audio.mp3")
output_audio_path = os.path.join(utils.task_dir(task_id), "merger_audio.mp3")
final_audio.export(output_audio_path, format="mp3")
logger.info(f"合并后的音频文件已保存: {output_audio_path}")

View File

@ -144,6 +144,7 @@ def clip_video(
result = {}
for item in tts_result:
_id = item["_id"]
timestamp = item["timestamp"]
start_time, _ = parse_timestamp(timestamp)
@ -180,7 +181,7 @@ def clip_video(
check=True
)
result[timestamp] = output_path
result[_id] = output_path
except subprocess.CalledProcessError as e:
logger.error(f"裁剪视频片段失败: {timestamp}")

View File

@ -217,7 +217,6 @@ def start_subclip(task_id: str, params: VideoClipParams, subclip_path_videos: di
voice_name=params.voice_name,
voice_rate=params.voice_rate,
voice_pitch=params.voice_pitch,
force_regenerate=True
)
sm.state.update_task(task_id, state=const.TASK_STATE_PROCESSING, progress=20)
@ -245,12 +244,13 @@ def start_subclip(task_id: str, params: VideoClipParams, subclip_path_videos: di
3. 裁剪视频 - 将超出音频长度的视频进行裁剪
"""
logger.info("\n\n## 3. 裁剪视频")
clip_result = clip_video.clip_video(params.video_origin_path, tts_results)
subclip_path_videos.update(clip_result)
video_clip_result = clip_video.clip_video(params.video_origin_path, tts_results)
# 更新 list_script 中的时间戳
tts_clip_result = {tts_result['timestamp']: tts_result['audio_file'] for tts_result in tts_results}
list_script = update_script.update_script_timestamps(list_script, clip_result, tts_clip_result)
subclip_videos = [x for x in subclip_path_videos.values()]
tts_clip_result = {tts_result['_id']: tts_result['audio_file'] for tts_result in tts_results}
subclip_clip_result = {
tts_result['_id']: tts_result['subtitle_file'] for tts_result in tts_results
}
list_script = update_script.update_script_timestamps(list_script, video_clip_result, tts_clip_result, subclip_clip_result)
sm.state.update_task(task_id, state=const.TASK_STATE_PROCESSING, progress=60)
@ -268,14 +268,14 @@ def start_subclip(task_id: str, params: VideoClipParams, subclip_path_videos: di
merged_audio_path = audio_merger.merge_audio_files(
task_id=task_id,
total_duration=total_duration,
list_script=list_script # 传入完整脚本以便处理OST
list_script=list_script
)
logger.info(f"音频文件合并成功->{merged_audio_path}")
# 合并字幕文件
merged_subtitle_path = subtitle_merger.merge_subtitle_files(
subtitle_files=subtitle_files,
)
logger.info(f"字幕文件合并成功->{merged_subtitle_path}")
# # 合并字幕文件
# merged_subtitle_path = subtitle_merger.merge_subtitle_files(
# subtitle_files=subtitle_files,
# )
# logger.info(f"字幕文件合并成功->{merged_subtitle_path}")
except Exception as e:
logger.error(f"合并音频文件失败: {str(e)}")
merged_audio_path = ""
@ -285,7 +285,7 @@ def start_subclip(task_id: str, params: VideoClipParams, subclip_path_videos: di
return
"""
6. 合并视频
5. 合并视频
"""
final_video_paths = []
combined_video_paths = []

View File

@ -10,7 +10,7 @@
import re
import os
from typing import Dict, List, Any, Tuple
from typing import Dict, List, Any, Tuple, Union
def extract_timestamp_from_video_path(video_path: str) -> str:
@ -63,14 +63,21 @@ def calculate_duration(timestamp: str) -> float:
return 0.0
def update_script_timestamps(script_list: List[Dict[str, Any]], video_result: Dict[str, str], audio_result: Dict[str, str] = None) -> List[Dict[str, Any]]:
def update_script_timestamps(
script_list: List[Dict[str, Any]],
video_result: Dict[Union[str, int], str],
audio_result: Dict[Union[str, int], str] = None,
subtitle_result: Dict[Union[str, int], str] = None
) -> List[Dict[str, Any]]:
"""
根据 video_result 中的视频文件更新 script_list 中的时间戳添加持续时间并根据 audio_result 添加音频路径
根据 video_result 中的视频文件更新 script_list 中的时间戳添加持续时间
并根据 audio_result 添加音频路径根据 subtitle_result 添加字幕路径
Args:
script_list: 原始脚本列表
video_result: 视频结果字典键为原时间戳值为视频文件路径
audio_result: 音频结果字典键为原时间戳值为音频文件路径
video_result: 视频结果字典键为原时间戳或_id值为视频文件路径
audio_result: 音频结果字典键为原时间戳或_id值为音频文件路径
subtitle_result: 字幕结果字典键为原时间戳或_id值为字幕文件路径
Returns:
更新后的脚本列表
@ -78,12 +85,12 @@ def update_script_timestamps(script_list: List[Dict[str, Any]], video_result: Di
# 创建副本,避免修改原始数据
updated_script = []
# 建立原始时间戳到视频路径和新时间戳的映射
timestamp_mapping = {}
for orig_timestamp, video_path in video_result.items():
# 建立ID和时间戳到视频路径和新时间戳的映射
id_timestamp_mapping = {}
for key, video_path in video_result.items():
new_timestamp = extract_timestamp_from_video_path(video_path)
if new_timestamp:
timestamp_mapping[orig_timestamp] = {
id_timestamp_mapping[key] = {
'new_timestamp': new_timestamp,
'video_path': video_path
}
@ -91,19 +98,35 @@ def update_script_timestamps(script_list: List[Dict[str, Any]], video_result: Di
# 更新脚本中的时间戳
for item in script_list:
item_copy = item.copy()
item_id = item_copy.get('_id')
orig_timestamp = item_copy.get('timestamp', '')
# 初始化音频路径为空字符串
# 初始化音频和字幕路径为空字符串
item_copy['audio'] = ""
item_copy['subtitle'] = ""
# 如果提供了音频结果字典且时间戳存在于音频结果中,直接使用对应的音频路径
if audio_result and orig_timestamp in audio_result:
item_copy['audio'] = audio_result[orig_timestamp]
# 如果提供了音频结果字典且ID存在于音频结果中直接使用对应的音频路径
if audio_result:
if item_id and item_id in audio_result:
item_copy['audio'] = audio_result[item_id]
elif orig_timestamp in audio_result:
item_copy['audio'] = audio_result[orig_timestamp]
if orig_timestamp in timestamp_mapping:
# 更新时间戳
item_copy['timestamp'] = timestamp_mapping[orig_timestamp]['new_timestamp']
# 计算持续时间
# 如果提供了字幕结果字典且ID存在于字幕结果中直接使用对应的字幕路径
if subtitle_result:
if item_id and item_id in subtitle_result:
item_copy['subtitle'] = subtitle_result[item_id]
elif orig_timestamp in subtitle_result:
item_copy['subtitle'] = subtitle_result[orig_timestamp]
# 更新时间戳和计算持续时间
if item_id and item_id in id_timestamp_mapping:
# 根据ID找到对应的新时间戳
item_copy['timestamp'] = id_timestamp_mapping[item_id]['new_timestamp']
item_copy['duration'] = calculate_duration(item_copy['timestamp'])
elif orig_timestamp in id_timestamp_mapping:
# 根据原始时间戳找到对应的新时间戳
item_copy['timestamp'] = id_timestamp_mapping[orig_timestamp]['new_timestamp']
item_copy['duration'] = calculate_duration(item_copy['timestamp'])
elif orig_timestamp:
# 对于未更新的时间戳,也计算并添加持续时间
@ -116,36 +139,66 @@ def update_script_timestamps(script_list: List[Dict[str, Any]], video_result: Di
if __name__ == '__main__':
list_script = [
{'picture': '【解说】好的,各位,欢迎回到我的频道!《庆余年 2》刚开播就给了我们一个王炸范闲在北齐""了?这怎么可能!',
'timestamp': '00:00:00-00:01:15',
'narration': '好的各位,欢迎回到我的频道!《庆余年 2》刚开播就给了我们一个王炸范闲在北齐""了?这怎么可能!上集片尾那个巨大的悬念,这一集就立刻揭晓了!范闲假死归来,他面临的第一个,也是最大的难关,就是如何面对他最敬爱的,同时也是最可怕的那个人——庆帝!',
'OST': 0},
{'picture': '【解说】上一集我们看到,范闲在北齐遭遇了惊天变故,生死不明!', 'timestamp': '00:01:15-00:04:40',
'narration': '但我们都知道,他绝不可能就这么轻易退场!第二集一开场,范闲就已经秘密回到了京都。他的生死传闻,可不像我们想象中那样只是小范围流传,而是…',
'OST': 0}, {'picture': '画面切到王启年小心翼翼地向范闲汇报。', 'timestamp': '00:04:41-00:04:58',
'narration': '我发现大人的死讯不光是在民间,在官场上也它传开了,所以呢,所以啊,可不是什么好事,将来您跟陛下怎么交代,这可是欺君之罪',
'OST': 1},
{'picture': '【解说】"欺君之罪"!在封建王朝,这可是抄家灭族的大罪!搁一般人,肯定脚底抹油溜之大吉了。',
'timestamp': '00:04:58-00:05:45',
'narration': '"欺君之罪"!在封建王朝,这可是抄家灭族的大罪!搁一般人,肯定脚底抹油溜之大吉了。但范闲是谁啊?他偏要反其道而行之!他竟然决定,直接去见庆帝!冒着天大的风险,用"假死"这个事实去赌庆帝的态度!',
'OST': 0}, {'picture': '【解说】但想见庆帝,哪有那么容易?范闲艺高人胆大,竟然选择了最激进的方式——闯宫!',
'timestamp': '00:05:45-00:06:00',
'narration': '但想见庆帝,哪有那么容易?范闲艺高人胆大,竟然选择了最激进的方式——闯宫!', 'OST': 0},
{'picture': '画面切换到范闲蒙面闯入皇宫,被侍卫包围的场景。', 'timestamp': '00:06:00-00:06:03',
'narration': '抓刺客', 'OST': 1}]
{
'picture': '【解说】好的,各位,欢迎回到我的频道!《庆余年 2》刚开播就给了我们一个王炸范闲在北齐""了?这怎么可能!',
'timestamp': '00:00:00-00:01:15',
'narration': '好的各位,欢迎回到我的频道!《庆余年 2》刚开播就给了我们一个王炸范闲在北齐""了?这怎么可能!上集片尾那个巨大的悬念,这一集就立刻揭晓了!范闲假死归来,他面临的第一个,也是最大的难关,就是如何面对他最敬爱的,同时也是最可怕的那个人——庆帝!',
'OST': 0,
'_id': 1
},
{
'picture': '【解说】上一集我们看到,范闲在北齐遭遇了惊天变故,生死不明!',
'timestamp': '00:01:15-00:04:40',
'narration': '但我们都知道,他绝不可能就这么轻易退场!第二集一开场,范闲就已经秘密回到了京都。他的生死传闻,可不像我们想象中那样只是小范围流传,而是…',
'OST': 0,
'_id': 2
},
{
'picture': '画面切到王启年小心翼翼地向范闲汇报。',
'timestamp': '00:04:41-00:04:58',
'narration': '我发现大人的死讯不光是在民间,在官场上也它传开了,所以呢,所以啊,可不是什么好事,将来您跟陛下怎么交代,这可是欺君之罪',
'OST': 1,
'_id': 3
},
{
'picture': '【解说】"欺君之罪"!在封建王朝,这可是抄家灭族的大罪!搁一般人,肯定脚底抹油溜之大吉了。',
'timestamp': '00:04:58-00:05:45',
'narration': '"欺君之罪"!在封建王朝,这可是抄家灭族的大罪!搁一般人,肯定脚底抹油溜之大吉了。但范闲是谁啊?他偏要反其道而行之!他竟然决定,直接去见庆帝!冒着天大的风险,用"假死"这个事实去赌庆帝的态度!',
'OST': 0,
'_id': 4
},
{
'picture': '【解说】但想见庆帝,哪有那么容易?范闲艺高人胆大,竟然选择了最激进的方式——闯宫!',
'timestamp': '00:05:45-00:06:00',
'narration': '但想见庆帝,哪有那么容易?范闲艺高人胆大,竟然选择了最激进的方式——闯宫!',
'OST': 0,
'_id': 5
},
{
'picture': '画面切换到范闲蒙面闯入皇宫,被侍卫包围的场景。',
'timestamp': '00:06:00-00:06:03',
'narration': '抓刺客',
'OST': 1,
'_id': 6
}]
video_res = {
'00:00:00-00:01:15': '/Users/apple/Desktop/home/NarratoAI/storage/temp/clip_video/0ac14d474144b54d614c26a5c87cffe7/vid-00-00-00-00-00-26.mp4',
'00:01:15-00:04:40': '/Users/apple/Desktop/home/NarratoAI/storage/temp/clip_video/0ac14d474144b54d614c26a5c87cffe7/vid-00-01-15-00-01-29.mp4',
'00:04:58-00:05:45': '/Users/apple/Desktop/home/NarratoAI/storage/temp/clip_video/0ac14d474144b54d614c26a5c87cffe7/vid-00-04-58-00-05-20.mp4',
'00:05:45-00:06:00': '/Users/apple/Desktop/home/NarratoAI/storage/temp/clip_video/0ac14d474144b54d614c26a5c87cffe7/vid-00-05-45-00-05-53.mp4'}
1: '/Users/apple/Desktop/home/NarratoAI/storage/temp/clip_video/2c0c2ae91b4f58596634a0b2f64d3eb0/vid-00-00-00-00-00-26.mp4',
2: '/Users/apple/Desktop/home/NarratoAI/storage/temp/clip_video/2c0c2ae91b4f58596634a0b2f64d3eb0/vid-00-01-15-00-01-29.mp4',
4: '/Users/apple/Desktop/home/NarratoAI/storage/temp/clip_video/2c0c2ae91b4f58596634a0b2f64d3eb0/vid-00-04-58-00-05-20.mp4',
5: '/Users/apple/Desktop/home/NarratoAI/storage/temp/clip_video/2c0c2ae91b4f58596634a0b2f64d3eb0/vid-00-05-45-00-05-53.mp4'}
audio_res = {
'00:00:00-00:01:15': '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/audio_00_00_00-00_01_15.mp3',
'00:01:15-00:04:40': '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/audio_00_01_15-00_04_40.mp3',
'00:04:58-00:05:45': '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/audio_00_04_58-00_05_45.mp3',
'00:05:45-00:06:00': '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/audio_00_05_45-00_06_00.mp3'}
1: '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/audio_00_00_00-00_01_15.mp3',
2: '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/audio_00_01_15-00_04_40.mp3',
4: '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/audio_00_04_58-00_05_45.mp3',
5: '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/audio_00_05_45-00_06_00.mp3'}
sub_res = {
1: '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/subtitle_00_00_00-00_01_15.srt',
2: '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/subtitle_00_01_15-00_04_40.srt',
4: '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/subtitle_00_04_58-00_05_45.srt',
5: '/Users/apple/Desktop/home/NarratoAI/storage/tasks/qyn2-2-demo/subtitle_00_05_45-00_06_00.srt'}
# 更新并打印结果
updated_list_script = update_script_timestamps(list_script, video_res, audio_res)
updated_list_script = update_script_timestamps(list_script, video_res, audio_res, sub_res)
for item in updated_list_script:
print(
f"Picture: {item['picture'][:20]}... | Timestamp: {item['timestamp']} | Duration: {item['duration']} 秒 | Audio: {item['audio']}")
f"ID: {item['_id']} | Picture: {item['picture'][:20]}... | Timestamp: {item['timestamp']} | Duration: {item['duration']} 秒 | Audio: {item['audio']} | Subtitle: {item['subtitle']}")

View File

@ -1411,7 +1411,7 @@ def get_audio_duration(sub_maker: submaker.SubMaker):
return sub_maker.offset[-1][1] / 10000000
def tts_multiple(task_id: str, list_script: list, voice_name: str, voice_rate: float, voice_pitch: float, force_regenerate: bool = True):
def tts_multiple(task_id: str, list_script: list, voice_name: str, voice_rate: float, voice_pitch: float):
"""
根据JSON文件中的多段文本进行TTS转换
@ -1419,7 +1419,6 @@ def tts_multiple(task_id: str, list_script: list, voice_name: str, voice_rate: f
:param list_script: 脚本列表
:param voice_name: 语音名称
:param voice_rate: 语音速率
:param force_regenerate: 是否强制重新生成已存在的音频文件
:return: 生成的音频文件列表
"""
voice_name = parse_voice_name(voice_name)
@ -1427,22 +1426,11 @@ def tts_multiple(task_id: str, list_script: list, voice_name: str, voice_rate: f
tts_results = []
for item in list_script:
tts_item = {
"audio_file": "",
"subtitle_file": "",
"duration": 0,
}
if item['OST'] != 1:
# 将时间戳中的冒号替换为下划线
timestamp = item['timestamp'].replace(':', '_')
audio_file = os.path.join(output_dir, f"audio_{timestamp}.mp3")
subtitle_file = os.path.join(output_dir, f"subtitle_{timestamp}.srt")
# # 检查文件是否已存在,如存在且不强制重新生成,则跳过
# if os.path.exists(audio_file) and not force_regenerate:
# logger.info(f"音频文件已存在,跳过生成: {audio_file}")
# tts_item["audio_file"] = audio_file
# continue
text = item['narration']
@ -1464,6 +1452,7 @@ def tts_multiple(task_id: str, list_script: list, voice_name: str, voice_rate: f
_, duration = create_subtitle(sub_maker=sub_maker, text=text, subtitle_file=subtitle_file)
tts_results.append({
"_id": item['_id'],
"timestamp": item['timestamp'],
"audio_file": audio_file,
"subtitle_file": subtitle_file,