ffmpeg | batch convert | make same filename
How would I use ffmpeg to create a batch convert file for sample rate conversion
, such as:
$ ffmpeg -i *.wav -ar 22050 *.wav
Take note, that i'm only doing a sample rate conversion, but in the end I want to batch convert all *.wav to 22050 *.wav files all while keeping the same file name for all the files converted.
Solution 1:
FFMpeg can't write on the same file while reading from it because it can cause errors.
The only way to do it is to convert into another file and replace the original files.
Or you can convert it to another folder and replace the original folder, it's easier.
For Windows:
mkdir outdir
for %i in (*.bmp) do (
ffmpeg -i %i -ar 22050 outdir\%i
)
Note: Replace %i with %%i when putting it in Windows batch file.
For Linux:
mkdir outdir
for i in *.wav; do
ffmpeg -i $i -ar 22050 outdir/$i;
done
Now just replace your directory with outdir.
For your case:
If you want it for Counter Strike:GO or HLDJ, then look here. HL requires more settings,
audio must be mono (add -ac 1
flag in FFMpeg) and 16-bit (add -acodec pcm_s16le
).
So your FFMpeg command (on Linux) will look like this:
mkdir outdir
for i in *.wav; do
ffmpeg -i $i -acodec pcm_s16le -ac 1 -ar 22050 outdir/$i;
done
Solution 2:
I wrote a script to do pretty much just that. It also has some features in addition to what you can do with one-liners or short bash snippets.
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