No description
Find a file
2026-05-26 10:03:09 +00:00
.gitignore Refine logging and Jamendo docs 2026-05-25 10:19:45 +00:00
movmaker.py Normalize media dates for sorting 2026-05-26 10:03:09 +00:00
mvlog_worker.py Make MVLog state writes permission-safe 2026-05-25 20:15:28 +00:00
README.md Add MVLog remote worker 2026-05-25 19:21:34 +00:00

movmaker

Simple Python + ffmpeg movie maker.

Drop pictures, videos, music, and optionally one simple data.txt file into a single flat input directory. Then run one command to create an MP4 video.

No YAML. No required subdirectories.

Requirements

  • Python 3
  • ffmpeg
  • ffprobe usually included with ffmpeg

Install on Debian/Ubuntu/Raspberry Pi OS:

sudo apt install ffmpeg

Fonts

The default title/stamp styling uses these fonts:

  • Bebas Neue for the opening title, date, and location
  • Noto Sans Bold for the bottom-left/bottom-right stamps
  • IBM Plex Sans SemiBold for per-file captions

Both fonts support common Danish and Croatian characters such as æ ø å Æ Ø Å č ć ž š đ Č Ć Ž Š Đ.

Install them locally for the current user:

mkdir -p ~/.local/share/fonts/google
curl -L -o ~/.local/share/fonts/google/BebasNeue-Regular.ttf \
  'https://github.com/google/fonts/raw/main/ofl/bebasneue/BebasNeue-Regular.ttf'
sudo apt install fonts-noto-core fonts-ibm-plex
fc-cache -f

Check that fonts are available:

fc-match 'Bebas Neue'
fc-match 'Noto Sans'
fc-match 'IBM Plex Sans'

Optional Jamendo music download

If the input folder has no audio file, movmaker can download one random track from Jamendo.

Save your Jamendo client ID in either an environment variable:

export JAMENDO_CLIENT_ID='your_client_id_here'

or in a local config file:

mkdir -p ~/.config/movmaker
echo 'JAMENDO_CLIENT_ID=your_client_id_here' > ~/.config/movmaker/jamendo.env
chmod 600 ~/.config/movmaker/jamendo.env

The default music search query is:

cinematic punk rock

Override it with:

python3 movmaker.py input --music-genre "upbeat rock"

Input folder

Example:

input/
  IMG_001.jpg
  IMG_002.png
  holiday_clip.mp4
  music.mp3
  data.txt

Supported file types:

  • Images: .jpg, .jpeg, .png, .webp, .bmp, .tif, .tiff
  • Videos: .mp4, .mov, .mkv, .avi, .webm, .m4v
  • Audio: .mp3, .wav, .m4a, .aac, .flac, .ogg
  • Data: data.txt, info.txt, or title.txt

Files are ordered by filename, so names like this work well:

001.jpg
002.jpg
003.mp4
004.jpg

data.txt

data.txt is optional, but if present every non-empty, non-comment line must use mandatory key: value syntax. Plain positional lines are not supported.

Movie metadata keys:

Title: Paris Trip
Location: Paris, France
Date: May 2024
Description: Longer description shown in MVLog and embedded in final MP4 metadata.

Supported metadata key aliases:

  • title: Title or Name
  • location: Location, Place, or Where
  • date: Date, Dates, or When
  • description: Description, Desc, or Synopsis

If the date entry is omitted, movmaker uses the date from the first picture/video and displays it like:

May 2026

If the location entry is omitted and GPS coordinates are found in the first picture/video that has them, movmaker reverse geocodes them and uses that as the location. It tries to return only city/town/village, country; if no city/town/village is found, it uses just the country.

Any other key: value line is treated as a per-file caption where the key is the exact media filename:

IMG_001.jpg: Eiffel Tower
IMG_002.jpg: Louvre
IMG_003.jpg: First line | Second line

MVLog worker

mvlog_worker.py can be run from this machine by cron or a systemd timer to process MVLog jobs stored on the web machine.

Default paths:

  • remote host: 192.168.0.204
  • remote web root: /var/www/html/mvlog
  • inputs: in-dir/<job>/
  • outputs: out-dir/YYYYMMDD_title.mp4
  • per-job state: in-dir/<job>/.movmaker-state.json

The worker:

  1. scans remote in-dir/
  2. computes a fingerprint from input filenames, sizes, and mtimes
  3. skips jobs whose fingerprint matches a completed state file and whose output exists
  4. copies changed jobs locally
  5. renders with movmaker.py
  6. uploads the MP4 atomically to remote out-dir/
  7. removes the old output if the regenerated filename changed
  8. clears the web metadata cache

Run once:

./mvlog_worker.py

Process one job:

./mvlog_worker.py --job romo2026

Force regeneration:

./mvlog_worker.py --job romo2026 --force

Pass extra movmaker options:

./mvlog_worker.py --movmaker-arg=--preview

Recommended scheduling is a systemd timer or cron with a separate lock, e.g. every 5 minutes.

Usage

Basic:

python3 movmaker.py input

With options:

python3 movmaker.py input \
  --out-dir . \
  --image-duration 6 \
  --fade 3 \
  --audio-fade 10 \
  --audio-fade-in 2 \
  --video-fade 1.5 \
  --music-genre "cinematic punk rock" \
  --resolution 1920x1080 \
  --fps 30

Fast preview render:

python3 movmaker.py input --preview

Current behavior

  • scans one flat input directory
  • auto-detects media/audio/data files by extension
  • shows each image fully visible for 6 seconds by default, not counting crossfades
  • keeps the first image longer so the first transition starts after 2x image duration, 12 seconds by default
  • includes video clips inline
  • crossfades between media items over 3 seconds by default
  • loops/trims music to match the video
  • 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 2 seconds and out over 10 seconds by default
  • shows opening title/stamps immediately on a black background while audio fades in, then fades the first image/video in over 1.5 seconds by default
  • fades video out over 1.5 seconds by default
  • overlays the opening title for the first 6 seconds: large title, then date/location on one line
  • 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
  • uses safe ASCII only for generated filenames, so special letters become readable equivalents like Č -> C, å -> a, ø -> o
  • embeds MP4 metadata: title, date, location/place, QuickTime location name, artist, comment, and creation_time
  • writes an MP4 file named like YYYYMMDD_title.mp4 in the current directory by default; use --out-dir DIR to choose another output directory
  • takes YYYYMMDD from the first picture/video metadata, falling back to file modification date
  • keeps console output minimal during rendering and writes detailed ffmpeg output to timestamped logs in .logs/ inside the input directory
  • uses local ffmpeg/ffmpeg and ffmpeg/ffprobe when present, otherwise system tools

Notes

This is an early version. The intended workflow is deliberately simple: dump files into one folder and run the script.