Minor updates
This commit is contained in:
parent
e512b5012b
commit
8a0901590b
2 changed files with 36 additions and 20 deletions
46
movmaker.py
46
movmaker.py
|
|
@ -466,7 +466,7 @@ def ordinal_day(day: int) -> str:
|
|||
|
||||
|
||||
def display_date(dt: datetime) -> str:
|
||||
return f"{dt.strftime('%B')} {ordinal_day(dt.day)} {dt.year}"
|
||||
return f"{dt.strftime('%B')} {dt.year}"
|
||||
|
||||
|
||||
def timestamp() -> str:
|
||||
|
|
@ -477,14 +477,6 @@ def console(message: str) -> None:
|
|||
print(f"[{timestamp()}] {ascii_safe(message)}")
|
||||
|
||||
|
||||
def estimate_output_size(duration: float, width: int, height: int, fps: int, crf: int) -> int:
|
||||
pixels = width * height
|
||||
quality_factor = max(0.45, (28 - crf) / 8)
|
||||
bitrate_mbps = max(2.0, pixels * fps * quality_factor / 7_000_000)
|
||||
audio_mbps = 0.192
|
||||
return int(duration * (bitrate_mbps + audio_mbps) * 1_000_000 / 8)
|
||||
|
||||
|
||||
def default_output_path(first_media: Path, data: MovieData, out_dir: Path) -> Path:
|
||||
date = media_date(first_media).strftime("%Y%m%d")
|
||||
title = slugify(data.title or first_media.parent.name or "movie")
|
||||
|
|
@ -601,6 +593,22 @@ def normalize_clip(item: MediaItem, out: Path, width: int, height: int, fps: int
|
|||
run(cmd)
|
||||
|
||||
|
||||
def audio_metadata(path: Path) -> tuple[str, str]:
|
||||
try:
|
||||
out = capture([
|
||||
tool_path(FFPROBE, "ffprobe"), "-v", "error",
|
||||
"-show_entries", "format_tags=artist,title",
|
||||
"-of", "json",
|
||||
str(path),
|
||||
])
|
||||
tags = json.loads(out).get("format", {}).get("tags", {})
|
||||
artist = tags.get("artist") or tags.get("ARTIST") or ""
|
||||
title = tags.get("title") or tags.get("TITLE") or ""
|
||||
return str(artist), str(title)
|
||||
except Exception:
|
||||
return "", ""
|
||||
|
||||
|
||||
def load_jamendo_client_id() -> str | None:
|
||||
if os.environ.get("JAMENDO_CLIENT_ID"):
|
||||
return os.environ["JAMENDO_CLIENT_ID"]
|
||||
|
|
@ -667,6 +675,8 @@ def download_jamendo_music(genre: str, out_dir: Path) -> Path | None:
|
|||
except Exception as exc:
|
||||
console(f"Could not download Jamendo music: {exc}")
|
||||
return None
|
||||
if title or artist:
|
||||
console("Audio metadata: " + " - ".join(x for x in [artist, title] if x))
|
||||
log_dir = out_dir / ".logs"
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
attribution = log_dir / f"{out.stem}.txt"
|
||||
|
|
@ -770,7 +780,7 @@ def build_video(
|
|||
title_label = "titled"
|
||||
title_font = str(Path.home() / ".local/share/fonts/google/BebasNeue-Regular.ttf")
|
||||
stamp_font = "/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf"
|
||||
caption_font = "Montserrat\\:style=Bold"
|
||||
caption_font = "/usr/share/fonts/truetype/ibm-plex/IBMPlexSans-SemiBold.ttf"
|
||||
if title_lines:
|
||||
title_font_size = max(60, height // 10)
|
||||
subtitle_font_size = max(40, height // 18)
|
||||
|
|
@ -839,8 +849,8 @@ def build_video(
|
|||
y_expr = f"h-text_h-62-{block_height - line_idx * line_gap}"
|
||||
next_label = f"caption{idx}_{line_idx}"
|
||||
filters.append(
|
||||
f"[{caption_label}]drawtext=font='{caption_font}':{textfile_arg(caption_line)}:x=(w-text_w)/2:y={y_expr}:"
|
||||
f"fontsize={caption_size}:fontcolor=white:borderw=1:bordercolor=black:"
|
||||
f"[{caption_label}]drawtext=fontfile='{caption_font}':{textfile_arg(caption_line)}:x=(w-text_w)/2:y={y_expr}:"
|
||||
f"fontsize={caption_size}:fontcolor=0xF2F2EE:borderw=0:shadowcolor=black@0.80:shadowx=3:shadowy=3:"
|
||||
f"alpha='{caption_alpha}':enable='between(t,{start:.3f},{end:.3f})'[{next_label}]"
|
||||
)
|
||||
caption_label = next_label
|
||||
|
|
@ -958,6 +968,10 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
console(f"Found {len(media)} media file(s), {len(audio_files)} audio file(s).")
|
||||
console(f"Audio: {audio_files[0] if audio_files else 'none'}")
|
||||
if audio_files:
|
||||
artist, title = audio_metadata(audio_files[0])
|
||||
if artist or title:
|
||||
console("Audio metadata: " + " - ".join(x for x in [artist, title] if x))
|
||||
console(f"Output: {output}")
|
||||
console(f"Log: {LOG_PATH}")
|
||||
if data.title or data.date or data.place:
|
||||
|
|
@ -989,14 +1003,14 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
# Use probed normalized duration for accuracy.
|
||||
durations = [ffprobe_duration(out) for out in clips]
|
||||
estimated_duration = durations[0] if len(durations) == 1 else durations[0] + sum(durations[1:]) - fade * (len(durations) - 1)
|
||||
estimated_size = estimate_output_size(estimated_duration, width, height, fps, crf)
|
||||
|
||||
captions = [data.captions.get(item.path.name, data.captions.get(str(item.path), "")) for item in media]
|
||||
audio = concat_or_mix_audio(audio_files, tmp / "music.m4a")
|
||||
if len(audio_files) > 1:
|
||||
console(f"Audio: {audio if audio else 'none'}")
|
||||
console(f"Estimated output: video length {estimated_duration:.1f}s, size about {format_bytes(estimated_size)}")
|
||||
if audio:
|
||||
artist, title = audio_metadata(audio)
|
||||
if artist or title:
|
||||
console("Audio metadata: " + " - ".join(x for x in [artist, title] if x))
|
||||
build_video(clips, durations, audio, output, data, first_media_date, captions, fade, audio_fade, audio_fade_in, video_fade, width, height, fps, preset, crf, tmp)
|
||||
|
||||
console(f"Done: {output}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue