Image Montage using ffmpeg
See bash script below, adapted from here
#!/usr/bin/env bash
set -euo pipefail
SECONDS_PER_IMAGE=5
TMP_DIR=".tmp"
#!/usr/bin/env bash
set -euo pipefail
# Configuration
SECONDS_PER_IMAGE=5
AUDIO_FILE="${1:-}" # First argument: path to audio file (optional)
TMP_DIR=".tmp"
FILES_LIST="$TMP_DIR/.files"
OUTPUT="output.mp4"
VF="scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,fps=30"
# Clean up previous outputs
rm -r "$TMP_DIR" "$OUTPUT" || true
mkdir "$TMP_DIR"
# Convert all videos to standard format
find . -maxdepth 1 -regex '.*\(mp4\|MP4\)' | while read i; do
ffmpeg -y -i "$i" -vf "$VF" -ar 44100 "$TMP_DIR/$i" < /dev/null
echo "file '$i'" >> "$FILES_LIST"
done
# Convert all images to videos
find . -maxdepth 1 -regex '.*\(jpe?g\|png\|JPG\|bmp\)' | while read i; do
ffmpeg -y -loop 1 -i "$i" -t "$SECONDS_PER_IMAGE" -vf "$VF" \
"$TMP_DIR/$i.mp4" < /dev/null
echo "file '$i.mp4'" >> "$FILES_LIST"
done
# Shuffle the media files
shuf -o "$FILES_LIST" "$FILES_LIST"
# Find first video with audio and move to top
first=
while read f; do
fn="$(echo "$f" | sed "s/^file '//; s/'$//")"
if ffprobe "$fn" 2>&1 | grep -q 'Audio:'; then
first="$fn"
fi
done < "$FILES_LIST"
if [ -n "$first" ]; then
{ echo "file '$first'"; cat "$FILES_LIST"; } | awk '!seen[$0]++' > files
mv files "$FILES_LIST"
fi
# Concatenate videos and optionally add audio overlay
if [ -n "$AUDIO_FILE" ]; then
if [ ! -f "$AUDIO_FILE" ]; then
echo "Error: Audio file '$AUDIO_FILE' not found"
exit 1
fi
echo "Adding audio overlay from: $AUDIO_FILE"
# First, create the concatenated video without final audio
TMP_VIDEO="$TMP_DIR/concat_video.mp4"
ffmpeg -y -f concat -safe 0 -i "$FILES_LIST" -c copy "$TMP_VIDEO"
# Then overlay the audio track
# -stream_loop -1: loop audio if shorter than video
# -shortest: end output when shortest input ends (video duration)
# -map 0:v:0: take video from first input (concatenated video)
# -map 1:a:0: take audio from second input (audio file)
# -c:v copy: copy video without re-encoding
# -c:a aac: encode audio as AAC
ffmpeg -y -stream_loop -1 -i "$TMP_VIDEO" -i "$AUDIO_FILE" \
-map 0:v:0 -map 1:a:0 -shortest -c:v copy -c:a aac -b:a 192k "$OUTPUT"
echo "Montage created with audio overlay: $OUTPUT"
else
# Original behavior: concatenate without audio overlay
ffmpeg -y -f concat -safe 0 -i "$FILES_LIST" "$OUTPUT"
echo "Montage created: $OUTPUT"
fi
echo "Done!"