fix(intro): precompose alpha-adjusted logo PNG and overlay to respect transparency across ffmpeg builds
This commit is contained in:
parent
801f17ef10
commit
192c57ee13
1 changed files with 40 additions and 13 deletions
53
movmaker.py
53
movmaker.py
|
|
@ -31,11 +31,15 @@ IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tif", ".tiff"}
|
|||
VIDEO_EXTS = {".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v"}
|
||||
AUDIO_EXTS = {".mp3", ".wav", ".m4a", ".aac", ".flac", ".ogg"}
|
||||
DATA_NAMES = {"data.txt", "info.txt", "title.txt"}
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
FFMPEG = BASE_DIR / "ffmpeg" / "ffmpeg"
|
||||
FFPROBE = BASE_DIR / "ffmpeg" / "ffprobe"
|
||||
INTRO_LOGO_NAME = "intro.png"
|
||||
DEFAULT_INTRO_LOGO = BASE_DIR / "img" / "moto_travel.png"
|
||||
JAMENDO_ENV = Path.home() / ".config" / "movmaker" / "jamendo.env"
|
||||
LOG_PATH: Path | None = None
|
||||
|
||||
INTRO_LOGO_TARGET_HEIGHT_RATIO = 0.99
|
||||
INTRO_LOGO_EXTRA_HOLD = 6.0
|
||||
INTRO_LOGO_FADE_OFFSET = 3.0
|
||||
|
|
@ -56,8 +60,6 @@ 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
|
||||
|
||||
|
||||
def tool_path(local_path: Path, fallback: str) -> str:
|
||||
|
|
@ -707,6 +709,11 @@ def drawtext_escape(text: str) -> str:
|
|||
def render_intro_logo_frame(logo: Path, out: Path, width: int, height: int, alpha: float) -> None:
|
||||
"""Build a single black frame with the intro logo centered and scaled.
|
||||
|
||||
This implementation first renders a temporary PNG of the logo with the
|
||||
requested alpha applied, then overlays that precomposed PNG onto a black
|
||||
background. This avoids passing problematic options to the overlay filter
|
||||
and works reliably across ffmpeg builds.
|
||||
|
||||
Args:
|
||||
logo: Source PNG to overlay.
|
||||
out: Destination PNG path for the composed frame.
|
||||
|
|
@ -718,17 +725,30 @@ def render_intro_logo_frame(logo: Path, out: Path, width: int, height: int, alph
|
|||
raise ValueError("intro logo frame must have positive width/height")
|
||||
alpha = min(1.0, max(0.0, alpha))
|
||||
target_logo_height = max(1, int(round(height * INTRO_LOGO_TARGET_HEIGHT_RATIO)))
|
||||
filters = (
|
||||
f"[1:v]scale=w={width}:h={target_logo_height}:force_original_aspect_ratio=decrease,"
|
||||
f"format=rgba,colorchannelmixer=aa={alpha:.3f}[logo];"
|
||||
f"[0:v]format=rgba[bg];"
|
||||
f"[bg][logo]overlay=x=(W-w)/2:y=(H-h)/2:format=rgba[vout]"
|
||||
)
|
||||
cmd = [
|
||||
tool_path(FFMPEG, "ffmpeg"), "-y",
|
||||
"-f", "lavfi", "-i", f"color=c=black:s={width}x{height}",
|
||||
|
||||
ffmpeg_exe = tool_path(FFMPEG, "ffmpeg")
|
||||
|
||||
# Prepare a temporary logo PNG with the requested alpha applied.
|
||||
tmp_logo = out.with_name(out.stem + "_alpha.png")
|
||||
cmd_logo = [
|
||||
ffmpeg_exe, "-y",
|
||||
"-i", str(logo),
|
||||
"-filter_complex", filters,
|
||||
"-vf", (
|
||||
f"scale=w={width}:h={target_logo_height}:force_original_aspect_ratio=decrease,"
|
||||
f"format=rgba,colorchannelmixer=aa={alpha:.3f}"
|
||||
),
|
||||
"-frames:v", "1",
|
||||
"-pix_fmt", "rgba",
|
||||
str(tmp_logo),
|
||||
]
|
||||
run(cmd_logo)
|
||||
|
||||
# Overlay the precomposed logo onto a black background and write the single frame.
|
||||
cmd = [
|
||||
ffmpeg_exe, "-y",
|
||||
"-f", "lavfi", "-i", f"color=c=black:s={width}x{height}",
|
||||
"-i", str(tmp_logo),
|
||||
"-filter_complex", "[0:v][1:v]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[vout]",
|
||||
"-map", "[vout]",
|
||||
"-frames:v", "1",
|
||||
"-pix_fmt", "rgba",
|
||||
|
|
@ -736,7 +756,14 @@ def render_intro_logo_frame(logo: Path, out: Path, width: int, height: int, alph
|
|||
"-update", "1",
|
||||
str(out),
|
||||
]
|
||||
run(cmd)
|
||||
try:
|
||||
run(cmd)
|
||||
finally:
|
||||
try:
|
||||
if tmp_logo.exists():
|
||||
tmp_logo.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def normalize_clip(item: MediaItem, out: Path, width: int, height: int, fps: int, preset: str, crf: int) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue