mirror of
https://github.com/linyqh/NarratoAI.git
synced 2026-01-03 11:48:12 +00:00
refactor(task): 重构任务处理流程
- 修改音频合并文件名 - 优化视频裁剪结果处理 - 更新脚本处理逻辑,支持字幕路径 - 调整 TTS 多重处理函数,移除未使用的参数
This commit is contained in:
parent
8b5ff0658b
commit
aaea6d913c
@ -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}")
|
||||
|
||||
|
||||
@ -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}")
|
||||
|
||||
@ -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 = []
|
||||
|
||||
@ -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']}")
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user