1, 增加一次性输出多个视频
2, 增加背景音乐音量设置 3, 增加字幕位置 4, UI优化 5, 一些其他Bug修复和优化
This commit is contained in:
@@ -105,7 +105,7 @@ def create(audio_file, subtitle_file: str = ""):
|
||||
lines.append(utils.text_to_srt(idx, text, subtitle.get("start_time"), subtitle.get("end_time")))
|
||||
idx += 1
|
||||
|
||||
sub = "\n".join(lines)
|
||||
sub = "\n".join(lines) + "\n"
|
||||
with open(subtitle_file, "w", encoding="utf-8") as f:
|
||||
f.write(sub)
|
||||
logger.info(f"subtitle file created: {subtitle_file}")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import math
|
||||
import os.path
|
||||
import re
|
||||
from os import path
|
||||
@@ -5,7 +6,7 @@ from os import path
|
||||
from loguru import logger
|
||||
|
||||
from app.config import config
|
||||
from app.models.schema import VideoParams, VoiceNames
|
||||
from app.models.schema import VideoParams, VoiceNames, VideoConcatMode
|
||||
from app.services import llm, material, voice, video, subtitle
|
||||
from app.utils import utils
|
||||
|
||||
@@ -78,6 +79,8 @@ def start(task_id, params: VideoParams):
|
||||
return
|
||||
|
||||
audio_duration = voice.get_audio_duration(sub_maker)
|
||||
audio_duration = math.ceil(audio_duration)
|
||||
|
||||
subtitle_path = ""
|
||||
if params.subtitle_enabled:
|
||||
subtitle_path = path.join(utils.task_dir(task_id), f"subtitle.srt")
|
||||
@@ -110,7 +113,7 @@ def start(task_id, params: VideoParams):
|
||||
search_terms=video_terms,
|
||||
video_aspect=params.video_aspect,
|
||||
video_contact_mode=params.video_concat_mode,
|
||||
audio_duration=audio_duration,
|
||||
audio_duration=audio_duration * params.video_count,
|
||||
max_clip_duration=max_clip_duration,
|
||||
)
|
||||
if not downloaded_videos:
|
||||
@@ -118,27 +121,37 @@ def start(task_id, params: VideoParams):
|
||||
"failed to download videos, maybe the network is not available. if you are in China, please use a VPN.")
|
||||
return
|
||||
|
||||
logger.info("\n\n## combining videos")
|
||||
combined_video_path = path.join(utils.task_dir(task_id), f"combined.mp4")
|
||||
video.combine_videos(combined_video_path=combined_video_path,
|
||||
video_paths=downloaded_videos,
|
||||
audio_file=audio_file,
|
||||
video_aspect=params.video_aspect,
|
||||
video_concat_mode=params.video_concat_mode,
|
||||
max_clip_duration=max_clip_duration,
|
||||
threads=n_threads)
|
||||
final_video_paths = []
|
||||
video_concat_mode = params.video_concat_mode
|
||||
if params.video_count > 1:
|
||||
video_concat_mode = VideoConcatMode.random
|
||||
|
||||
final_video_path = path.join(utils.task_dir(task_id), f"final.mp4")
|
||||
for i in range(params.video_count):
|
||||
index = i + 1
|
||||
combined_video_path = path.join(utils.task_dir(task_id), f"combined-{index}.mp4")
|
||||
logger.info(f"\n\n## combining video: {index} => {combined_video_path}")
|
||||
video.combine_videos(combined_video_path=combined_video_path,
|
||||
video_paths=downloaded_videos,
|
||||
audio_file=audio_file,
|
||||
video_aspect=params.video_aspect,
|
||||
video_concat_mode=video_concat_mode,
|
||||
max_clip_duration=max_clip_duration,
|
||||
threads=n_threads)
|
||||
|
||||
final_video_path = path.join(utils.task_dir(task_id), f"final-{index}.mp4")
|
||||
|
||||
logger.info(f"\n\n## generating video: {index} => {final_video_path}")
|
||||
# Put everything together
|
||||
video.generate_video(video_path=combined_video_path,
|
||||
audio_path=audio_file,
|
||||
subtitle_path=subtitle_path,
|
||||
output_file=final_video_path,
|
||||
params=params,
|
||||
)
|
||||
final_video_paths.append(final_video_path)
|
||||
|
||||
logger.success(f"task {task_id} finished, generated {len(final_video_paths)} videos.")
|
||||
|
||||
logger.info("\n\n## generating video")
|
||||
# Put everything together
|
||||
video.generate_video(video_path=combined_video_path,
|
||||
audio_path=audio_file,
|
||||
subtitle_path=subtitle_path,
|
||||
output_file=final_video_path,
|
||||
params=params,
|
||||
)
|
||||
logger.start(f"task {task_id} finished")
|
||||
return {
|
||||
"video_file": final_video_path,
|
||||
"videos": final_video_paths,
|
||||
}
|
||||
|
||||
@@ -34,31 +34,30 @@ def combine_videos(combined_video_path: str,
|
||||
max_clip_duration: int = 5,
|
||||
threads: int = 2,
|
||||
) -> str:
|
||||
logger.info(f"combining {len(video_paths)} videos into one file: {combined_video_path}")
|
||||
audio_clip = AudioFileClip(audio_file)
|
||||
max_duration = audio_clip.duration
|
||||
logger.info(f"max duration of audio: {max_duration} seconds")
|
||||
audio_duration = audio_clip.duration
|
||||
logger.info(f"max duration of audio: {audio_duration} seconds")
|
||||
# Required duration of each clip
|
||||
req_dur = max_duration / len(video_paths)
|
||||
req_dur = audio_duration / len(video_paths)
|
||||
req_dur = max_clip_duration
|
||||
logger.info(f"each clip will be maximum {req_dur} seconds long")
|
||||
|
||||
aspect = VideoAspect(video_aspect)
|
||||
video_width, video_height = aspect.to_resolution()
|
||||
|
||||
clips = []
|
||||
tot_dur = 0
|
||||
video_duration = 0
|
||||
# Add downloaded clips over and over until the duration of the audio (max_duration) has been reached
|
||||
while tot_dur < max_duration:
|
||||
while video_duration < audio_duration:
|
||||
# random video_paths order
|
||||
if video_concat_mode.value == VideoConcatMode.random.value:
|
||||
random.shuffle(video_paths)
|
||||
|
||||
for video_path in video_paths:
|
||||
clip = VideoFileClip(video_path)
|
||||
clip = clip.without_audio()
|
||||
clip = VideoFileClip(video_path).without_audio()
|
||||
# Check if clip is longer than the remaining audio
|
||||
if (max_duration - tot_dur) < clip.duration:
|
||||
clip = clip.subclip(0, (max_duration - tot_dur))
|
||||
if (audio_duration - video_duration) < clip.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
|
||||
elif req_dur < clip.duration:
|
||||
clip = clip.subclip(0, req_dur)
|
||||
@@ -88,7 +87,7 @@ def combine_videos(combined_video_path: str,
|
||||
clip = clip.subclip(0, max_clip_duration)
|
||||
|
||||
clips.append(clip)
|
||||
tot_dur += clip.duration
|
||||
video_duration += clip.duration
|
||||
|
||||
final_clip = concatenate_videoclips(clips)
|
||||
final_clip = final_clip.set_fps(30)
|
||||
@@ -125,7 +124,7 @@ def wrap_text(text, max_width, font='Arial', fontsize=60):
|
||||
_wrapped_lines_.append(_txt_)
|
||||
_txt_ = ''
|
||||
_wrapped_lines_.append(_txt_)
|
||||
return '\n'.join(_wrapped_lines_)
|
||||
return '\n'.join(_wrapped_lines_).strip()
|
||||
|
||||
|
||||
def generate_video(video_path: str,
|
||||
@@ -153,11 +152,23 @@ def generate_video(video_path: str,
|
||||
|
||||
logger.info(f"using font: {font_path}")
|
||||
|
||||
def generator(txt):
|
||||
wrapped_txt = wrap_text(txt, max_width=video_width - 100,
|
||||
if params.subtitle_position == "top":
|
||||
position_height = video_height * 0.1
|
||||
elif params.subtitle_position == "bottom":
|
||||
position_height = video_height * 0.9
|
||||
else:
|
||||
position_height = "center"
|
||||
|
||||
def generator(txt, **kwargs):
|
||||
max_width = video_width * 0.9
|
||||
# logger.debug(f"rendering text: {txt}")
|
||||
wrapped_txt = wrap_text(txt,
|
||||
max_width=max_width,
|
||||
font=font_path,
|
||||
fontsize=params.font_size) # 调整max_width以适应你的视频
|
||||
return TextClip(
|
||||
fontsize=params.font_size
|
||||
) # 调整max_width以适应你的视频
|
||||
|
||||
clip = TextClip(
|
||||
wrapped_txt,
|
||||
font=font_path,
|
||||
fontsize=params.font_size,
|
||||
@@ -167,18 +178,16 @@ def generate_video(video_path: str,
|
||||
stroke_width=params.stroke_width,
|
||||
print_cmd=False,
|
||||
)
|
||||
|
||||
position_height = video_height - 200
|
||||
if params.video_aspect == VideoAspect.landscape:
|
||||
position_height = video_height - 100
|
||||
return clip
|
||||
|
||||
clips = [
|
||||
VideoFileClip(video_path),
|
||||
]
|
||||
|
||||
if subtitle_path and os.path.exists(subtitle_path):
|
||||
subtitles = SubtitlesClip(subtitles=subtitle_path, make_textclip=generator, encoding='utf-8')
|
||||
clips.append(subtitles.set_position(lambda _t: ('center', position_height)))
|
||||
sub = SubtitlesClip(subtitles=subtitle_path, make_textclip=generator, encoding='utf-8')
|
||||
sub_clip = sub.set_position(lambda _t: ('center', position_height))
|
||||
clips.append(sub_clip)
|
||||
|
||||
result = CompositeVideoClip(clips)
|
||||
|
||||
@@ -199,7 +208,7 @@ def generate_video(video_path: str,
|
||||
original_audio = video_clip.audio
|
||||
song_clip = AudioFileClip(bgm_file).set_fps(44100)
|
||||
# Set the volume of the song to 10% of the original volume
|
||||
song_clip = song_clip.volumex(0.2).set_fps(44100)
|
||||
song_clip = song_clip.volumex(params.bgm_volume)
|
||||
# Add the song to the video
|
||||
comp_audio = CompositeAudioClip([original_audio, song_clip])
|
||||
video_clip = video_clip.set_audio(comp_audio)
|
||||
@@ -211,3 +220,38 @@ def generate_video(video_path: str,
|
||||
|
||||
os.remove(temp_output_file)
|
||||
logger.success(f"completed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
txt = "hello 幸福经常被描述为最终人生目标和人类追求的核心 但它通常涉及对个人生活中意义和目的的深刻感悟"
|
||||
font = utils.resource_dir() + "/fonts/STHeitiMedium.ttc"
|
||||
t = wrap_text(text=txt, max_width=1000, font=font, fontsize=60)
|
||||
print(t)
|
||||
|
||||
task_id = "69232dfa-f6c5-4b5e-80ba-be3098d3f930"
|
||||
task_dir = utils.task_dir(task_id)
|
||||
video_file = f"{task_dir}/combined-1.mp4"
|
||||
audio_file = f"{task_dir}/audio.mp3"
|
||||
subtitle_file = f"{task_dir}/subtitle.srt"
|
||||
output_file = f"{task_dir}/final.mp4"
|
||||
cfg = VideoParams()
|
||||
cfg.video_aspect = VideoAspect.portrait
|
||||
cfg.font_name = "STHeitiMedium.ttc"
|
||||
cfg.font_size = 60
|
||||
cfg.stroke_color = "#000000"
|
||||
cfg.stroke_width = 1.5
|
||||
cfg.text_fore_color = "#FFFFFF"
|
||||
cfg.text_background_color = "transparent"
|
||||
cfg.bgm_file = ""
|
||||
cfg.bgm_volume = 0.2
|
||||
cfg.subtitle_enabled = True
|
||||
cfg.subtitle_position = "bottom"
|
||||
cfg.n_threads = 2
|
||||
cfg.paragraph_number = 1
|
||||
|
||||
generate_video(video_path=video_file,
|
||||
audio_path=audio_file,
|
||||
subtitle_path=subtitle_file,
|
||||
output_file=output_file,
|
||||
params=cfg
|
||||
)
|
||||
|
||||
@@ -78,7 +78,7 @@ def create_subtitle(sub_maker: submaker.SubMaker, text: str, subtitle_file: str)
|
||||
sub_line = ""
|
||||
|
||||
with open(subtitle_file, "w", encoding="utf-8") as file:
|
||||
file.write("\n".join(sub_items))
|
||||
file.write("\n".join(sub_items) + "\n")
|
||||
|
||||
|
||||
def get_audio_duration(sub_maker: submaker.SubMaker):
|
||||
|
||||
Reference in New Issue
Block a user