mirror of
https://github.com/linyqh/NarratoAI.git
synced 2025-12-13 12:12:50 +00:00
refactor(video): 可以剪辑短剧
- 添加多个视频处理相关函数,提高代码可复用性 - 优化日志输出,增加中文注释,提高代码可读性 -调整视频处理流程,提升效率和准确性 - 修复部分函数的参数类型和返回值类型
This commit is contained in:
parent
bd879079c3
commit
1a332c72bb
@ -253,7 +253,7 @@ def start_subclip(task_id: str, params: VideoClipParams, subclip_path_videos: di
|
|||||||
segment for segment in list_script
|
segment for segment in list_script
|
||||||
if segment['OST'] in [0, 2]
|
if segment['OST'] in [0, 2]
|
||||||
]
|
]
|
||||||
logger.debug(f"tts_segments: {tts_segments}")
|
# logger.debug(f"tts_segments: {tts_segments}")
|
||||||
if tts_segments:
|
if tts_segments:
|
||||||
audio_files, sub_maker_list = voice.tts_multiple(
|
audio_files, sub_maker_list = voice.tts_multiple(
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
@ -302,7 +302,7 @@ def start_subclip(task_id: str, params: VideoClipParams, subclip_path_videos: di
|
|||||||
|
|
||||||
logger.info("\n\n## 4. 裁剪视频")
|
logger.info("\n\n## 4. 裁剪视频")
|
||||||
subclip_videos = [x for x in subclip_path_videos.values()]
|
subclip_videos = [x for x in subclip_path_videos.values()]
|
||||||
logger.debug(f"\n\n## 裁剪后的视频文件列表: \n{subclip_videos}")
|
# logger.debug(f"\n\n## 裁剪后的视频文件列表: \n{subclip_videos}")
|
||||||
|
|
||||||
if not subclip_videos:
|
if not subclip_videos:
|
||||||
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
||||||
|
|||||||
@ -18,6 +18,15 @@ from app.utils import utils
|
|||||||
|
|
||||||
|
|
||||||
def get_bgm_file(bgm_type: str = "random", bgm_file: str = ""):
|
def get_bgm_file(bgm_type: str = "random", bgm_file: str = ""):
|
||||||
|
"""
|
||||||
|
获取背景音乐文件路径
|
||||||
|
Args:
|
||||||
|
bgm_type: 背景音乐类型,可选值: random(随机), ""(无背景音乐)
|
||||||
|
bgm_file: 指定的背景音乐文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 背景音乐文件路径
|
||||||
|
"""
|
||||||
if not bgm_type:
|
if not bgm_type:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@ -56,13 +65,27 @@ def combine_videos(
|
|||||||
max_clip_duration: int = 5,
|
max_clip_duration: int = 5,
|
||||||
threads: int = 2,
|
threads: int = 2,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
"""
|
||||||
|
合并多个视频片段
|
||||||
|
Args:
|
||||||
|
combined_video_path: 合并后的视频保存路径
|
||||||
|
video_paths: 待合并的视频路径列表
|
||||||
|
audio_file: 音频文件路径
|
||||||
|
video_aspect: 视频宽高比
|
||||||
|
video_concat_mode: 视频拼接模式(随机/顺序)
|
||||||
|
max_clip_duration: 每个片段的最大时长(秒)
|
||||||
|
threads: 处理线程数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 合并后的视频路径
|
||||||
|
"""
|
||||||
audio_clip = AudioFileClip(audio_file)
|
audio_clip = AudioFileClip(audio_file)
|
||||||
audio_duration = audio_clip.duration
|
audio_duration = audio_clip.duration
|
||||||
logger.info(f"max duration of audio: {audio_duration} seconds")
|
logger.info(f"音频时长: {audio_duration} 秒")
|
||||||
# Required duration of each clip
|
# 每个片段的所需时长
|
||||||
req_dur = audio_duration / len(video_paths)
|
req_dur = audio_duration / len(video_paths)
|
||||||
req_dur = max_clip_duration
|
req_dur = max_clip_duration
|
||||||
logger.info(f"each clip will be maximum {req_dur} seconds long")
|
logger.info(f"每个片段最大时长: {req_dur} 秒")
|
||||||
output_dir = os.path.dirname(combined_video_path)
|
output_dir = os.path.dirname(combined_video_path)
|
||||||
|
|
||||||
aspect = VideoAspect(video_aspect)
|
aspect = VideoAspect(video_aspect)
|
||||||
@ -81,22 +104,22 @@ def combine_videos(
|
|||||||
end_time = min(start_time + max_clip_duration, clip_duration)
|
end_time = min(start_time + max_clip_duration, clip_duration)
|
||||||
split_clip = clip.subclip(start_time, end_time)
|
split_clip = clip.subclip(start_time, end_time)
|
||||||
raw_clips.append(split_clip)
|
raw_clips.append(split_clip)
|
||||||
# logger.info(f"splitting from {start_time:.2f} to {end_time:.2f}, clip duration {clip_duration:.2f}, split_clip duration {split_clip.duration:.2f}")
|
# logger.info(f"从 {start_time:.2f} 到 {end_time:.2f}, 片段时长 {clip_duration:.2f}, 分割片段时长 {split_clip.duration:.2f}")
|
||||||
start_time = end_time
|
start_time = end_time
|
||||||
if video_concat_mode.value == VideoConcatMode.sequential.value:
|
if video_concat_mode.value == VideoConcatMode.sequential.value:
|
||||||
break
|
break
|
||||||
|
|
||||||
# random video_paths order
|
# 随机视频片段顺序
|
||||||
if video_concat_mode.value == VideoConcatMode.random.value:
|
if video_concat_mode.value == VideoConcatMode.random.value:
|
||||||
random.shuffle(raw_clips)
|
random.shuffle(raw_clips)
|
||||||
|
|
||||||
# Add downloaded clips over and over until the duration of the audio (max_duration) has been reached
|
# 添加下载的片段,直到音频时长(max_duration)达到
|
||||||
while video_duration < audio_duration:
|
while video_duration < audio_duration:
|
||||||
for clip in raw_clips:
|
for clip in raw_clips:
|
||||||
# Check if clip is longer than the remaining audio
|
# 检查片段是否比剩余音频时长长
|
||||||
if (audio_duration - video_duration) < clip.duration:
|
if (audio_duration - video_duration) < clip.duration:
|
||||||
clip = clip.subclip(0, (audio_duration - video_duration))
|
clip = clip.subclip(0, (audio_duration - video_duration))
|
||||||
# Only shorten clips if the calculated clip length (req_dur) is shorter than the actual clip to prevent still image
|
# 仅当计算的片段时长(req_dur)小于实际片段时长时,缩短片段
|
||||||
elif req_dur < clip.duration:
|
elif req_dur < clip.duration:
|
||||||
clip = clip.subclip(0, req_dur)
|
clip = clip.subclip(0, req_dur)
|
||||||
clip = clip.set_fps(30)
|
clip = clip.set_fps(30)
|
||||||
@ -134,7 +157,7 @@ def combine_videos(
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"resizing video to {video_width} x {video_height}, clip size: {clip_w} x {clip_h}"
|
f"调整视频尺寸为 {video_width} x {video_height}, 片段尺寸: {clip_w} x {clip_h}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if clip.duration > max_clip_duration:
|
if clip.duration > max_clip_duration:
|
||||||
@ -146,7 +169,7 @@ def combine_videos(
|
|||||||
video_clip = concatenate_videoclips(clips)
|
video_clip = concatenate_videoclips(clips)
|
||||||
video_clip = video_clip.set_fps(30)
|
video_clip = video_clip.set_fps(30)
|
||||||
logger.info("writing")
|
logger.info("writing")
|
||||||
# https://github.com/harry0703/NarratoAI/issues/111#issuecomment-2032354030
|
|
||||||
video_clip.write_videofile(
|
video_clip.write_videofile(
|
||||||
filename=combined_video_path,
|
filename=combined_video_path,
|
||||||
threads=threads,
|
threads=threads,
|
||||||
@ -161,6 +184,17 @@ def combine_videos(
|
|||||||
|
|
||||||
|
|
||||||
def wrap_text(text, max_width, font, fontsize=60):
|
def wrap_text(text, max_width, font, fontsize=60):
|
||||||
|
"""
|
||||||
|
文本自动换行处理
|
||||||
|
Args:
|
||||||
|
text: 待处理的文本
|
||||||
|
max_width: 最大宽度
|
||||||
|
font: 字体文件路径
|
||||||
|
fontsize: 字体大小
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (换行后的文本, 文本高度)
|
||||||
|
"""
|
||||||
# 创建字体对象
|
# 创建字体对象
|
||||||
font = ImageFont.truetype(font, fontsize)
|
font = ImageFont.truetype(font, fontsize)
|
||||||
|
|
||||||
@ -220,6 +254,14 @@ def wrap_text(text, max_width, font, fontsize=60):
|
|||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def manage_clip(clip):
|
def manage_clip(clip):
|
||||||
|
"""
|
||||||
|
视频片段资源管理器
|
||||||
|
Args:
|
||||||
|
clip: 视频片段对象
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
VideoFileClip: 视频片段对象
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
yield clip
|
yield clip
|
||||||
finally:
|
finally:
|
||||||
@ -232,6 +274,7 @@ def generate_video_v2(
|
|||||||
audio_path: str,
|
audio_path: str,
|
||||||
subtitle_path: str,
|
subtitle_path: str,
|
||||||
output_file: str,
|
output_file: str,
|
||||||
|
list_script: list,
|
||||||
params: Union[VideoParams, VideoClipParams],
|
params: Union[VideoParams, VideoClipParams],
|
||||||
progress_callback=None,
|
progress_callback=None,
|
||||||
):
|
):
|
||||||
@ -335,6 +378,7 @@ def generate_video_v2(
|
|||||||
update_progress("字幕处理完成")
|
update_progress("字幕处理完成")
|
||||||
|
|
||||||
# 合并音频和导出
|
# 合并音频和导出
|
||||||
|
logger.info("开始导出视频 (此步骤耗时较长请耐心等待)")
|
||||||
video_clip = video_clip.set_audio(final_audio)
|
video_clip = video_clip.set_audio(final_audio)
|
||||||
video_clip.write_videofile(
|
video_clip.write_videofile(
|
||||||
output_file,
|
output_file,
|
||||||
@ -356,7 +400,17 @@ def generate_video_v2(
|
|||||||
|
|
||||||
|
|
||||||
def process_audio_tracks(original_audio, new_audio, params, video_duration):
|
def process_audio_tracks(original_audio, new_audio, params, video_duration):
|
||||||
"""处理所有音轨"""
|
"""
|
||||||
|
处理所有音轨(原声、配音、背景音乐)
|
||||||
|
Args:
|
||||||
|
original_audio: 原始音频
|
||||||
|
new_audio: 新音频
|
||||||
|
params: 视频参数
|
||||||
|
video_duration: 视频时长
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CompositeAudioClip: 合成后的音频
|
||||||
|
"""
|
||||||
audio_tracks = []
|
audio_tracks = []
|
||||||
|
|
||||||
if original_audio is not None:
|
if original_audio is not None:
|
||||||
@ -379,7 +433,17 @@ def process_audio_tracks(original_audio, new_audio, params, video_duration):
|
|||||||
|
|
||||||
|
|
||||||
def process_subtitles(subtitle_path, video_clip, video_duration, create_text_clip):
|
def process_subtitles(subtitle_path, video_clip, video_duration, create_text_clip):
|
||||||
"""处理字幕"""
|
"""
|
||||||
|
处理字幕
|
||||||
|
Args:
|
||||||
|
subtitle_path: 字幕文件路径
|
||||||
|
video_clip: 视频片段
|
||||||
|
video_duration: 视频时长
|
||||||
|
create_text_clip: 创建文本片段的回调函数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CompositeVideoClip: 添加字幕后的视频
|
||||||
|
"""
|
||||||
if not (subtitle_path and os.path.exists(subtitle_path)):
|
if not (subtitle_path and os.path.exists(subtitle_path)):
|
||||||
return video_clip
|
return video_clip
|
||||||
|
|
||||||
@ -403,6 +467,15 @@ def process_subtitles(subtitle_path, video_clip, video_duration, create_text_cli
|
|||||||
|
|
||||||
|
|
||||||
def preprocess_video(materials: List[MaterialInfo], clip_duration=4):
|
def preprocess_video(materials: List[MaterialInfo], clip_duration=4):
|
||||||
|
"""
|
||||||
|
预处理视频素材
|
||||||
|
Args:
|
||||||
|
materials: 素材信息列表
|
||||||
|
clip_duration: 片段时长(秒)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[MaterialInfo]: 处理后的素材信息列表
|
||||||
|
"""
|
||||||
for material in materials:
|
for material in materials:
|
||||||
if not material.url:
|
if not material.url:
|
||||||
continue
|
continue
|
||||||
@ -430,12 +503,12 @@ def preprocess_video(materials: List[MaterialInfo], clip_duration=4):
|
|||||||
# 使用resize方法来添加缩放效果。这里使用了lambda函数来使得缩放效果随时间变化。
|
# 使用resize方法来添加缩放效果。这里使用了lambda函数来使得缩放效果随时间变化。
|
||||||
# 假设我们想要从原始大小逐渐放大到120%的大小。
|
# 假设我们想要从原始大小逐渐放大到120%的大小。
|
||||||
# t代表当前时间,clip.duration为视频总时长,这里是3秒。
|
# t代表当前时间,clip.duration为视频总时长,这里是3秒。
|
||||||
# 注意:1 表示100%的大小,所以1.2表示120%的大小
|
# 注意:1 表示100%的大小所以1.2表示120%的大小
|
||||||
zoom_clip = clip.resize(
|
zoom_clip = clip.resize(
|
||||||
lambda t: 1 + (clip_duration * 0.03) * (t / clip.duration)
|
lambda t: 1 + (clip_duration * 0.03) * (t / clip.duration)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 如果需要,可以创建一个包含缩放剪辑的复合视频剪辑
|
# 如果需要,可以创建一个包含缩放剪辑的复合频剪辑
|
||||||
# (这在您想要在视频中添加其他元素时非常有用)
|
# (这在您想要在视频中添加其他元素时非常有用)
|
||||||
final_clip = CompositeVideoClip([zoom_clip])
|
final_clip = CompositeVideoClip([zoom_clip])
|
||||||
|
|
||||||
@ -511,7 +584,7 @@ def combine_clip_videos(combined_video_path: str,
|
|||||||
video_clip = concatenate_videoclips(clips)
|
video_clip = concatenate_videoclips(clips)
|
||||||
video_clip = video_clip.set_fps(30)
|
video_clip = video_clip.set_fps(30)
|
||||||
|
|
||||||
logger.info("开始合并视频...")
|
logger.info("开始合并视频... (过程中出现 UserWarning: 不必理会)")
|
||||||
video_clip.write_videofile(
|
video_clip.write_videofile(
|
||||||
filename=combined_video_path,
|
filename=combined_video_path,
|
||||||
threads=threads,
|
threads=threads,
|
||||||
@ -521,7 +594,7 @@ def combine_clip_videos(combined_video_path: str,
|
|||||||
temp_audiofile=os.path.join(output_dir, "temp-audio.m4a")
|
temp_audiofile=os.path.join(output_dir, "temp-audio.m4a")
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
# 确保资源被正确<EFBFBD><EFBFBD><EFBFBD>放
|
# 确保资源被正确放
|
||||||
video_clip.close()
|
video_clip.close()
|
||||||
for clip in clips:
|
for clip in clips:
|
||||||
clip.close()
|
clip.close()
|
||||||
@ -531,7 +604,16 @@ def combine_clip_videos(combined_video_path: str,
|
|||||||
|
|
||||||
|
|
||||||
def resize_video_with_padding(clip, target_width: int, target_height: int):
|
def resize_video_with_padding(clip, target_width: int, target_height: int):
|
||||||
"""辅助函数:调整视频尺寸并添加黑边"""
|
"""
|
||||||
|
调整视频尺寸并添加黑边
|
||||||
|
Args:
|
||||||
|
clip: 视频片段
|
||||||
|
target_width: 目标宽度
|
||||||
|
target_height: 目标高度
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CompositeVideoClip: 调整尺寸后的视频
|
||||||
|
"""
|
||||||
clip_ratio = clip.w / clip.h
|
clip_ratio = clip.w / clip.h
|
||||||
target_ratio = target_width / target_height
|
target_ratio = target_width / target_height
|
||||||
|
|
||||||
@ -559,7 +641,18 @@ def resize_video_with_padding(clip, target_width: int, target_height: int):
|
|||||||
|
|
||||||
|
|
||||||
def validate_params(video_path, audio_path, output_file, params):
|
def validate_params(video_path, audio_path, output_file, params):
|
||||||
"""验证输入参数"""
|
"""
|
||||||
|
验证输入参数
|
||||||
|
Args:
|
||||||
|
video_path: 视频文件路径
|
||||||
|
audio_path: 音频文件路径
|
||||||
|
output_file: 输出文件路径
|
||||||
|
params: 视频参数
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: 文件不存在时抛出
|
||||||
|
ValueError: 参数无效时抛出
|
||||||
|
"""
|
||||||
if not os.path.exists(video_path):
|
if not os.path.exists(video_path):
|
||||||
raise FileNotFoundError(f"视频文件不存在: {video_path}")
|
raise FileNotFoundError(f"视频文件不存在: {video_path}")
|
||||||
|
|
||||||
@ -592,21 +685,21 @@ if __name__ == "__main__":
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"timestamp": "00:45-01:01",
|
"timestamp": "00:45-01:01",
|
||||||
"picture": "好的以下是视频画面的客观描述:\n\n视频显示了一个人在森林里挖掘。\n\n第一个镜头是地面特写,显示出松散的泥土、碎石和落叶。光线照在部分区域。\n\n第二个镜头中,一模糊不清的蹲一个树根旁挖掘,一个橄榄绿色的背包放在地上。树根缠绕着常春藤。\n\n第三个镜头显示该人在一个更开阔的区域挖掘,那里有一些树根,以及部分倒的树干。他起来像是在挖掘一个较大的坑。\n\n第四个镜头是特写镜头,显示该人用工具清理土坑的墙壁。\n\n第五个镜头是土坑内部的特写镜头,可以看到土质的纹理,有一些小树根和其它植被的残留物。",
|
"picture": "好的以下是视频画面的客观描述:\n\n视频显示了一个人在森林里挖掘。\n\n第一个镜头是地面特写,显示出松<EFBFBD><EFBFBD>的泥土、碎石和落叶。光线照在部分区域。\n\n第二个镜头中,一模糊不清的蹲一个树根旁挖掘,一个橄榄绿色的背包放在地上。树根缠绕着常春藤。\n\n第三个镜头显示该人在一个更开阔的区域挖掘,那里有一些树根,以及部分倒的树干。他起来像是在挖掘一个较大的坑。\n\n第四个镜头是特写镜头,显示该人用工具清理土坑的墙壁。\n\n第五个镜头是土坑内部的特写镜头,可以看到土质的纹理,有一些小树根和它植被的残留物。",
|
||||||
"narration": "现在,这位勇敢的挖掘者就像个“现代版的土豆农夫”,在森林里开辟新天地。的目标是什么?挖出一个宝藏还一块“树根披萨”?小心哦,别让树根追着你喊:“不要挖我,我也是有故事的!”",
|
"narration": "现在,这位勇敢的挖掘者就像个“现代版的土豆农夫”,在林里开辟新天地。的目标是什么?挖一个宝藏还块“树根披萨”?小心哦,别让树根追着你喊:“不要挖我,我也是有故事的!”",
|
||||||
"OST": 2,
|
"OST": 2,
|
||||||
"new_timestamp": "00:00:33,000-00:00:49,000"
|
"new_timestamp": "00:00:33,000-00:00:49,000"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"timestamp": "01:07-01:25",
|
"timestamp": "01:07-01:25",
|
||||||
"picture": "好,以下是视频画面的客观描述:\n\n画面1:特写镜头,显示出一丛带有水珠的深绿色灌木叶片。叶片呈椭圆形,边缘光滑。背景是树根和泥土。\n\n画面2:一个留着胡子的男人正在一个森林中土坑里挖掘。他穿着黑色T恤和卡其色裤子,跪在地上,用具挖掘泥土。周围环绕着树木、树根和灌木。一个倒下的树干横跨土坑上方。\n\n画面3:同一个男人坐在他刚才挖的坑的边缘,看着前方。他的表情似乎略带沉思。背景与画面2相同。\n\n画面4:一个广角镜头显示出他挖出的坑。这是一个不规则形状的土坑,在树木繁茂的斜坡上。土壤呈深棕色,可见树根。\n\n画面5:同一个男人跪在地上,用一把小斧头砍一根木头。他穿着与前几个画面相同的衣服。地面上覆盖着落叶。周围是树木和灌木。",
|
"picture": "好,以下是视频画面的客观描述:\n\n画面1:特写镜头,显示出一丛带有水珠的深绿色灌木叶片。叶片呈椭圆形,边缘光滑。背景是树根和泥土。\n\n画面2:一个留着胡子的男人正在一个森林中土坑里挖掘。他穿着黑色T恤和卡其色裤子,跪在地,用具挖掘泥土。周围环绕着树木、树根和灌木。一个倒下的树干横跨土坑上方。\n\n画面3:同一个男人坐在他刚才挖的坑的边缘,看着前方。他的表情似乎略带沉思。背景与画面2相同。\n\n画面4:一个广角镜头显示出他挖出的坑。这是一个不规则形状的土坑,在树木繁茂的斜坡上。土壤呈深棕色,可见树根。\n\n画面5:同一个男人跪在地上,用一把小斧头砍一根木头。他穿着与前几个画面相同的衣服。地面上覆盖着落叶。周围是树木和灌木。",
|
||||||
"narration": "“哎呀,这片灌木叶子滴水如雨,感觉像是大自然的洗发水广告!但我这位‘挖宝达人’似乎更适合拍个‘森林里的单身狗’真人秀。等会儿,我要给树根唱首歌,听说它们爱音乐!”",
|
"narration": "“哎呀,这片灌木叶子滴水如雨,感觉像是大自然的洗发水广告!但我这位‘挖宝达人’似乎更适合拍个‘森林里的单身狗’真人秀。等会儿,我要给树根唱首歌,听说它们爱音乐!”",
|
||||||
"OST": 2,
|
"OST": 2,
|
||||||
"new_timestamp": "00:00:49,000-00:01:07,000"
|
"new_timestamp": "00:00:49,000-00:01:07,000"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"timestamp": "01:36-01:53",
|
"timestamp": "01:36-01:53",
|
||||||
"picture": "好的,以下是视频画面内容的客观描述:\n\n视频包含三个镜头:\n\n**镜头一:**个小型、浅水池塘,位于树林中。池塘的水看起来浑浊,呈绿褐色。池塘周围遍布泥土和落叶。多根树枝和树干横跨池塘,部分浸没在水中。周围的植被茂密,主要是深色树木和灌木。\n\n**镜头二:**距拍摄树深处,阳光透过树叶洒落在植被上。镜头中可见粗大的树干、树枝和各种绿叶植物。部分树枝似乎被砍断,切口可见。\n\n**镜头三:**近距离特写镜头,聚焦在树枝和绿叶上。叶片呈圆形,颜色为鲜绿色,有些叶片上有缺损。树枝颜色较深,呈现深褐色。背景是模糊的树林。\n",
|
"picture": "好的,以下是视频画面内容的客观描述:\n\n视频包含三个镜头:\n\n**镜头一:**个小型、浅水池塘,位于树林中。池塘的水看起来浑浊,呈绿褐色。池塘周围遍布泥土和落叶。多根树枝和树干横跨池塘,部分浸没在水中。周围的植被茂密主要是深色树木和灌木。\n\n**镜头二:**距拍摄树深处,阳光透过树叶洒落在植被上。镜头中可见粗大的树干、树枝和各种绿叶植物。部分树枝似乎被砍断,切口可见。\n\n**镜头三:**近距离特写镜头,聚焦在树枝和绿叶上。叶片呈圆形,颜色为鲜绿色,有些叶片上有缺损。树枝颜色较深,呈现深褐色。背景是模糊的树林。\n",
|
||||||
"narration": "“好吧,看来我们的‘挖宝达人’终于找到了一‘宝藏’——一个色泽如同绿豆汤的池塘!我敢打赌,这里不仅是小鱼儿的游乐场更是树枝们的‘水疗中心’!下次来这里,我得带上浮潜装备!”",
|
"narration": "“好吧,看来我们的‘挖宝达人’终于找到了一‘宝藏’——一个色泽如同绿豆汤的池塘!我敢打赌,这里不仅是小鱼儿的游乐场更是树枝们的‘水疗中心’!下次来这里,我得带上浮潜装备!”",
|
||||||
"OST": 2,
|
"OST": 2,
|
||||||
"new_timestamp": "00:01:07,000-00:01:24,000"
|
"new_timestamp": "00:01:07,000-00:01:24,000"
|
||||||
@ -639,8 +732,9 @@ if __name__ == "__main__":
|
|||||||
output_file = "../../storage/tasks/123/final-123.mp4"
|
output_file = "../../storage/tasks/123/final-123.mp4"
|
||||||
|
|
||||||
generate_video_v2(video_path=video_path,
|
generate_video_v2(video_path=video_path,
|
||||||
audio_path=audio_path,
|
audio_path=audio_path,
|
||||||
subtitle_path=subtitle_path,
|
subtitle_path=subtitle_path,
|
||||||
output_file=output_file,
|
output_file=output_file,
|
||||||
params=cfg
|
params=cfg,
|
||||||
|
list_script=list_script,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -40,7 +40,7 @@ def to_json(obj):
|
|||||||
# 如果对象是二进制数据,转换为base64编码的字符串
|
# 如果对象是二进制数据,转换为base64编码的字符串
|
||||||
elif isinstance(o, bytes):
|
elif isinstance(o, bytes):
|
||||||
return "*** binary data ***"
|
return "*** binary data ***"
|
||||||
# 如果<EFBFBD><EFBFBD><EFBFBD>象是字典,递归处理每个键值对
|
# 如果象是字典,递归处理每个键值对
|
||||||
elif isinstance(o, dict):
|
elif isinstance(o, dict):
|
||||||
return {k: serialize(v) for k, v in o.items()}
|
return {k: serialize(v) for k, v in o.items()}
|
||||||
# 如果对象是列表或元组,递归处理每个元素
|
# 如果对象是列表或元组,递归处理每个元素
|
||||||
@ -56,7 +56,7 @@ def to_json(obj):
|
|||||||
# 使用serialize函数处理输入对象
|
# 使用serialize函数处理输入对象
|
||||||
serialized_obj = serialize(obj)
|
serialized_obj = serialize(obj)
|
||||||
|
|
||||||
# 序列化处理后的对象为JSON<EFBFBD><EFBFBD><EFBFBD>符串
|
# 序列化处理后的对象为JSON符串
|
||||||
return json.dumps(serialized_obj, ensure_ascii=False, indent=4)
|
return json.dumps(serialized_obj, ensure_ascii=False, indent=4)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return None
|
return None
|
||||||
@ -354,15 +354,25 @@ def seconds_to_time(seconds: float) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def calculate_total_duration(scenes):
|
def calculate_total_duration(scenes):
|
||||||
|
"""
|
||||||
|
计算场景列表的总时长
|
||||||
|
|
||||||
|
Args:
|
||||||
|
scenes: 场景列表,每个场景包含 timestamp 字段,格式如 "00:00:28,350-00:00:41,000"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 总时长(秒)
|
||||||
|
"""
|
||||||
total_seconds = 0
|
total_seconds = 0
|
||||||
|
|
||||||
for scene in scenes:
|
for scene in scenes:
|
||||||
start, end = scene['timestamp'].split('-')
|
start, end = scene['timestamp'].split('-')
|
||||||
start_time = datetime.strptime(start, '%M:%S')
|
# 使用 time_to_seconds 函数处理更精确的时间格式
|
||||||
end_time = datetime.strptime(end, '%M:%S')
|
start_seconds = time_to_seconds(start)
|
||||||
|
end_seconds = time_to_seconds(end)
|
||||||
|
|
||||||
duration = end_time - start_time
|
duration = end_seconds - start_seconds
|
||||||
total_seconds += duration.total_seconds()
|
total_seconds += duration
|
||||||
|
|
||||||
return total_seconds
|
return total_seconds
|
||||||
|
|
||||||
@ -485,7 +495,7 @@ def clear_keyframes_cache(video_path: str = None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if video_path:
|
if video_path:
|
||||||
# <EFBFBD><EFBFBD><EFBFBD>理指定视频的缓存
|
# 理指定视频的缓存
|
||||||
video_hash = md5(video_path + str(os.path.getmtime(video_path)))
|
video_hash = md5(video_path + str(os.path.getmtime(video_path)))
|
||||||
video_keyframes_dir = os.path.join(keyframes_dir, video_hash)
|
video_keyframes_dir = os.path.join(keyframes_dir, video_hash)
|
||||||
if os.path.exists(video_keyframes_dir):
|
if os.path.exists(video_keyframes_dir):
|
||||||
|
|||||||
@ -26,7 +26,7 @@ psutil>=5.9.0
|
|||||||
opencv-python~=4.10.0.84
|
opencv-python~=4.10.0.84
|
||||||
scikit-learn~=1.5.2
|
scikit-learn~=1.5.2
|
||||||
google-generativeai~=0.8.3
|
google-generativeai~=0.8.3
|
||||||
pillow~=10.3.0
|
pillow==10.3.0
|
||||||
python-dotenv~=1.0.1
|
python-dotenv~=1.0.1
|
||||||
openai~=1.53.0
|
openai~=1.53.0
|
||||||
tqdm>=4.66.6
|
tqdm>=4.66.6
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user