Last updated: October 2026 · Reading time: about 9 minutes
brew install ffmpeg. Put your .aiff files and a cover.jpg in the same folder. Then run the command below — it converts every .aiff file in the folder to .mp4, with the JPEG as a static video frame and the AIFF as the audio track. It uses H.264 video, AAC audio at 192k, and -shortest so each video ends when its audio ends rather than looping forever.
That's the whole thing. The rest of this post explains what each flag does, why some of it is not optional, and what I had to fix when the AI's first version didn't work.
What this guide covers
The problem: AIFF files that needed to become MP4
I had a folder of audio recordings in AIFF format — the uncompressed audio format Apple uses natively on macOS. They needed to become MP4 videos, because the platforms I was distributing them on accept video but not audio-only files.
The obvious approach would be an app. But most audio-to-video converters:
- Process one file at a time, which is fine for three files and unbearable for two hundred
- Add watermarks or branding
- Re-encode at whatever bitrate they choose, not what you choose
- Charge a subscription for a task FFmpeg does in one command
FFmpeg is the command-line tool that every one of those apps is built on top of. It's free, it's fast, and it does exactly what you tell it — which is the point, but also the problem: it does *only* what you tell it, and the syntax is unforgiving.
So I asked an AI to write the command for me.
Prerequisites: FFmpeg and Homebrew
FFmpeg does not ship with macOS. You install it through Homebrew, the Mac package manager, which also does not ship with macOS — so if you don't have Homebrew yet, install that first.
Check whether Homebrew is installed:
If that returns a version number, you're set. If it returns "command not found," install Homebrew with the one-line script from brew.sh.
Install FFmpeg:
This takes a few minutes — FFmpeg has a large dependency tree. When it finishes, confirm the install:
You should see a version number and a long list of supported codecs. If you get "command not found" after a successful install, your shell's PATH doesn't include Homebrew's bin directory. On Apple Silicon Macs that's /opt/homebrew/bin; on Intel it's /usr/local/bin. The install script usually handles this, but it's worth knowing where to look if it doesn't.
libx264 and aac. If it doesn't, your FFmpeg build is missing the codecs this command needs, and you'll get a cryptic error at the encoding stage rather than the install stage.
Setting up the folder
FFmpeg works on whatever is in the current directory, so the simplest setup is to put everything in one place.
- Create a folder on your Desktop called
audio-to-mp4. - Copy all your
.aifffiles into it. - Add a single image file named exactly
cover.jpg— this becomes the video frame for every output file. A 1280×720 JPEG is a good size; larger covers produce larger output files without improving quality.
The folder should look like this:
Then navigate to it in Terminal:
Everything else in the command is relative to this folder, so getting this step right matters. Run ls to confirm you're in the right place — you should see your .aiff files and cover.jpg listed.
The command that did the job
Here is the final version, ready to paste into Terminal once you're in the folder:
The structure is a shell loop wrapped around a single FFmpeg invocation. The loop iterates over every file matching *.aiff and runs FFmpeg once per file, producing an MP4 with the same base name.
The ${f%.*}.mp4 expression at the end is doing two things: it strips the .aiff extension from $f (the % means "remove the shortest matching suffix"), then appends .mp4. So episode-01.aiff becomes episode-01.mp4. Nothing gets overwritten, and you keep the originals.
What each flag actually does
This is the part the AI skipped, and it's the part that matters when something goes wrong.
| Flag | What it does | What happens without it |
|---|---|---|
-loop 1 |
Loops the image input infinitely so it can serve as the video frame for the full audio duration | The video ends after 1 frame and the audio plays over a frozen or black screen |
-framerate 1 |
Sets the input image to 1 frame per second — minimal processing for a static image | Higher framerates encode identical frames repeatedly, bloating the file for no benefit |
-i cover.jpg -i "$f" |
Two inputs: image first (index 0), audio second (index 1) | Without two inputs there's nothing to combine |
-map 0:v:0 |
Takes the video stream from input 0 (the image) | FFmpeg guesses, and sometimes guesses wrong |
-map 1:a:0 |
Takes the audio stream from input 1 (the AIFF) | Same as above — explicit is better than implicit |
-c:v libx264 |
H.264 video codec — the most widely compatible option | Default codec may not play in QuickTime or on social platforms |
-tune stillimage |
Optimises the encoder for static images, reducing bitrate significantly | Larger file, no visible quality improvement |
-pix_fmt yuv420p |
Standard pixel format for maximum compatibility | Videos may not play in QuickTime, iOS, or on social platforms — this is the single most common missing flag |
-c:a aac |
AAC audio codec — standard for MP4 | Default may be a codec MP4 players won't accept |
-b:a 192k |
192 kbps audio bitrate | Default AAC bitrate is often lower; fine for speech, lossy for anything else |
-shortest |
Ends the output when the shortest stream ends — i.e. when the audio finishes | Output never ends because the image loops forever — you'd have to stop it manually |
-movflags +faststart |
Moves the MP4 metadata to the start of the file so it can begin playing before fully downloading | Video won't start streaming until the whole file has downloaded — bad for web players, slow for anything shared online |
"${f%.*}.mp4" |
Output filename: original name with .aiff stripped, .mp4 appended |
Without a named output, FFmpeg writes a file called output.mp4 — each loop overwrites the last one |
Three of these flags (-shortest, -pix_fmt yuv420p, and -movflags +faststart) are the ones people miss most often, and they're the ones that cause the most confusing failures. More on that below.
What the AI got right — and what it missed
The command above is the final version. It's not the first version the AI produced, and the difference between the two is the useful part of this post.
What the AI got right on the first pass: the overall structure. It recognised immediately that the task was a loop around an FFmpeg call, that the image needed -loop 1, that the two inputs needed explicit mapping, and that the output filename needed the extension substituted. That's the hard part of the problem, and it got it right.
What it missed:
-pix_fmt yuv420p— the first version omitted this. The resulting files played fine in VLC but showed a black screen in QuickTime and were rejected by one of the social platforms I was uploading to. This is the classic FFmpeg compatibility trap, and it's not obvious from the error message (there isn't one — the file just doesn't display).-movflags +faststart— also omitted. Not a hard failure, but the files were slower to preview and would have been slow for anyone streaming them.-tune stillimage— added after I noticed the output files were much larger than expected. Static video shouldn't need 5MB per minute, and this flag brought them down substantially.- Error handling — the loop as written just runs FFmpeg on every file without checking whether each one succeeded. If one file is corrupt or has an unsupported codec, the loop keeps going and you notice at the end that one output is missing. Not a problem for a small batch, worth adding a check for a large one.
What this taught me about prompting for shell commands: AI is reliably good at the *shape* of a command — the structure, the loop, the input/output pattern. It's less reliable at the *compatibility flags*, which are the ones that are hard to reason about and easy to omit. Those are exactly the flags you should look up in the man page rather than trust the AI to include.
Common errors and fixes
| Error | Cause | Fix |
|---|---|---|
ffmpeg: command not found |
FFmpeg not installed, or not in PATH | Run brew install ffmpeg. If already installed, check your PATH for /opt/homebrew/bin |
No such file or directory: cover.jpg |
The cover image isn't in the current folder, or is named differently | Run ls to confirm. Rename the file to exactly cover.jpg — case-sensitive |
| The loop runs, but no output files appear | No .aiff files matched the glob — either none exist, or they have a different extension case |
Run ls *.aiff to confirm matches. If files are .AIFF uppercase, use *.AIFF in the loop |
| Output plays in VLC but shows a black screen in QuickTime | Missing -pix_fmt yuv420p |
Add -pix_fmt yuv420p to the command — this is the most common FFmpeg compatibility issue |
| Video never ends | Missing -shortest |
Add -shortest — without it, the looping image keeps the output running indefinitely |
| Filename errors on files with spaces | Unquoted variable expansion | The quotes around "$f" and "${f%.*}.mp4" handle this — do not remove them |
| Audio is missing from the output | The AIFF file's codec isn't supported by your FFmpeg build, or the -map flags point at the wrong stream |
Run ffmpeg -i input.aiff on a single file to see what streams FFmpeg detects |
| Output files are 5–10MB per minute of audio | Missing -tune stillimage, or the cover image is very large |
Add -tune stillimage and resize the cover to 1280×720 before running the batch |
How to verify the output
After the loop finishes, three checks before you distribute anything.
1. Count the outputs against the inputs.
The two numbers should match. If they don't, one or more files failed silently — check the Terminal output for FFmpeg errors on the missing ones.
2. Play one file end-to-end. Open one of the output MP4s in QuickTime (not VLC — VLC is more forgiving and will hide compatibility problems). If it plays with video and audio in QuickTime, it will play almost anywhere.
3. Check the technical spec of one file:
You should see codec_name=h264 for video, codec_name=aac for audio, the expected dimensions (matching your cover.jpg), and a duration matching the original audio file.
If all three checks pass, the batch is done. If the third check shows the wrong codecs or dimensions, something in the command didn't apply as expected — go back and check that the flags are exactly as written.
Frequently asked questions
How do I convert AIFF to MP4 on Mac without installing anything?
You can use QuickTime Player's File → Export for a single file, but it doesn't support batch conversion and won't produce MP4 with a static image without additional software. FFmpeg is the practical tool for batch conversion, and it requires one Homebrew install.
Why does my MP4 show a black screen in QuickTime but play fine in VLC?
The output is missing -pix_fmt yuv420p. QuickTime and iOS require the yuv420p pixel format for H.264 playback; VLC is more forgiving and will play other formats. Add -pix_fmt yuv420p to the command and re-run.
Can I use a different image format instead of cover.jpg?
Yes. FFmpeg accepts PNG, BMP, WEBP and most image formats. Change the filename in the command to match — for example -i cover.png. JPEG is recommended for the smallest file size; PNG will work but produces larger output for the same visual result.
How do I keep the audio quality lossless?
Change -c:a aac -b:a 192k to -c:a alac for Apple Lossless in MP4, or -c:a copy if the source AIFF contains audio that's already in a compatible format (rare — AIFF is usually uncompressed PCM). For most distribution purposes, AAC at 192k is transparent for speech and fine for podcast use.
How do I add a fade-in or fade-out?
Add -af "afade=t=in:st=0:d=2,afade=t=out:st=118:d=2" for a 2-second fade at the start and end (adjusting the st=118 to your file's duration minus 2). This is a case where you should run FFmpeg on one file first to get the timing right before running the batch.
What if my files are .wav or .mp3 instead of .aiff?
Change *.aiff to *.wav or *.mp3 in the loop. Everything else in the command works unchanged — FFmpeg handles the input format automatically. If you have mixed formats in one folder, run the loop once per extension.
Does this work on Linux or Windows?
The FFmpeg command works on Linux unchanged. On Windows, the syntax is the same but you'd run it in PowerShell or Git Bash rather than the default Command Prompt. The Homebrew install step is Mac-only — use your system package manager instead (apt install ffmpeg on Debian/Ubuntu, choco install ffmpeg on Windows).
Why does the output file have a video track when all I wanted was the audio?
Because that's the point — the platforms you're distributing on require a video file. An MP4 with a static image and an audio track is technically a video file. If a platform accepted audio-only files, you wouldn't need this conversion at all.
Related reading on Dedon Tech: AI in insurance: what it actually does to your premium and claim · who owns the AI agent in your law firm · full site map

0 Comments: