diff --git a/README.md b/README.md index e64a203..b00f97b 100644 --- a/README.md +++ b/README.md @@ -82,15 +82,21 @@ Simplest format: ```text Paris Trip -May 2024 Paris, France +May 2024 ``` Those three lines mean: 1. title -2. date -3. place +2. location +3. date + +If the date line is omitted, movmaker uses the date from the first picture/video and displays it like: + +```text +May 14th 2026 +``` Key/value format also works: @@ -100,7 +106,7 @@ date: May 2024 place: Paris, France ``` -Optional per-file captions may be supported, but they are not required: +Optional per-file captions are supported: ```text IMG_001.jpg: Eiffel Tower diff --git a/movmaker.py b/movmaker.py index 284fffc..54db5be 100755 --- a/movmaker.py +++ b/movmaker.py @@ -223,6 +223,18 @@ def slugify(value: str) -> str: return value or "movie" +def ordinal_day(day: int) -> str: + if 10 <= day % 100 <= 20: + suffix = "th" + else: + suffix = {1: "st", 2: "nd", 3: "rd"}.get(day % 10, "th") + return f"{day}{suffix}" + + +def display_date(dt: datetime) -> str: + return f"{dt.strftime('%B')} {ordinal_day(dt.day)} {dt.year}" + + 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") @@ -292,13 +304,13 @@ def parse_data_file(path: Path | None) -> MovieData: else: plain.append(line) - # Simple mode: first three non-key lines are title/date/place. + # Simple mode: first three non-key lines are title/location/date. if plain: data.title = data.title or plain[0] if len(plain) > 1: - data.date = data.date or plain[1] + data.place = data.place or plain[1] if len(plain) > 2: - data.place = data.place or plain[2] + data.date = data.date or plain[2] return data @@ -400,6 +412,7 @@ def build_video( title_label = "titled" title_font = str(Path.home() / ".local/share/fonts/google/BebasNeue-Regular.ttf") stamp_font = str(Path.home() / ".local/share/fonts/google/Inter%5Bopsz,wght%5D.ttf") + stamp_bold_font = str(Path.home() / ".local/share/fonts/google/Inter%5Bopsz,wght%5D.ttf") if title_lines: title_font_size = max(60, height // 10) subtitle_font_size = max(40, height // 18) @@ -438,7 +451,7 @@ def build_video( filters.append(f"[{current}]copy[{title_label}]") caption_label = title_label - caption_size = subtitle_font_size if title_lines else max(40, height // 18) + caption_size = (subtitle_font_size if title_lines else max(40, height // 18)) + 2 for idx, caption in enumerate(captions): if not caption: continue @@ -463,13 +476,13 @@ def build_video( ) if caption_fade > 0 else "1" next_label = f"caption{idx}" filters.append( - f"[{caption_label}]drawtext=fontfile='{title_font}':text='{drawtext_escape(caption)}':x=(w-text_w)/2:y=h-text_h-62:" + f"[{caption_label}]drawtext=fontfile='{stamp_bold_font}':text='{drawtext_escape(caption)}':x=(w-text_w)/2:y=h-text_h-62:" f"fontsize={caption_size}:fontcolor=white:borderw=1:bordercolor=black:" f"alpha='{caption_alpha}':enable='between(t,{start:.3f},{end:.3f})'[{next_label}]" ) bold_label = f"caption{idx}b" filters.append( - f"[{next_label}]drawtext=fontfile='{title_font}':text='{drawtext_escape(caption)}':x=(w-text_w)/2+1:y=h-text_h-62:" + f"[{next_label}]drawtext=fontfile='{stamp_bold_font}':text='{drawtext_escape(caption)}':x=(w-text_w)/2+1:y=h-text_h-62:" f"fontsize={caption_size}:fontcolor=white:borderw=1:bordercolor=black:" f"alpha='{caption_alpha}':enable='between(t,{start:.3f},{end:.3f})'[{bold_label}]" ) @@ -536,6 +549,8 @@ def main(argv: list[str] | None = None) -> int: crf = 28 media, audio_files, data = scan_input(args.input, args.image_duration) + if not data.date: + data.date = display_date(media_date(media[0].path)) fade = min(args.fade, max(0.0, args.image_duration - 0.1)) audio_fade = max(0.0, args.audio_fade)