Reuse intro logo as outro and refine title fade
This commit is contained in:
parent
61fbf6382e
commit
2ac00e0e67
2 changed files with 34 additions and 11 deletions
|
|
@ -256,9 +256,9 @@ Adjust these constants and rerun `movmaker.py` to change the intro behavior with
|
|||
- if no audio file exists in the input folder, searches Jamendo using `--music-genre` and downloads one random Creative Commons track that allows non-commercial reuse and derivative works; default query is `cinematic punk rock`
|
||||
- reads the Jamendo client ID from `JAMENDO_CLIENT_ID` or `~/.config/movmaker/jamendo.env`
|
||||
- fades music in over `DEFAULT_AUDIO_FADE_IN` seconds (2 s) and out over `DEFAULT_AUDIO_FADE_OUT` seconds (10 s) by default
|
||||
- builds a black intro still (matching the first media item’s resolution) when `--intro-logo` is provided, scales the logo to `INTRO_LOGO_TARGET_HEIGHT_RATIO` of the frame height (75 % by default), shows it at `INTRO_LOGO_DEFAULT_ALPHA` opacity (1.0 = fully opaque) from frame 0, and keeps it on screen for `audio_fade_in + INTRO_LOGO_EXTRA_HOLD` seconds (with defaults this is `DEFAULT_AUDIO_FADE_IN + INTRO_LOGO_EXTRA_HOLD`, i.e. 8 s) before allowing the first crossfade
|
||||
- builds a black intro still (matching the first media item’s resolution) when `--intro-logo` is provided, scales the logo to `INTRO_LOGO_TARGET_HEIGHT_RATIO` of the frame height (75 % by default), shows it at `INTRO_LOGO_DEFAULT_ALPHA` opacity (1.0 = fully opaque) from frame 0, and keeps it on screen for `audio_fade_in + INTRO_LOGO_EXTRA_HOLD` seconds (with defaults this is `DEFAULT_AUDIO_FADE_IN + INTRO_LOGO_EXTRA_HOLD`, i.e. 8 s) before allowing the first crossfade; the same clip is appended at the end so the last real scene crossfades back into the logo-on-black frame
|
||||
- opening title text fades in after `INTRO_TITLE_FADE_DELAY` seconds (2 s), stays visible for `INTRO_TITLE_VISIBLE` seconds (4 s), then fades out over `INTRO_TITLE_FADE_LENGTH` seconds (1 s)
|
||||
- fades the first real clip in and the final clip out with the `--video-fade` duration (default `DEFAULT_VIDEO_FADE`, currently 1 s); the black intro/logo pad respects this fade-in only when `INTRO_VIDEO_FADE_ENABLED` is `True`
|
||||
- fades the first real clip in with the `--video-fade` duration (default `DEFAULT_VIDEO_FADE`, currently 1 s) while leaving the logo intro instant-on when `INTRO_VIDEO_FADE_ENABLED` is `False`; when a logo intro is present, the reusable outro clip removes the need for a final global fade-out, so the crossfade into the outro handles the closing visuals while audio still follows `DEFAULT_AUDIO_FADE_OUT`
|
||||
- displays optional per-file captions at bottom center with slightly off-white text and a soft shadow; use `|` to split caption lines
|
||||
- adds persistent bottom-left title/date/place and bottom-right `Bubulescu.Org` stamps
|
||||
- preserves special characters in rendered overlays and metadata
|
||||
|
|
|
|||
41
movmaker.py
41
movmaker.py
|
|
@ -36,7 +36,7 @@ FFMPEG = BASE_DIR / "ffmpeg" / "ffmpeg"
|
|||
FFPROBE = BASE_DIR / "ffmpeg" / "ffprobe"
|
||||
INTRO_LOGO_NAME = "intro.png"
|
||||
DEFAULT_INTRO_LOGO = BASE_DIR / "img" / "moto_travel.png"
|
||||
INTRO_LOGO_TARGET_HEIGHT_RATIO = 0.75
|
||||
INTRO_LOGO_TARGET_HEIGHT_RATIO = 0.90
|
||||
INTRO_LOGO_EXTRA_HOLD = 6.0
|
||||
INTRO_LOGO_FADE_OFFSET = 3.0
|
||||
INTRO_LOGO_DEFAULT_ALPHA = 1.0
|
||||
|
|
@ -883,6 +883,7 @@ def build_video(
|
|||
text_dir: Path,
|
||||
video_intro_duration: float | None = None,
|
||||
intro_visible_lead: float = 0.0,
|
||||
skip_video_fade_out: bool = False,
|
||||
) -> None:
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
|
@ -937,7 +938,7 @@ def build_video(
|
|||
|
||||
fade_label = current
|
||||
apply_fade_in = video_fade > 0 and INTRO_VIDEO_FADE_ENABLED
|
||||
apply_fade_out = video_fade > 0
|
||||
apply_fade_out = video_fade > 0 and not skip_video_fade_out
|
||||
if apply_fade_in or apply_fade_out:
|
||||
fade_in_start = intro_duration
|
||||
fade_out_start = max(0.0, total_duration - video_fade)
|
||||
|
|
@ -961,34 +962,44 @@ def build_video(
|
|||
subtitle_font_size = max(40, height // 18)
|
||||
main_title = data.title or title_lines[0]
|
||||
subtitle = " | ".join(x for x in [data.date, data.place] if x)
|
||||
title_alpha = "if(lt(t\\,2)\\,0\\,if(lt(t\\,3)\\,(t-2)/1\\,if(lt(t\\,7)\\,1\\,if(lt(t\\,8)\\,8-t\\,0))))"
|
||||
fade_in_start = INTRO_TITLE_FADE_DELAY
|
||||
fade_in_end = fade_in_start + INTRO_TITLE_FADE_LENGTH
|
||||
visible_end = fade_in_end + INTRO_TITLE_VISIBLE
|
||||
fade_out_end = visible_end + INTRO_TITLE_FADE_LENGTH
|
||||
title_alpha = (
|
||||
f"if(lt(t\\,{fade_in_start:.3f})\\,0\\,"
|
||||
f"if(lt(t\\,{fade_in_end:.3f})\\,(t-{fade_in_start:.3f})/{INTRO_TITLE_FADE_LENGTH:.3f}\\,"
|
||||
f"if(lt(t\\,{visible_end:.3f})\\,1\\,"
|
||||
f"if(lt(t\\,{fade_out_end:.3f})\\,({fade_out_end:.3f}-t)/{INTRO_TITLE_FADE_LENGTH:.3f}\\,0))))"
|
||||
)
|
||||
title_enable = f"enable='between(t,{fade_in_start:.3f},{fade_out_end:.3f})'"
|
||||
if subtitle:
|
||||
title_y = f"(h-text_h)/2-{title_font_size // 2}"
|
||||
filters.append(
|
||||
f"[{current}]drawtext=fontfile='{title_font}':{textfile_arg(main_title)}:x=(w-text_w)/2:y={title_y}:"
|
||||
f"fontsize={title_font_size}:fontcolor=white:borderw=0:shadowcolor=black@0.75:shadowx=3:shadowy=3:"
|
||||
f"alpha='{title_alpha}':enable='between(t,0,7)'[title0]"
|
||||
f"alpha='{title_alpha}':{title_enable}[title0]"
|
||||
)
|
||||
filters.append(
|
||||
f"[title0]drawtext=fontfile='{title_font}':{textfile_arg(main_title)}:x=(w-text_w)/2+1:y={title_y}:"
|
||||
f"fontsize={title_font_size}:fontcolor=white:borderw=0:shadowcolor=black@0.75:shadowx=3:shadowy=3:"
|
||||
f"alpha='{title_alpha}':enable='between(t,0,7)'[title0b]"
|
||||
f"alpha='{title_alpha}':{title_enable}[title0b]"
|
||||
)
|
||||
filters.append(
|
||||
f"[title0b]drawtext=fontfile='{title_font}':{textfile_arg(subtitle)}:x=(w-text_w)/2:y=(h-text_h)/2+{subtitle_font_size}:"
|
||||
f"fontsize={subtitle_font_size}:fontcolor=white:borderw=0:shadowcolor=black@0.75:shadowx=2:shadowy=2:"
|
||||
f"alpha='{title_alpha}':enable='between(t,0,7)'[{title_label}]"
|
||||
f"alpha='{title_alpha}':{title_enable}[{title_label}]"
|
||||
)
|
||||
else:
|
||||
filters.append(
|
||||
f"[{current}]drawtext=fontfile='{title_font}':{textfile_arg(main_title)}:x=(w-text_w)/2:y=(h-text_h)/2:"
|
||||
f"fontsize={title_font_size}:fontcolor=white:borderw=0:shadowcolor=black@0.75:shadowx=3:shadowy=3:"
|
||||
f"alpha='{title_alpha}':enable='between(t,0,7)'[title0]"
|
||||
f"alpha='{title_alpha}':{title_enable}[title0]"
|
||||
)
|
||||
filters.append(
|
||||
f"[title0]drawtext=fontfile='{title_font}':{textfile_arg(main_title)}:x=(w-text_w)/2+1:y=(h-text_h)/2:"
|
||||
f"fontsize={title_font_size}:fontcolor=white:borderw=0:shadowcolor=black@0.75:shadowx=3:shadowy=3:"
|
||||
f"alpha='{title_alpha}':enable='between(t,0,7)'[{title_label}]"
|
||||
f"alpha='{title_alpha}':{title_enable}[{title_label}]"
|
||||
)
|
||||
else:
|
||||
filters.append(f"[{current}]copy[{title_label}]")
|
||||
|
|
@ -1265,6 +1276,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||
console("Audio metadata: " + " - ".join(x for x in [artist, title] if x))
|
||||
video_intro_duration = audio_fade_in
|
||||
intro_visible_lead = 0.0
|
||||
intro_clip_path: Path | None = None
|
||||
intro_clip_duration = 0.0
|
||||
if intro_logo_path is not None and audio_fade_in > 0:
|
||||
if first_media_resolution is None:
|
||||
console("Warning: Could not determine first media resolution; skipping intro logo frame")
|
||||
|
|
@ -1278,12 +1291,22 @@ def main(argv: list[str] | None = None) -> int:
|
|||
normalize_clip(intro_item, intro_clip, width, height, fps, preset, crf)
|
||||
clips = [intro_clip, *clips]
|
||||
durations = [intro_item.duration, *durations]
|
||||
intro_clip_path = intro_clip
|
||||
intro_clip_duration = intro_item.duration
|
||||
captions = ["", *captions]
|
||||
clip_audio_paths = [None, *clip_audio_paths]
|
||||
clip_stamps = ["", *clip_stamps]
|
||||
intro_visible_lead = min(intro_item.duration, INTRO_LOGO_FADE_OFFSET)
|
||||
video_intro_duration = 0.0
|
||||
build_video(clips, durations, audio, clip_audio_paths, output, data, first_media_date, captions, clip_stamps, fade, audio_fade, audio_fade_in, video_fade, args.intro_logo, intro_logo_alpha, width, height, fps, preset, crf, tmp, video_intro_duration, intro_visible_lead=intro_visible_lead)
|
||||
outro_added = False
|
||||
if intro_clip_path is not None:
|
||||
clips.append(intro_clip_path)
|
||||
durations.append(intro_clip_duration)
|
||||
captions.append("")
|
||||
clip_audio_paths.append(None)
|
||||
clip_stamps.append("")
|
||||
outro_added = True
|
||||
build_video(clips, durations, audio, clip_audio_paths, output, data, first_media_date, captions, clip_stamps, fade, audio_fade, audio_fade_in, video_fade, args.intro_logo, intro_logo_alpha, width, height, fps, preset, crf, tmp, video_intro_duration, intro_visible_lead=intro_visible_lead, skip_video_fade_out=outro_added)
|
||||
|
||||
console(f"Done: {output}")
|
||||
return 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue