From a675e35f1da555b60456602cb9bb742828a38eb3 Mon Sep 17 00:00:00 2001 From: linyqh Date: Fri, 20 Sep 2024 00:42:33 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=88=90=E5=8A=9F=EF=BC=8C?= =?UTF-8?q?=E4=BD=86=E8=84=9A=E6=9C=AC=E9=97=AE=E9=A2=98=E8=BF=98=E5=BE=88?= =?UTF-8?q?=E5=A4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/llm.py | 28 ++++----- app/services/task.py | 13 +++-- app/services/video.py | 129 ++++++++++++++++++++++++++++-------------- app/services/voice.py | 4 +- app/utils/utils.py | 41 +++++++++++++- webui/Main.py | 2 +- 6 files changed, 149 insertions(+), 68 deletions(-) diff --git a/app/services/llm.py b/app/services/llm.py index c033ab5..25c6557 100644 --- a/app/services/llm.py +++ b/app/services/llm.py @@ -451,19 +451,19 @@ def gemini_video2json(video_origin_name: str, video_origin_path: str, video_plot """ % (language, video_plot) logger.debug(f"视频名称: {video_origin_name}") - try: - gemini_video_file = gemini.upload_file(video_origin_path) - logger.debug(f"上传视频至 Google cloud 成功: {gemini_video_file.name}") - while gemini_video_file.state.name == "PROCESSING": - import time - time.sleep(1) - gemini_video_file = gemini.get_file(gemini_video_file.name) - logger.debug(f"视频当前状态(ACTIVE才可用): {gemini_video_file.state.name}") - if gemini_video_file.state.name == "FAILED": - raise ValueError(gemini_video_file.state.name) - except Exception as err: - logger.error(f"上传视频至 Google cloud 失败, 请检查 VPN 配置和 APIKey 是否正确 \n{traceback.format_exc()}") - raise TimeoutError(f"上传视频至 Google cloud 失败, 请检查 VPN 配置和 APIKey 是否正确; {err}") + # try: + gemini_video_file = gemini.upload_file(video_origin_path) + logger.debug(f"上传视频至 Google cloud 成功: {gemini_video_file.name}") + while gemini_video_file.state.name == "PROCESSING": + import time + time.sleep(1) + gemini_video_file = gemini.get_file(gemini_video_file.name) + logger.debug(f"视频当前状态(ACTIVE才可用): {gemini_video_file.state.name}") + if gemini_video_file.state.name == "FAILED": + raise ValueError(gemini_video_file.state.name) + # except Exception as err: + # logger.error(f"上传视频至 Google cloud 失败, 请检查 VPN 配置和 APIKey 是否正确 \n{traceback.format_exc()}") + # raise TimeoutError(f"上传视频至 Google cloud 失败, 请检查 VPN 配置和 APIKey 是否正确; {err}") streams = model.generate_content([prompt, gemini_video_file], stream=True) response = [] @@ -490,7 +490,7 @@ if __name__ == "__main__": # sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # video_subject = "卖菜大妈竟是皇嫂" - video_path = "/NarratoAI/resource/videos/demoyasuo.mp4" + video_path = "../../resource/videos/demoyasuo.mp4" video_plot = ''' ''' language = "zh-CN" diff --git a/app/services/task.py b/app/services/task.py index e58f4b4..c768e6a 100644 --- a/app/services/task.py +++ b/app/services/task.py @@ -440,12 +440,13 @@ def start_subclip(task_id, params: VideoClipParams, subclip_path_videos): logger.info(f"\n\n## 6. 最后一步: {index} => {final_video_path}") # 把所有东西合到在一起 - video.generate_video(video_path=combined_video_path, - audio_path=audio_file, - subtitle_path=subtitle_path, - output_file=final_video_path, - params=params, - ) + video.generate_video_v2( + video_path=combined_video_path, + audio_paths=audio_files, + subtitle_path=subtitle_path, + output_file=final_video_path, + params=params, + ) _progress += 50 / params.video_count / 2 sm.state.update_task(task_id, progress=_progress) diff --git a/app/services/video.py b/app/services/video.py index 9924923..dd9907f 100644 --- a/app/services/video.py +++ b/app/services/video.py @@ -1,4 +1,5 @@ import re +import os import glob import random from typing import List @@ -369,10 +370,17 @@ def generate_video_v2( return _clip video_clip = VideoFileClip(video_path) + original_audio = video_clip.audio # 保存原始视频的音轨 + video_duration = video_clip.duration # 处理多个音频文件 audio_clips = [] for audio_path in audio_paths: + # 确保每个音频文件路径是正确的 + if not os.path.exists(audio_path): + logger.warning(f"音频文件不存在: {audio_path}") + continue + # 从文件名中提取时间信息 match = re.search(r'audio_(\d{2}-\d{2}-\d{2}-\d{2})\.mp3', os.path.basename(audio_path)) if match: @@ -382,28 +390,53 @@ def generate_video_v2( end_time = sum(int(x) * 60 ** i for i, x in enumerate(reversed(end))) audio_clip = AudioFileClip(audio_path).volumex(params.voice_volume) - audio_clip = audio_clip.set_start(start_time).set_end(end_time) + + # 确保结束时间不超过音频实际长度 + actual_end_time = min(end_time - start_time, audio_clip.duration) + + audio_clip = audio_clip.subclip(0, actual_end_time) + audio_clip = audio_clip.set_start(start_time).set_end(start_time + actual_end_time) audio_clips.append(audio_clip) else: logger.warning(f"无法从文件名解析时间信息: {audio_path}") - # 合并所有音频剪辑 + # 合并所有音频剪辑,包括原始音轨 if audio_clips: + audio_clips.insert(0, original_audio) # 将原始音轨添加到音频剪辑列表的开头 audio_clip = CompositeAudioClip(audio_clips) else: - logger.warning("没有有效的音频文件") - audio_clip = AudioClip(lambda t: 0, duration=video_clip.duration) + logger.warning("没有有效的音频文件,使用原始音轨") + audio_clip = original_audio - # 字幕处理部分保持不变 + # 字幕处理部分 if subtitle_path and os.path.exists(subtitle_path): sub = SubtitlesClip(subtitles=subtitle_path, encoding="utf-8") text_clips = [] + for item in sub.subtitles: clip = create_text_clip(subtitle_item=item) + + # 确保字幕的开始时间不早于视频开始 + start_time = max(clip.start, 0) + + # 如果字幕的开始时间晚于视频结束时间,则跳过此字幕 + if start_time >= video_duration: + continue + + # 调整字幕的结束时间,但不要超过视频长度 + end_time = min(clip.end, video_duration) + + # 调整字幕的时间范围 + clip = clip.set_start(start_time).set_end(end_time) + text_clips.append(clip) + + logger.info(f"处理了 {len(text_clips)} 段字幕") + + # 创建一个新的视频剪辑,包含所有字幕 video_clip = CompositeVideoClip([video_clip, *text_clips]) - # 背景音乐处理部分保持不变 + # 背景音乐处理部分 bgm_file = get_bgm_file(bgm_type=params.bgm_type, bgm_file=params.bgm_file) if bgm_file: try: @@ -573,39 +606,43 @@ def combine_clip_videos(combined_video_path: str, if __name__ == "__main__": - combined_video_path = "../../storage/tasks/12312312/com123.mp4" - - video_paths = ['../../storage/cache_videos/vid-00_00-00_03.mp4', - '../../storage/cache_videos/vid-00_03-00_07.mp4', - '../../storage/cache_videos/vid-00_12-00_17.mp4', - '../../storage/cache_videos/vid-00_26-00_31.mp4'] - video_ost_list = [False, True, False, True] - list_script = [ - { - "picture": "夜晚,一个小孩在树林里奔跑,后面有人拿着火把在追赶", - "timestamp": "00:00-00:03", - "narration": "夜黑风高的树林,一个小孩在拼命奔跑,后面的人穷追不舍!", - "OST": False - }, - { - "picture": "追赶的人命令抓住小孩", - "timestamp": "00:03-00:07", - "narration": "原声播放1", - "OST": True - }, - { - "picture": "小孩躲在草丛里,黑衣人用脚踢了踢他", - "timestamp": "00:12-00:17", - "narration": "小孩脱下外套,跑进树林, 一路奔跑,直到第二天清晨", - "OST": False - }, - { - "picture": "小孩跑到车前,慌慌张张地对女人说有人要杀他", - "timestamp": "00:26-00:31", - "narration": "原声播放2", - "OST": True - } - ] + # combined_video_path = "../../storage/tasks/12312312/com123.mp4" + # + # video_paths = ['../../storage/cache_videos/vid-00_00-00_03.mp4', + # '../../storage/cache_videos/vid-00_03-00_07.mp4', + # '../../storage/cache_videos/vid-00_12-00_17.mp4', + # '../../storage/cache_videos/vid-00_26-00_31.mp4'] + # video_ost_list = [False, True, False, True] + # list_script = [ + # { + # "picture": "夜晚,一个小孩在树林里奔跑,后面有人拿着火把在追赶", + # "timestamp": "00:00-00:03", + # "narration": "夜黑风高的树林,一个小孩在拼命奔跑,后面的人穷追不舍!", + # "OST": False, + # "new_timestamp": "00:00-00:03" + # }, + # { + # "picture": "追赶的人命令抓住小孩", + # "timestamp": "00:03-00:07", + # "narration": "原声播放1", + # "OST": True, + # "new_timestamp": "00:03-00:07" + # }, + # { + # "picture": "小孩躲在草丛里,黑衣人用脚踢了踢他", + # "timestamp": "00:12-00:17", + # "narration": "小孩脱下外套,跑进树林, 一路奔跑,直到第二天清晨", + # "OST": False, + # "new_timestamp": "00:07-00:12" + # }, + # { + # "picture": "小孩跑到车前,慌慌张张地对女人说有人要杀他", + # "timestamp": "00:26-00:31", + # "narration": "原声播放2", + # "OST": True, + # "new_timestamp": "00:12-00:17" + # } + # ] # combine_clip_videos(combined_video_path=combined_video_path, video_paths=video_paths, video_ost_list=video_ost_list, list_script=list_script) cfg = VideoClipParams() @@ -633,14 +670,18 @@ if __name__ == "__main__": # params=cfg # ) - video_path = "../../storage/tasks/12312312/com123.mp4" + video_path = "../../storage/tasks/7f5ae494-abce-43cf-8f4f-4be43320eafa/combined-1.mp4" - audio_paths = ['../../storage/tasks/12312312/audio_00-00-00-03.mp3', - '../../storage/tasks/12312312/audio_00-12-00-17.mp3'] + audio_paths = ['../../storage/tasks/7f5ae494-abce-43cf-8f4f-4be43320eafa/audio_00-00-00-07.mp3', + '../../storage/tasks/7f5ae494-abce-43cf-8f4f-4be43320eafa/audio_00-14-00-17.mp3', + '../../storage/tasks/7f5ae494-abce-43cf-8f4f-4be43320eafa/audio_00-17-00-22.mp3', + '../../storage/tasks/7f5ae494-abce-43cf-8f4f-4be43320eafa/audio_00-34-00-45.mp3', + '../../storage/tasks/7f5ae494-abce-43cf-8f4f-4be43320eafa/audio_00-59-01-09.mp3', + ] - subtitle_path = "../../storage/tasks/12312312/subtitle_multiple.srt" + subtitle_path = "../../storage/tasks/7f5ae494-abce-43cf-8f4f-4be43320eafa\subtitle.srt" - output_file = "../../storage/tasks/12312312/out123.mp4" + output_file = "../../storage/tasks/7f5ae494-abce-43cf-8f4f-4be43320eafa/final-123.mp4" generate_video_v2(video_path=video_path, audio_paths=audio_paths, diff --git a/app/services/voice.py b/app/services/voice.py index 20180ba..785f3f1 100644 --- a/app/services/voice.py +++ b/app/services/voice.py @@ -1213,7 +1213,7 @@ def create_subtitle_from_multiple(text: str, sub_maker_list: List[SubMaker], lis if script_item['OST']: continue - start_time, end_time = script_item['timestamp'].split('-') + start_time, end_time = script_item['new_timestamp'].split('-') if sub_maker_index >= len(sub_maker_list): logger.error(f"Sub maker list index out of range: {sub_maker_index}") break @@ -1317,7 +1317,7 @@ def tts_multiple(task_id: str, list_script: list, voice_name: str, voice_rate: f for item in list_script: if not item['OST']: - timestamp = item['timestamp'].replace(':', '-') + timestamp = item['new_timestamp'].replace(':', '-') audio_file = os.path.join(output_dir, f"audio_{timestamp}.mp3") # 检查文件是否已存在,如存在且不强制重新生成,则跳过 diff --git a/app/utils/utils.py b/app/utils/utils.py index 95d796b..b5a91cb 100644 --- a/app/utils/utils.py +++ b/app/utils/utils.py @@ -7,7 +7,7 @@ from loguru import logger import json from uuid import uuid4 import urllib3 -from datetime import datetime +from datetime import datetime, timedelta from app.models import const @@ -326,3 +326,42 @@ def calculate_total_duration(scenes): total_seconds += duration.total_seconds() return total_seconds + + +def add_new_timestamps(scenes): + """ + 新增新视频的时间戳,并为"原生播放"的narration添加唯一标识符 + Args: + scenes: 场景列表 + + Returns: + 更新后的场景列表 + """ + current_time = timedelta() + updated_scenes = [] + + for scene in scenes: + new_scene = scene.copy() # 创建场景的副本,以保留原始数据 + start, end = new_scene['timestamp'].split('-') + start_time = datetime.strptime(start, '%M:%S') + end_time = datetime.strptime(end, '%M:%S') + duration = end_time - start_time + + new_start = current_time + current_time += duration + new_end = current_time + + # 将 timedelta 转换为分钟和秒 + new_start_str = f"{int(new_start.total_seconds() // 60):02d}:{int(new_start.total_seconds() % 60):02d}" + new_end_str = f"{int(new_end.total_seconds() // 60):02d}:{int(new_end.total_seconds() % 60):02d}" + + new_scene['new_timestamp'] = f"{new_start_str}-{new_end_str}" + + # 为"原生播放"的narration添加唯一标识符 + if new_scene.get('narration') == "原声播放": + unique_id = str(uuid4())[:8] # 使用UUID的前8个字符作为唯一标识符 + new_scene['narration'] = f"原声播放_{unique_id}" + + updated_scenes.append(new_scene) + + return updated_scenes diff --git a/webui/Main.py b/webui/Main.py index 10efc55..2db4569 100644 --- a/webui/Main.py +++ b/webui/Main.py @@ -395,7 +395,7 @@ with left_panel: # 去掉json的头尾标识 input_json = input_json.strip('```json').strip('```') try: - data = json.loads(input_json) + data = utils.add_new_timestamps(json.loads(input_json)) except Exception as err: raise ValueError( f"视频脚本格式错误,请检查脚本是否符合 JSON 格式;{err} \n\n{traceback.format_exc()}")