ivan
ivan

Reputation: 6322

Can FFmpeg encode multiple files at once?

I'm working on a shell script to encode the audio files in a given directory and output .flac files to another directory. My proof of concept loops through each file one by one and runs it through ffmpeg. I suspect there's a way to pass the whole list of input files to ffmpeg in a single command, but haven't been able to figure it out.

My current version looks like this:

#!/bin/sh

encode_album() {
    local artist=$1; shift
    local album=$1; shift
    local in_dir="${HOME}/Music/raw-imports/${artist}/${album}"
    local out_dir="${HOME}/Music/library/${artist}/${album}"

    mkdir -p "${out_dir}"

    find "${in_dir}" -type f -name *.aiff | while IFS= read -r in_file; do
        local track_name=$(basename "${in_file}" .aiff)
        local out_file="${out_dir}/${track_name}.flac"

        encode_track "${in_file}" "${out_file}"
    done
}

encode_track() {
    local in_file=$1; shift
    local out_file=$1; shift

    </dev/null ffmpeg -i "${in_file}" -codec:a flac "${out_file}"
}

encode_album "Rolf Lislevand" "La Mascarade"

This works, but do I need to feed these files into ffmpeg one by one, or is it capable of accepting a batch of files and processing them?

Upvotes: 1

Views: 13049

Answers (3)

clone206
clone206

Reputation: 89

In case it helps, you could use or modify the following script. ffmpeg's built-in batch syntax can get a bit unwieldy for lots of files, so I don't see much benefit in trying to use it rather than looping through one by one, and this script does some things that can't be done by default in ffmpeg, including automatic recursion and directory structure recreation in the target location.

Full disclosure, I'm the author.

https://github.com/clone206/ffmpeg-batch-audio-resampler

Code at time of posting:

#!/bin/bash

########################
# CONSTANTS
########################
# The requested output sample rate.
MAX_SR=$1
# The specified output file extension
OUTFILE_EXT=$2
# The specified volume boost for output file
VOL_BOOST=$3
# Don't go below this sample rate:
MIN_SR=44100
# The directory to output converted files to. Use trailing slash. Needs manual escaping (not quotes)
# if this is customized and there are special chars (spaces, etc) in the pathname supplied, eg: 
# OUT_DIR=~/Music/iTunes/iTunes\ Media/Automatically\ Add\ to\ iTunes.localized/
OUT_DIR=~/batch_resampled/

########################
# UTILITY FUNCTIONS
########################
# Send string to stderr
err() {
    >&2 echo "$1"
}

# Display usage info
print_usage() {
    err ""
    err "USAGE: "$(basename $0)" <sample_rate> <outfile_extension> [vol_adjust_db]"
    err ""
    err "Converts all supported audio files in the current directory to the format corresponding "
    err "to the given file extension (don't include the dot), at the speciied sample rate (in Hz). "
    err "To specify a maximum output sample rate, where any input file of a greater rate gets downsampled "
    err "to the nearest even multiple of either 44100 or 48000, add an 'm' to the end of the number, "
    err "eg. '96000m'. If an input file has a sample rate that is already below this, it will not be upsampled. "
    err ""
    err "An optional volume adjust (in dB) can be given (positive number for boost, "
    err "negative for cut). "
    err ""
    err "Renames file basenames on conversion and doesn't re-convert already "
    err "converted files on subsequent runs."
    err ""
    err "Supported infile types: flac,dsf,dff,wav,aiff,m4a,mp3"
    err "Supported outfile types: flac,wav,aiff,m4a(alac),mp3"
    err ""
}

########################
# ARGS CHECKING
########################
if [[ -z "$OUTFILE_EXT" || $OUTFILE_EXT == "dsf" || $OUTFILE_EXT == "dff" ]]
then
    print_usage
    exit 1
fi

########################
# GLOBALS
########################
# Added to the end of the file basename
suffix=""
# Args to pass to the resampler
filter_args=""
pre_filter_args=""
# Args for the output file, like codec
out_args=""
# Volume filter level. Setting to 0dB explicitly seems to prevent replaygain-related clipping.
# Also comes in handy for boosting DSD file levels on conversion to PCM.
vol_level=""
# The sample rate of each input file
infile_sr=""
# The sample format of each input file
infile_sfmt=""
# Output file name
output_file=""
# Level of any errors encountered.
error_level=0
# Lowest factor of input file
lowest_factor=0
# Destination sample rate of a given output file
dest_sr=$MAX_SR

########################
# FUNCTIONS
########################
# Recalculate sample rate to nearest even multiple
# of 44.1/48K, depending on the input file's sample rate and the max sample rate given
recalc_sr() {
    infile_sr=$1
    lowest_factor=$2
    max_sr=$(echo $MAX_SR | sed 's/m$//')
    dest_sr=0

    # Make sure our output sample rate is a multiple of either 44.1K or 48K
    if [[ ! $(($max_sr % 44100)) -eq 0 && ! $(($max_sr % 48000)) -eq 0 ]]
    then
        err "ERROR: Only even multiples of 44100 and 48000 are allowed to be specified for maximum output sample rates!"
        return 2
    fi

    # If the input is already at or below the specified max...
    if [[ $infile_sr -le $max_sr ]]
    then
        # Nothing to calculate. Keep sr the same
        dest_sr=$infile_sr

        err "INFO: Input sample rate <= output rate. Will not be upsampled."
        echo "$dest_sr"
        return 0
    # If user specified the min sr as the max
    elif [[ $max_sr -eq $MIN_SR ]]
    then
        # Downsample even if not even multiple
        dest_sr=$max_sr

        err "INFO: No even multiple available below minimum rate of ${MIN_SR}, so downsampling to ${MIN_SR}."
        echo "$dest_sr"
        return 0
    else
        # Find closest even multiple below the max. Takes advantage of the dropping of decimals by bash for easy math.
        dest_sr=$(( ($max_sr/$lowest_factor)*$lowest_factor ))

        err "INFO: Downsampling to even multiple of ${lowest_factor}"
        echo "$dest_sr"
        return 0
    fi
}

# Takes input file sample rate as the only param.
# Determines whether this is an even multiple or 44.1K or 48K
# Delegates to the recalc_sr function and passes through the results via echo's
find_lowest_factor() {
    infile_sr=$1

    if [[ $infile_sr && $(($infile_sr % 44100)) -eq 0 ]]
    then
        echo "44100"
        return 0
    elif [[ $infile_sr && $(($infile_sr % 48000)) -eq 0 ]]
    then
        echo "48000"
        return 0
    else
        err "ERROR: Only multiples of 44100 and 48000 are allowed for input sample rates when in maximum mode!"
        return 1
    fi
}

# Takes an input file as the only argument and converts it
# based on certain conditions, or returns an error if necessary
conv() {
    # The input file with "./" stripped from the beginning
    item=${1#./}

    err "item: $item"

    # Get some specifics about this input file
    # To see all info about a particular input file on the command line, type ffprobe -v error -show_streams <file>
    infile_sfmt=$(ffprobe -v error -select_streams a:0 -show_entries stream=sample_fmt -of default=noprint_wrappers=1:nokey=1 "$item" \
        | xargs echo -n)
    infile_sr=$(ffprobe -v error -select_streams a:0 -show_entries stream=sample_rate -of default=noprint_wrappers=1:nokey=1 "$item" \
        | xargs echo -n)

    # If user appended an "m" for "maximum" to the end of
    # the sample rate param, find nearest even multiple of
    # this infile's sample rate
    if [[ $MAX_SR =~ m$ ]]
    then
        err "INFO: Maximum sample rate specified for output. Recalculating destination sample rate"

        # Get lowest factor from infile sr and skip this file on error
        if ! lowest_factor=$(find_lowest_factor $infile_sr)
        then
            error_level=1
            err "SKIPPING ${item}..."
            return 0
        fi

        # Get destination sample rate for this file. Stop all conversion on error
        if ! dest_sr=$(recalc_sr $infile_sr $lowest_factor)
        then
            error_level=2
            err "FATAL"
            return 1
        fi
    fi

    # Append a marker to the end of the output filename indicating new sample rate
    suffix="_ff"$(($dest_sr / 1000))"k"
    filter_args="aresample=resampler=soxr:precision=32:dither_method=triangular:osr=${dest_sr}"

    # Double pass for signed to signed resampling, as ffmpeg has some kind of problem
    # with this while setting the output sample rate of a signed audio codec type w sox. 
    # This way it gets double dithered with sox, which seems to be the best way of 
    # avoiding the bug, which causes pops in the converted audio.
    if [[ $infile_sfmt =~ ^s[0-9]+ && ($item =~ .flac$ || $item =~ .m[^\.]+$) ]]
    then
        pre_filter_args="aresample=resampler=soxr:precision=32:dither_method=triangular,"
    fi

    # Set the right output codec/sfmt for m4a, wav, aif, flac
    if [[ $OUTFILE_EXT == "aiff" ]]
    then
        out_args="-acodec pcm_${infile_sfmt}be"
    elif [[ $OUTFILE_EXT == "wav" ]]
    then
        out_args="-acodec pcm_${infile_sfmt}le"
    elif [[ $OUTFILE_EXT == "m4a" ]]
    then
        out_args="-acodec alac"
    # For some reason (perhaps bc of the precision set in sox resampler),
    # signed pcm files are having their bit depth increased from 16 to 24 if we
    # don't specify to keep the sample format the same.
    elif [[ $OUTFILE_EXT == "flac" && $infile_sfmt =~ ^s[0-9]+ ]]
    then
        if [[ $infile_sfmt =~ p$ ]]
        then
            # Flac doesn't support the "p" variants of signed sample fmts
            infile_sfmt=${infile_sfmt%p}
        fi
        out_args="-sample_fmt ${infile_sfmt}"
    fi

    # Prepend output directory, strip infile extension, add suffix, add outfile extension
    output_file=${OUT_DIR}${item%.*}${suffix}.${OUTFILE_EXT}

    # Skip if we've already created this output file and it's not zero-size
    if [[ -e $output_file && -s $output_file ]]
    then
        err "INFO: Output file already exists!"
        err "SKIPPING ${output_file}"
        return 0
    fi

    # Print some info about the output file to be created
    err "filter_args: $pre_filter_args$filter_args$vol_level"
    err "out_args: $out_args"
    err "output_file: $output_file"

    # Where the magic happens
    ffmpeg -y -i "$item" -af "$pre_filter_args$filter_args$vol_level" $out_args "$output_file"

    return 0
}

########################
# DRIVER CODE
########################
# Set volume boost/cut if given as argument
if [[ $VOL_BOOST ]]
then
    vol_level=",volume=${3}dB"
fi

# Recreate the directory structure from the input dir in the output dir
# First create the output directory in case there are no subdirectories in CWD
mkdir "$OUT_DIR"
find . -mindepth 1 -type d -exec mkdir -p -- "${OUT_DIR}{}" \;

# Recursively loop through all supported audio files, call the conv function on each
# Using brace expansion to search both the current dir, and subdirs
for item in ./{*,**/*}.{flac,dsf,dff,wav,aiff,m4a,mp3}
do 
    # Skip unexpanded globs
    if [[ -e $item ]] 
    then 
        # Reset some file-specific resampler args
        pre_filter_args=""
        out_args=""

        # If we got a fatal error on trying to convert this file...
        if ! conv "$item"
        then
            # Exit loop
            break
        fi
    fi
done

# Not doing anything specific with the various error levels for now. Just checking for existence.
if [[ $error_level -ne 0 ]]
then
    err ""
    err "WARNING: Errors were encountered. Some or all files may not have been converted"
    print_usage
    exit 1
else
    exit 0
fi

Upvotes: -1

Gyan
Gyan

Reputation: 93058

Basic syntax is

ffmpeg -i input1
       -i input2
       ...
       -i inputn
       -map 0 -f flac out1.flac
       -map 1 -f flac out2.flac
       ...
       -map n-1 -f flac outn.flac

Upvotes: 5

Pawan Developers
Pawan Developers

Reputation: 377

Yes, you can do using batch processing. see here for more info:

http://forum.videohelp.com/threads/356314-How-to-batch-convert-multiplex-any-files-with-ffmpeg

Upvotes: -3

Related Questions