Make Your Own Music Montage in BASH

Use bash to make your own music montage

Make Your Own Music Montage in BASH

I made a music montage last week for my Code Monkey podcast. Some folks have asked me to share the script I used.

You'll need a fairly normal linux system with bash and ffmpeg installed.

Put Copies of Your Slides Into a Temp Directory

These files will be renamed, overwritten, etc! So only use copies of your pictures!

Prepare Them For Conversion

# Change file format to png if you can
# ====================================
GLOBIGNORE="*.png"
for f in *; do 
    ffmpeg -i "$f" "${f%.*}.png"
done
unset GLOBIGNORE;


# Delete everything not a png
# ===========================
GLOBIGNORE="*.png"
rm *
unset GLOBIGNORE;

Number Them In The Order You Want Them To Appear

# Randomly numerize the files
# ===========================
((ls) | shuf) | grep -v "/$" | cat -n | while read n f; do mv -n "${f}" "$(printf "%04d" $n).png" --; done

Here I just autonumbered a randomized list, ie, ls | shuf

You can also tweak the ls command to sort by date, size, etc

Figure Out How Long The mp3 File Is

# Get the number of seconds in the target mp3 file
# For our sample here, we're using the song 'code monkey', ie ../CodeMonkey.mp3 which is in the parent directory
# ===================================================
timegoal=$(ffmpeg -i "../CodeMonkey.mp3" 2>&1 | grep "Duration" | grep -o " [0-9:.]*, " | head -n1 | tr ',' ' ' | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')

Note that "CodeMonkey.mp3" is my target background music file. Find one you like

Hook It All Up

# Attach mp3 to video of slides
# This has an output file of out.mp4 in the parent directory
# You need a target number of frames for each slide
# It's 1/x where x is the number of seconds for each slide
# ie, 1/2 means 1 slide every 2 seconds
# likewise 2 (like our example) means 2 slides per second

ffmpeg -r 2 -t $timegoal -i "%04d.png" -i ../CodeMonkey.mp3 -c:v libx264 -pix_fmt yuv420p -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2"  ../out.mp4

That's It!

You should be able to copy/pase each of these sections into your linux shell.

None of this is tested for production!

Optional

You may want to include an opening and/or closing section.

That's beyond the scope of this tutorial, but some video platforms will allow you to add this when you upload the video. There's also plenty of examples using bash on the net. Let me know if you'd like me to show this as well

You can also change the frame size/codec/etc of the resulting video. That's also outside the scope of this tutorial

All Together Now

You can also find this on my bash-snippets repo on github

#!/bin/bash

# Takes path to mp3 file, framerate, and name of output

# Initialize variables
MP3FILE="${1:-mymp3.mp3}"
FRAMERATE="${2:-2}"
MONTAGENAME="${3:-mymontage.mp4}"

# Change file format to png if you can
# ====================================
GLOBIGNORE="*.png"
for f in *; do 
    ffmpeg -i "$f" "${f%.*}.png"
done
unset GLOBIGNORE;


# Delete everything not a png
# ===========================
GLOBIGNORE="*.png"
rm *
unset GLOBIGNORE;


# Randomly numerize the files
# ===========================
((ls) | shuf) | grep -v "/$" | cat -n | while read n f; do mv -n "${f}" "$(printf "%04d" $n).png" --; done


# Get the number of seconds in the target mp3 file
# For our sample here, we're using the song 'code monkey', ie ../CodeMonkey.mp3 which is in the parent directory
# ===================================================
timegoal=$(ffmpeg -i "$MP3FILE" 2>&1 | grep "Duration" | grep -o " [0-9:.]*, " | head -n1 | tr ',' ' ' | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')

# Attach mp3 to video of slides
# This has an output file of out.mp4 in the parent directory
# You need a target number of frames for each slide
# It's 1/x where x is the number of seconds for each slide
# ie, 1/2 means 1 slide every 2 seconds
# likewise 2 (like our example) means 2 slides per second

ffmpeg -r "$FRAMERATE" -t $timegoal -i "%04d.png" -i "$MP3FILE" -c:v libx264 -pix_fmt yuv420p -vf "scale=1280:720, pad=ceil(iw/2)*2:ceil(ih/2)*2" -preset slow -crf 18  "$MONTAGENAME"