Slideshow via FFmpeg

3 days ago 13

Since I’m known in our family (and in parts of my community) as someone with a few technology and media skills, from time to time I’m asked to help out with slideshows at special events. In the past this has included funerals for both of my spouse’s parents, as well as my mom’s funeral. Back […]

Since I’m known in our family (and in parts of my community) as someone with a few technology and media skills, from time to time I’m asked to help out with slideshows at special events. In the past this has included funerals for both of my spouse’s parents, as well as my mom’s funeral. Back in November I created a video slideshow for the 40th anniversary of the Mint Hill Historical Society, and burned them to DVD for offline playback in their venue. This weekend (in May 2026) our family needed a slideshow for a happy occasion: The graduation party of my nephew, but the need was the same: “Can you please create a slideshow with a bunch of photos we’ve collected?”

In this case, I had over 1400 photos to work with. These included 1200 photos contributed by the parents of the two high school graduates we were celebrating, and about 200 pictures I had of our nephew, identified via Google Photos’ AI / facial recognition features.

My task was to create a slideshow featuring both graduates, without music. Rather than create this slideshow with software like iMovie, Canva, or Descript, I opted to use the MacOS terminal and FFmpeg:

a free and open-source software project consisting of a suite of libraries and programs for handling video, audio, and other multimedia files and streams.

Since my Linux and command line skills are very limited, the only reason I was able to do this was my growing relationship and skill set with Claude AI. I’ve been using Claude with more regularity to diagnose and fix technical issues with Apache and WordPress on the VPS (virtual private server) I pay for to host many of my various web properties and projects, and I predicted (correctly) that Claude could help me work efficiently with over 1400 photos for a high school graduation celebration slideshow. In this post, I’ll highlight some of the things Claude helped me do relatively quickly using FFmpeg and some other Homebrew open source software programs, all via the MacOS Terminal.

A terminal window displays the final output of an FFmpeg video processing task, showing that the "Slideshow Random" process has completed. The console log highlights several warnings regarding deprecated pixel formats and reconfigurations of the filter graph due to changing video resolutions. The summary statistics indicate a total file size of approximately 2.3 GB for the generated MP4 output after about five minutes of processing. The prompt returns to the command line, ready for further input from the user at wfryer@Mac.
MacOS Terminal Screenshot (CC BY 4.0) by Wesley Fryer

The first step was installing the tools I would need. I already had Homebrew installed on my MacBook (which Claude has previously helped me set up for other projects), so adding the additional utilities was a single command each. The workhorses for this project ended up being exiftool (for reading and writing the metadata embedded in image files), jhead (a small utility specifically designed for handling JPEG orientation and EXIF metadata), and FFmpeg itself.

brew install exiftool ffmpeg jhead

A handful of the photos contributed to the slideshow were actually short video clips (MP4 files), and I wanted to grab a single representative frame from each as a still image. Rather than open each video and screenshot a frame manually, Claude wrote a short loop that walked through every MP4 in a folder, calculated the midpoint of each video, and extracted that frame as a JPG. It also copied the original video’s creation date onto the new JPG so the still would sort chronologically alongside the other photos:

for f in *.MP4; do
  duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f")
  midpoint=$(echo "$duration / 2" | bc -l)
  output="${f%.*}.jpg"
  ffmpeg -ss "$midpoint" -i "$f" -frames:v 1 -q:v 2 "$output" -y 2>/dev/null
  date_taken=$(exiftool -CreateDate -d "%Y:%m:%d %H:%M:%S" -s -s -s "$f")
  exiftool -DateTimeOriginal="$date_taken" -overwrite_original "$output" >/dev/null
done

With more than 50 short MP4s in the mix, this saved me a serious chunk of tedious manual work.

Next came the renaming problem. Photos from different sources had wildly inconsistent filenames (IMG_1234.JPG from iPhone, “sydney – 482.jpeg” from Google Photos exports, longer alphanumeric strings from shared albums, and so on). For the slideshow to play in chronological order, I needed filenames that would naturally sort that way. Every photo had a “DateTimeOriginal” field buried in its EXIF metadata that recorded when the photo was actually taken. With one exiftool command, every file in a folder was renamed based on that field:

exiftool '-FileName<DateTimeOriginal' -d '%Y-%m-%d_%H-%M-%S%%-c.%%e' .

After running this, my folder contained files like 2018-05-25_12-31-28.jpeg and 2023-11-04_10-55-23.jpg, all sorted into proper chronological order by default just by alphabetizing the filenames. Eight hundred-plus photos, one command, a few seconds of execution.

A small subset of the photos (about 46 out of 854) had no EXIF date-taken metadata, usually because they had been forwarded through text messages or stripped during some earlier processing. Rather than try to date them by hand, I had Claude move them into a sibling folder so they would not pollute the chronological sort, and we proceeded with the rest:

exiftool -if 'not $DateTimeOriginal' '-Directory=../undated' . 2>/dev/null

The most frustrating discovery of the whole project came on the first slideshow render: many of the photos appeared sideways or upside down in the final video. The reason turned out to be one of those quirks of digital photography that I had never thought about. iPhones and most modern cameras store the rotation of an image as a small piece of EXIF metadata rather than actually rotating the pixels. When FFmpeg reads the raw image, it ignores that rotation tag, so portrait photos arrive in the video on their side. The fix was a single jhead command that losslessly rotates the actual pixel data of every photo to match its EXIF rotation flag:

jhead -autorot *.jpg

With the images now in chronological order, properly oriented, and uniformly named, the actual slideshow render was almost anticlimactic. One FFmpeg command produced a 1080p MP4 with each image displayed for five seconds, properly letterboxed in black for any portrait-orientation photos, and encoded with H.264 for broad device compatibility:

ffmpeg -framerate 1/5 -pattern_type glob -i '*.jpg' \
  -c:v libx264 -r 30 -pix_fmt yuv420p \
  -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black" \
  slideshow.mp4

About eight minutes of rendering on my MacBook, and an hour-plus video slideshow was ready to play.

After previewing the result, I decided I wanted a second version with more visual variety. Rather than a strict chronological order, I wanted the slideshow to alternate between solo photos of my nephew, solo photos of his close friend, and shots of the two of them together. I had already used Google Photos’ facial recognition to build separate albums for each (482 photos of just my nephew, 361 of just his friend), and Claude wrote me a custom Python script that shuffled both folders into random order, then interleaved them in a 4:3 ratio (matching the source counts) so the final slideshow used nearly every available photo with balanced representation. The script copied the shuffled, interleaved photos into a new output folder with sequential numbering, ready to feed back into the same FFmpeg command above.

What strikes me about this whole experience is not just that I was able to complete a project that would have been daunting (and probably abandoned) without AI assistance, but how the conversation FELT. I would describe what I wanted in plain English, Claude would suggest a command or write a small script, I would run it, and we would iterate based on what happened. When something failed (and several things did fail along the way) I could paste the error message back into the chat and we would debug together. This is a workflow that simply did not exist for non-experts even three years ago.

There is one more practical wrinkle worth mentioning. The venue for the graduation party was outdoors, and although my brother-in-law has a perfectly good Plex server running at the house, the outdoor wifi signal was not strong enough to stream the slideshow reliably to the Apple TV. After watching it buffer one too many times, I ended up plugging an HDMI adapter into my iPad, loading the MP4 directly from local storage, and playing it on a loop with the iPad app’s repeat function. Sometimes the boring solution is the right one. The slideshow looked great, the kids loved it, and not a single guest asked me whether it was rendered with FFmpeg.

If you are an educator, a family member designated as “the tech person” at gatherings, or simply someone with a creative project that involves more files than you can comfortably handle by hand, I cannot recommend strongly enough that you start experimenting with AI as a Terminal partner. The tools have always been available. What is new is having a patient, knowledgeable collaborator who can meet you wherever your own skills happen to be.


AI Attribution: I used Claude AI throughout the project described in this post, both to design and write the Terminal commands and Python script, and also to debug the various small problems that came up along the way. I also used Claude to help draft this blog post based on our chat transcript, and I edited and revised the final result before publishing. Specifically, I wrote the first 4 introductory paragraphs, then asked Claude to continue matching my tone and style, highlighting in chronological order the different ways I used Terminal commands to create this slideshow.

Check out more road signs from my journey of learning with AI on ai.wesfryer.com.


View Entire Post

Read Entire Article