Duck soundtrack when using clip audio
This commit is contained in:
parent
2ca263dd58
commit
af3fd51226
2 changed files with 44 additions and 8 deletions
|
|
@ -229,7 +229,7 @@ The most common visual timing/styling tweaks are exposed as constants near the t
|
|||
| `INTRO_LOGO_EXTRA_HOLD` | Extra seconds the synthesized intro still remains before the first crossfade. | `6.0` |
|
||||
| `INTRO_LOGO_FADE_OFFSET` | Minimum time (s) the logo stays fully visible before the first xfade is allowed to start. | `3.0` |
|
||||
| `INTRO_LOGO_DEFAULT_ALPHA` | Default opacity for the intro logo (1 = fully opaque, 0 = invisible). | `1.0` |
|
||||
| `INTRO_TITLE_FADE_DELAY` | Seconds after start before the main title begins fading in. | `2.0` |
|
||||
| `INTRO_TITLE_FADE_DELAY` | Seconds after start before the main title begins fading in. | `3.0` |
|
||||
| `INTRO_TITLE_FADE_LENGTH` | Seconds the fade-in/out lasts. | `1.0` |
|
||||
| `INTRO_TITLE_VISIBLE` | Seconds the title stays fully visible between fade in/out. | `4.0` |
|
||||
| `DEFAULT_IMAGE_DURATION` | Default fully visible time per still image before fades are added. | `6.0` |
|
||||
|
|
@ -239,6 +239,7 @@ The most common visual timing/styling tweaks are exposed as constants near the t
|
|||
| `DEFAULT_AUDIO_FADE_OUT` | Default audio fade-out duration. | `10.0` |
|
||||
| `DEFAULT_VIDEO_FADE` | Default video fade-in/out duration for the first/last real clips. | `1.0` |
|
||||
| `INTRO_VIDEO_FADE_ENABLED` | Whether the synthesized intro/logo pad gets the global fade-in effect (`False` keeps it fully visible from frame 0). | `False` |
|
||||
| `CLIP_AUDIO_FADE` | Seconds used to fade video clip audio in/out while ducking the soundtrack. | `0.75` |
|
||||
|
||||
Adjust these constants and rerun `movmaker.py` to change the intro behavior without touching the rest of the pipeline.
|
||||
|
||||
|
|
@ -253,6 +254,7 @@ Adjust these constants and rerun `movmaker.py` to change the intro behavior with
|
|||
- includes video clips inline
|
||||
- crossfades between media items over `DEFAULT_CROSSFADE_DURATION` seconds by default (3 s)
|
||||
- loops/trims music to match the video
|
||||
- when a clip's "Use video file audio" option is enabled, movmaker crossfades (`CLIP_AUDIO_FADE` seconds) from the soundtrack to that clip's audio, ducks the music while it plays, then fades the music back in
|
||||
- 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
|
||||
|
|
|
|||
48
movmaker.py
48
movmaker.py
|
|
@ -50,6 +50,11 @@ DEFAULT_AUDIO_FADE_IN = 2.0
|
|||
DEFAULT_AUDIO_FADE_OUT = 10.0
|
||||
DEFAULT_VIDEO_FADE = 1.0
|
||||
INTRO_VIDEO_FADE_ENABLED = False
|
||||
CLIP_AUDIO_FADE = 0.75
|
||||
CLIP_AUDIO_SIDECHAIN_THRESHOLD = 0.04
|
||||
CLIP_AUDIO_SIDECHAIN_RATIO = 8.0
|
||||
CLIP_AUDIO_SIDECHAIN_ATTACK = 5.0
|
||||
CLIP_AUDIO_SIDECHAIN_RELEASE = 250.0
|
||||
JAMENDO_ENV = Path.home() / ".config" / "movmaker" / "jamendo.env"
|
||||
LOG_PATH: Path | None = None
|
||||
|
||||
|
|
@ -1109,7 +1114,7 @@ def build_video(
|
|||
f"fontsize={stamp_size}:fontcolor=white:borderw=1:bordercolor=black[{final_label}]"
|
||||
)
|
||||
|
||||
audio_mix_labels: list[str] = []
|
||||
bg_audio_label: str | None = None
|
||||
if audio:
|
||||
audio_index = len(clips)
|
||||
audio_fade_out_start = max(0.0, total_duration - audio_fade)
|
||||
|
|
@ -1117,7 +1122,8 @@ def build_video(
|
|||
f"[{audio_index}:a:0]atrim=0:{total_duration:.3f},asetpts=PTS-STARTPTS,"
|
||||
f"afade=t=in:st=0:d={audio_fade_in:.3f},afade=t=out:st={audio_fade_out_start:.3f}:d={audio_fade:.3f}[bg_audio]"
|
||||
)
|
||||
audio_mix_labels.append("[bg_audio]")
|
||||
bg_audio_label = "bg_audio"
|
||||
clip_audio_labels: list[str] = []
|
||||
clip_audio_input = clip_audio_input_start
|
||||
for idx, clip_audio in enumerate(clip_audio_paths):
|
||||
if clip_audio is None:
|
||||
|
|
@ -1138,7 +1144,7 @@ def build_video(
|
|||
if segment <= 0:
|
||||
clip_audio_input += 1
|
||||
continue
|
||||
fade_len = min(fade, segment / 2)
|
||||
fade_len = min(CLIP_AUDIO_FADE, segment / 2)
|
||||
delay_ms = max(0, int(start * 1000))
|
||||
label = f"clip_audio_{idx}"
|
||||
filters.append(
|
||||
|
|
@ -1146,10 +1152,38 @@ def build_video(
|
|||
f"afade=t=in:st=0:d={fade_len:.3f},afade=t=out:st={max(0.0, segment - fade_len):.3f}:d={fade_len:.3f},"
|
||||
f"adelay={delay_ms}:all=1[{label}]"
|
||||
)
|
||||
audio_mix_labels.append(f"[{label}]")
|
||||
clip_audio_labels.append(label)
|
||||
clip_audio_input += 1
|
||||
if audio_mix_labels:
|
||||
filters.append("".join(audio_mix_labels) + f"amix=inputs={len(audio_mix_labels)}:duration=longest:normalize=0[aout]")
|
||||
clip_mix_label: str | None = None
|
||||
if clip_audio_labels:
|
||||
if len(clip_audio_labels) == 1:
|
||||
clip_mix_label = clip_audio_labels[0]
|
||||
else:
|
||||
inputs = "".join(f"[{name}]" for name in clip_audio_labels)
|
||||
filters.append(inputs + f"amix=inputs={len(clip_audio_labels)}:duration=longest:normalize=0[clip_mix]")
|
||||
clip_mix_label = "clip_mix"
|
||||
|
||||
final_bg_label = bg_audio_label
|
||||
final_clip_label = clip_mix_label
|
||||
|
||||
if final_bg_label and final_clip_label:
|
||||
sc_label = f"{final_clip_label}_sc"
|
||||
mix_label = f"{final_clip_label}_main"
|
||||
filters.append(f"[{final_clip_label}]asplit=2[{sc_label}][{mix_label}]")
|
||||
filters.append(
|
||||
f"[{final_bg_label}][{sc_label}]sidechaincompress=threshold={CLIP_AUDIO_SIDECHAIN_THRESHOLD}:"
|
||||
f"ratio={CLIP_AUDIO_SIDECHAIN_RATIO}:attack={CLIP_AUDIO_SIDECHAIN_ATTACK}:"
|
||||
f"release={CLIP_AUDIO_SIDECHAIN_RELEASE}[bg_ducked]"
|
||||
)
|
||||
final_bg_label = "bg_ducked"
|
||||
final_clip_label = mix_label
|
||||
|
||||
if final_bg_label and final_clip_label:
|
||||
filters.append(f"[{final_bg_label}][{final_clip_label}]amix=inputs=2:duration=longest:normalize=0[aout]")
|
||||
elif final_bg_label:
|
||||
filters.append(f"[{final_bg_label}]anull[aout]")
|
||||
elif final_clip_label:
|
||||
filters.append(f"[{final_clip_label}]anull[aout]")
|
||||
|
||||
cmd += ["-filter_complex", ";".join(filters), "-map", f"[{final_label}]"]
|
||||
metadata = {
|
||||
|
|
@ -1168,7 +1202,7 @@ def build_video(
|
|||
if value:
|
||||
cmd += ["-metadata", f"{key}={value}"]
|
||||
|
||||
if audio_mix_labels:
|
||||
if final_bg_label or final_clip_label:
|
||||
cmd += ["-map", "[aout]", "-c:a", "aac", "-b:a", "192k", "-shortest"]
|
||||
else:
|
||||
cmd += ["-an"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue