From e90c195e9e248f021296097575697dda44fcca5e Mon Sep 17 00:00:00 2001 From: Larkspur-Wang Date: Fri, 25 Apr 2025 00:13:46 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E5=B8=A7=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E8=BD=AC=E5=9C=BA=E7=89=B9=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/schema.py | 2 +- app/services/utils/title_animations.py | 37 +++++++++++++++++- app/services/video.py | 52 +++++++++++++++----------- webui/Main.py | 2 +- 4 files changed, 68 insertions(+), 25 deletions(-) diff --git a/app/models/schema.py b/app/models/schema.py index cceec3f..bf5fa69 100644 --- a/app/models/schema.py +++ b/app/models/schema.py @@ -93,7 +93,7 @@ class VideoParams(BaseModel): subtitle_enabled: Optional[bool] = True subtitle_position: Optional[str] = "custom" # top, bottom, center, custom - custom_position: float = 70.0 + custom_position: float = 85.0 font_name: Optional[str] = "STHeitiMedium.ttc" text_fore_color: Optional[str] = "#FFFFFF" text_background_color: Union[bool, str] = True diff --git a/app/services/utils/title_animations.py b/app/services/utils/title_animations.py index d87fc7a..27bc96e 100644 --- a/app/services/utils/title_animations.py +++ b/app/services/utils/title_animations.py @@ -58,6 +58,11 @@ def pulse_animation(clip: ImageClip, speed: float = 1.0) -> ImageClip: :param speed: 动画速度 :return: 应用了动画效果的剪辑 """ + # 确保剪辑有持续时间 + if not hasattr(clip, 'duration') or clip.duration is None: + logger.warning("Clip has no duration, using default duration of 5 seconds") + clip = clip.with_duration(5) + # 应用整体缩放效果 return clip.resized(lambda t: 1 + 0.1 * np.sin(speed * 2 * np.pi * t)) @@ -70,6 +75,11 @@ def whole_bounce_animation(clip: ImageClip, speed: float = 1.0) -> ImageClip: :param speed: 动画速度 :return: 应用了动画效果的剪辑 """ + # 确保剪辑有持续时间 + if not hasattr(clip, 'duration') or clip.duration is None: + logger.warning("Clip has no duration, using default duration of 5 seconds") + clip = clip.with_duration(5) + # 获取原始位置 original_position = clip.pos @@ -129,10 +139,13 @@ def light_sweep_animation(clip: ImageClip, duration: float, speed: float = 1.0) # 创建一个白色光照剪辑 light_clip = ColorClip(size=clip.size, color=(255, 255, 255)) light_clip = light_clip.with_mask(light_mask) + light_clip = light_clip.with_duration(duration) # 合成原始剪辑和光照剪辑 # 由于 ColorClip 没有 with_blend 方法,我们直接使用 CompositeVideoClip - return CompositeVideoClip([clip, light_clip]) + composite_clip = CompositeVideoClip([clip, light_clip]) + composite_clip = composite_clip.with_duration(duration) + return composite_clip def fade_animation(clip: ImageClip, speed: float = 1.0) -> ImageClip: @@ -143,6 +156,11 @@ def fade_animation(clip: ImageClip, speed: float = 1.0) -> ImageClip: :param speed: 动画速度 :return: 应用了动画效果的剪辑 """ + # 确保剪辑有持续时间 + if not hasattr(clip, 'duration') or clip.duration is None: + logger.warning("Clip has no duration, using default duration of 5 seconds") + clip = clip.with_duration(5) + # 创建一个函数,根据时间返回不透明度 def make_frame_opacity(t): # 计算总时长 @@ -191,6 +209,11 @@ def wave_animation(clip: ImageClip, speed: float = 1.0) -> ImageClip: :param speed: 动画速度 :return: 应用了动画效果的剪辑 """ + # 确保剪辑有持续时间 + if not hasattr(clip, 'duration') or clip.duration is None: + logger.warning("Clip has no duration, using default duration of 5 seconds") + clip = clip.with_duration(5) + # 获取原始位置 original_position = clip.pos @@ -220,6 +243,11 @@ def bounce_animation(clip: ImageClip, speed: float = 1.0) -> ImageClip: :param speed: 动画速度 :return: 应用了动画效果的剪辑 """ + # 确保剪辑有持续时间 + if not hasattr(clip, 'duration') or clip.duration is None: + logger.warning("Clip has no duration, using default duration of 5 seconds") + clip = clip.with_duration(5) + # 注意:这个函数需要对每个字符单独处理 # 但由于我们已经将文本渲染为图像,无法直接操作单个字符 # 这里我们实现一个模拟效果,通过对图像应用波浪变形来模拟字符跳动 @@ -235,9 +263,14 @@ def bounce_animation(clip: ImageClip, speed: float = 1.0) -> ImageClip: # 获取原始帧 frame = clip.get_frame(t) - # 创建一个新的帧 + # 创建一个新的帧,保持透明通道 + # 使用与原始帧相同的数据类型和形状,但初始化为透明 new_frame = np.zeros_like(frame) + # 如果有alpha通道(RGBA),将所有像素设置为完全透明 + if frame.shape[2] == 4: + new_frame[:, :, 3] = 0 # 设置alpha通道为0(完全透明) + # 对每一列应用不同的垂直偏移,创造波浪效果 for x in range(w): # 计算该列的垂直偏移量,使用正弦函数创建波浪效果 diff --git a/app/services/video.py b/app/services/video.py index 32f4635..e61ff1d 100644 --- a/app/services/video.py +++ b/app/services/video.py @@ -93,6 +93,9 @@ def combine_videos( random.shuffle(raw_clips) # Add downloaded clips over and over until the duration of the audio (max_duration) has been reached + # 用于跟踪是否是第一个视频片段 + is_first_clip = True + while video_duration < audio_duration: for clip in raw_clips: # Check if clip is longer than the remaining audio @@ -139,27 +142,34 @@ def combine_videos( f"resizing video to {video_width} x {video_height}, clip size: {clip_w} x {clip_h}" ) - shuffle_side = random.choice(["left", "right", "top", "bottom"]) - logger.info(f"Using transition mode: {video_transition_mode}") - if video_transition_mode.value == VideoTransitionMode.none.value: - clip = clip - elif video_transition_mode.value == VideoTransitionMode.fade_in.value: - clip = video_effects.fadein_transition(clip, 1) - elif video_transition_mode.value == VideoTransitionMode.fade_out.value: - clip = video_effects.fadeout_transition(clip, 1) - elif video_transition_mode.value == VideoTransitionMode.slide_in.value: - clip = video_effects.slidein_transition(clip, 1, shuffle_side) - elif video_transition_mode.value == VideoTransitionMode.slide_out.value: - clip = video_effects.slideout_transition(clip, 1, shuffle_side) - elif video_transition_mode.value == VideoTransitionMode.shuffle.value: - transition_funcs = [ - lambda c: video_effects.fadein_transition(c, 1), - lambda c: video_effects.fadeout_transition(c, 1), - lambda c: video_effects.slidein_transition(c, 1, shuffle_side), - lambda c: video_effects.slideout_transition(c, 1, shuffle_side), - ] - shuffle_transition = random.choice(transition_funcs) - clip = shuffle_transition(clip) + # 如果是第一个视频片段,不应用转场效果 + if is_first_clip: + logger.info("First clip: no transition effect applied") + # 不应用任何转场效果 + is_first_clip = False # 更新标志,后续片段将应用转场效果 + else: + # 对后续视频片段应用转场效果 + shuffle_side = random.choice(["left", "right", "top", "bottom"]) + logger.info(f"Using transition mode: {video_transition_mode}") + if video_transition_mode.value == VideoTransitionMode.none.value: + clip = clip + elif video_transition_mode.value == VideoTransitionMode.fade_in.value: + clip = video_effects.fadein_transition(clip, 1) + elif video_transition_mode.value == VideoTransitionMode.fade_out.value: + clip = video_effects.fadeout_transition(clip, 1) + elif video_transition_mode.value == VideoTransitionMode.slide_in.value: + clip = video_effects.slidein_transition(clip, 1, shuffle_side) + elif video_transition_mode.value == VideoTransitionMode.slide_out.value: + clip = video_effects.slideout_transition(clip, 1, shuffle_side) + elif video_transition_mode.value == VideoTransitionMode.shuffle.value: + transition_funcs = [ + lambda c: video_effects.fadein_transition(c, 1), + lambda c: video_effects.fadeout_transition(c, 1), + lambda c: video_effects.slidein_transition(c, 1, shuffle_side), + lambda c: video_effects.slideout_transition(c, 1, shuffle_side), + ] + shuffle_transition = random.choice(transition_funcs) + clip = shuffle_transition(clip) if clip.duration > max_clip_duration: clip = clip.subclipped(0, max_clip_duration) diff --git a/webui/Main.py b/webui/Main.py index 239f32e..3888e1c 100644 --- a/webui/Main.py +++ b/webui/Main.py @@ -762,7 +762,7 @@ with right_panel: if params.subtitle_position == "custom": custom_position = st.text_input( - tr("Custom Position (% from top)"), value="65.0" + tr("Custom Position (% from top)"), value="85.0" ) try: params.custom_position = float(custom_position)