Information Technology Grimoire

Version .0.0.1

IT Notes from various projects because I forget, and hopefully they help you too.

Bash Make mp4 with ffmpeg

As part of learning how to use ffmpeg to make mp4 videos, I wrote this script to stitch a bunch of files that started with ss*.png into an mp3 called ss.mp3. This was the proof of concept for the more advanced version that creates drag and drop thumbnails.

One Liner Command

ffmpeg -loop 1 -t 2 -i 1.png -loop 1 -t 2 -i 2.png -loop 1 -t 2 -i 3.png -i s.mp3 -filter_complex "[0:v]scale=1960:2472,setdar=1[v0];[1:v]scale=1960:2472,setdar=1[v1];[2:v]scale=1960:2472,setdar=1[v2];[v0][v1][v2]concat=n=3:v=1:a=0[v]" -map "[v]" -map 3:a -c:v libx264 -pix_fmt yuv420p -c:a aac -shortest output.mp4

Once we saw the ffmpeg command worked, then we made a script for multiple images, however, it puts the images every 2 seconds instead of at the time I wanted them. Still this was a process in learning. The final script is in python.

#!/usr/bin/bash

# Function to print usage information
usage() {
    echo "Usage: $0 -i 'image_glob' -a audio_file -o output_file"
    echo "Example: $0 -i 'ss*.png' -a ss.mp3 -o output.mp4"
    exit 1
}

# Parse command line arguments
while getopts "i:a:o:" opt; do
    case ${opt} in
        i)
            image_glob=${OPTARG}
            ;;
        a)
            audio_file=${OPTARG}
            ;;
        o)
            output_file=${OPTARG}
            ;;
        *)
            usage
            ;;
    esac
done

# Check if required arguments are provided
if [[ -z "$image_glob" || -z "$audio_file" || -z "$output_file" ]]; then
    usage
fi

# Collect images from the glob pattern
images=($(ls $image_glob))

# Check if any images are found
if [ ${#images[@]} -eq 0 ]; then
    echo "No images found matching the glob pattern."
    exit 1
fi

# Construct the FFmpeg input arguments and filter_complex parts
input_args=""
filter_complex=""
map_args=""
for i in "${!images[@]}"; do
    input_args+="-loop 1 -t 2 -i ${images[$i]} "
    filter_complex+="[$i:v]scale=1960:2472,setdar=1[v$i];"
    map_args+="-map $i "
done

# Append the final part of the filter_complex
concat_inputs=$(printf "[v%d]" $(seq 0 $((${#images[@]}-1))))
filter_complex+="${concat_inputs}concat=n=${#images[@]}:v=1:a=0[v]"

# Construct the complete FFmpeg command
ffmpeg_command="ffmpeg $input_args -i $audio_file -filter_complex \"$filter_complex\" -map \"[v]\" -map ${#images[@]}:a -c:v libx264 -pix_fmt yuv420p -c:a aac -shortest $output_file"

# Print the constructed command for debugging
echo "Constructed FFmpeg command:"
echo $ffmpeg_command

# Execute the FFmpeg command
eval $ffmpeg_command