mirror of
https://github.com/natelandau/shell-scripting-templates.git
synced 2025-11-08 13:13:47 -05:00
v2.1.0 - Added m4b (audiobook) and concatenation support
This commit is contained in:
119
bin/convertMedia
119
bin/convertMedia
@@ -3,7 +3,7 @@
|
|||||||
# ##################################################
|
# ##################################################
|
||||||
# My Generic BASH script template
|
# My Generic BASH script template
|
||||||
#
|
#
|
||||||
version="2.0.1" # Sets version variable for this script
|
version="2.1.0" # Sets version variable for this script
|
||||||
#
|
#
|
||||||
scriptTemplateVersion="1.5.0" # Version of scriptTemplate.sh that this script is based on
|
scriptTemplateVersion="1.5.0" # Version of scriptTemplate.sh that this script is based on
|
||||||
#
|
#
|
||||||
@@ -21,12 +21,11 @@ scriptTemplateVersion="1.5.0" # Version of scriptTemplate.sh that this script is
|
|||||||
# - Better error handling
|
# - Better error handling
|
||||||
# - Better renaming of existing files
|
# - Better renaming of existing files
|
||||||
# * 2016-01-09 - v2.0.1 - Fixed bug on in video preset section where 'videoPreset' was malformed
|
# * 2016-01-09 - v2.0.1 - Fixed bug on in video preset section where 'videoPreset' was malformed
|
||||||
|
# * 2016-01-09 - v2.1.0 - Support for audiobooks with .m4b
|
||||||
|
# - Support for concatenating multiple audio files
|
||||||
|
# - Support for --probe function to output ffprobe data in JSON
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# TODO
|
|
||||||
# - fix delete file function
|
|
||||||
# - add overwrite original function
|
|
||||||
#
|
|
||||||
# ##################################################
|
# ##################################################
|
||||||
|
|
||||||
# Provide a variable with the location of this script.
|
# Provide a variable with the location of this script.
|
||||||
@@ -80,6 +79,8 @@ deleteOriginal=false
|
|||||||
newFileFlag=false
|
newFileFlag=false
|
||||||
videoFoundFlag=false
|
videoFoundFlag=false
|
||||||
audioFoundFlag=false
|
audioFoundFlag=false
|
||||||
|
probe=false
|
||||||
|
concat=false
|
||||||
XLD=0
|
XLD=0
|
||||||
args=()
|
args=()
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ function mainScript() {
|
|||||||
|
|
||||||
# File extension mappings - ie - are we working with a video or music file?
|
# File extension mappings - ie - are we working with a video or music file?
|
||||||
videoTypes=(mp4 mov avi mkv wmv flv ogg m4p m4v 3gp divx h264)
|
videoTypes=(mp4 mov avi mkv wmv flv ogg m4p m4v 3gp divx h264)
|
||||||
audioTypes=(mp3 m4a aiff aac m4p wav wma flac)
|
audioTypes=(mp3 m4a aiff aac m4p wav wma flac m4b)
|
||||||
|
|
||||||
function identifyFiles() {
|
function identifyFiles() {
|
||||||
local item
|
local item
|
||||||
@@ -238,6 +239,20 @@ function mainScript() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function concatFiles() {
|
||||||
|
if ${concatFiles}; then
|
||||||
|
# Create variable for ffmpeg to concacenate the files
|
||||||
|
concatConvert="concat:$(join "|" ${filesToConvert[@]})"
|
||||||
|
# Create the variable for the deleteOriginalFile function
|
||||||
|
concatDelete="$(join " " ${filesToConvert[@]})"
|
||||||
|
|
||||||
|
# Ask the user for the name of the newly created file
|
||||||
|
input "Please enter the name of the new file to be created. [ENTER]: "
|
||||||
|
read concatOutput
|
||||||
|
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
function parseJSON() {
|
function parseJSON() {
|
||||||
local ii
|
local ii
|
||||||
local videoFoundFlag
|
local videoFoundFlag
|
||||||
@@ -257,6 +272,12 @@ function mainScript() {
|
|||||||
# ffprobe -v quiet -print_format json -show_format -show_streams "${file}" >> "${file}.json"
|
# ffprobe -v quiet -print_format json -show_format -show_streams "${file}" >> "${file}.json"
|
||||||
# cat "${informationFile}"
|
# cat "${informationFile}"
|
||||||
|
|
||||||
|
# If the '--probe' flag is set, we print the media file information onto the screen.
|
||||||
|
if "${probe}"; then
|
||||||
|
cat "${informationFile}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Read the necessary information from the JSON
|
# Read the necessary information from the JSON
|
||||||
format="$(jq -r ".format.format_long_name" "${informationFile}")"
|
format="$(jq -r ".format.format_long_name" "${informationFile}")"
|
||||||
formatName="$(jq -r ".format.format_name" "${informationFile}")"
|
formatName="$(jq -r ".format.format_name" "${informationFile}")"
|
||||||
@@ -471,7 +492,6 @@ function mainScript() {
|
|||||||
# Build the Conversion Command
|
# Build the Conversion Command
|
||||||
# ########################################
|
# ########################################
|
||||||
|
|
||||||
# FLAC TO ALAC
|
|
||||||
if [[ "${userOutput,,}" == "alac" ]]; then
|
if [[ "${userOutput,,}" == "alac" ]]; then
|
||||||
if type_exists "xld"; then
|
if type_exists "xld"; then
|
||||||
XLD=1
|
XLD=1
|
||||||
@@ -485,23 +505,23 @@ function mainScript() {
|
|||||||
elif [[ "${userOutput,,}" == "flac" ]]; then
|
elif [[ "${userOutput,,}" == "flac" ]]; then
|
||||||
if type_exists "xld"; then
|
if type_exists "xld"; then
|
||||||
XLD=1
|
XLD=1
|
||||||
audioConvertCommand="-f flac" && verbose "Using XLD. audioConvertCommand = -f flac"
|
audioConvertCommand="-f flac"
|
||||||
else
|
else
|
||||||
audioConvertCommand="-c:a flac" && verbose "Using ffmpeg. audioConvertCommand = -c:a flac"
|
audioConvertCommand="-c:a flac"
|
||||||
fi
|
fi
|
||||||
elif [[ "${userOutput,,}" == "aac" || "${userOutput,,}" == "m4a" ]]; then
|
elif [[ "${userOutput,,}" == "aac" || "${userOutput,,}" == "m4a" ]]; then
|
||||||
# Pick the best aac audio encoder
|
# Pick the best aac audio encoder
|
||||||
if $(ffmpeg -version | grep enable-libfdk-aac >/dev/null); then
|
if $(ffmpeg -version | grep enable-libfdk-aac >/dev/null); then
|
||||||
# set variable bit rate to '5', the highest
|
# set variable bit rate to '5', the highest
|
||||||
aacEncoder='libfdk_aac -vbr 5' && verbose "aacEncoder = libfdk_aac -vbr 5"
|
aacEncoder='libfdk_aac -vbr 5'
|
||||||
else
|
else
|
||||||
aacEncoder='libfaac -q:a 400' && verbose "aacEncoder = libfaac -q:a 400"
|
aacEncoder='libfaac -q:a 400'
|
||||||
fi
|
fi
|
||||||
if type_exists "xlds"; then
|
if type_exists "xlds"; then
|
||||||
XLD=1
|
XLD=1
|
||||||
audioConvertCommand="-f aac" && verbose "using xld. audioConvertCommand = -f aac "
|
audioConvertCommand="-f aac" && verbose "using xld. audioConvertCommand = -f aac "
|
||||||
else
|
else
|
||||||
audioConvertCommand="-acodec ${aacEncoder}" && verbose "using ffmpeg. audioConvertCommand = -acodec ${aacEncoder}"
|
audioConvertCommand="-acodec ${aacEncoder}"
|
||||||
fi
|
fi
|
||||||
elif [[ "${userOutput,,}" == "mp3" ]]; then
|
elif [[ "${userOutput,,}" == "mp3" ]]; then
|
||||||
# Can we convert to mp3? Do we have an ffmpeg encoder?
|
# Can we convert to mp3? Do we have an ffmpeg encoder?
|
||||||
@@ -515,12 +535,28 @@ function mainScript() {
|
|||||||
if [ -n "$bitrate" ]; then
|
if [ -n "$bitrate" ]; then
|
||||||
bitrate="${bitrate%k}k" # Ensure 'k' is at the end of any bitrate sent to ffmpeg
|
bitrate="${bitrate%k}k" # Ensure 'k' is at the end of any bitrate sent to ffmpeg
|
||||||
ffmpegBitrate="-b:a $bitrate" && verbose "bitrate = ${bitrate}"
|
ffmpegBitrate="-b:a $bitrate" && verbose "bitrate = ${bitrate}"
|
||||||
audioConvertCommand="-acodec ${mp3Encoder} ${ffmpegBitrate}" && verbose "audioConvertCommand = -acodec ${mp3Encoder} ${ffmpegBitrate}"
|
audioConvertCommand="-acodec ${mp3Encoder} ${ffmpegBitrate}"
|
||||||
else
|
else
|
||||||
audioConvertCommand="-acodec ${mp3Encoder} -qscale:a 0" && verbose "audioConvertCommand = -acodec ${mp3Encoder} -qscale:a 0"
|
audioConvertCommand="-acodec ${mp3Encoder} -qscale:a 0"
|
||||||
fi
|
fi
|
||||||
|
elif [[ "${userOutput,,}" == "m4b" ]]; then
|
||||||
|
# m4b is exactly the same as m4a except it tells Apple that the file is an audiobook.
|
||||||
|
# so we use m4a conversion here and then rename the output file to m4b
|
||||||
|
# The main difference here is that we make the assumption that audiobooks don't
|
||||||
|
# need high fidelity or stereo so we make them mono and low bit-rate.
|
||||||
|
|
||||||
|
# Pick the best aac audio encoder
|
||||||
|
if $(ffmpeg -version | grep enable-libfdk-aac >/dev/null); then
|
||||||
|
# set variable bit rate to '5', the highest
|
||||||
|
aacEncoder='libfdk_aac -b:a 64k -ac 1 -f mp4'
|
||||||
|
else
|
||||||
|
aacEncoder='libfaac -b:a 64k -ac 1 -f mp4'
|
||||||
|
fi
|
||||||
|
audioConvertCommand="-acodec ${aacEncoder}"
|
||||||
else
|
else
|
||||||
die "Unknown audio conversion format: '${outputFormat}'."
|
warning "We don't know what to do with audio format: '${outputFormat}'."
|
||||||
|
warning "Exiting"
|
||||||
|
safeExit
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,6 +596,12 @@ function mainScript() {
|
|||||||
# Add the output directory
|
# Add the output directory
|
||||||
output="${outputDir}${output}"
|
output="${outputDir}${output}"
|
||||||
|
|
||||||
|
#Override the output file to the user's input if we are concatenating files
|
||||||
|
if [ -n "$concatOutput" ]; then
|
||||||
|
output="${outputDir}${concatOutput%.*}.${outputFormat}"
|
||||||
|
success "output=${output}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Confirm we're not overwriting an existing file
|
# Confirm we're not overwriting an existing file
|
||||||
# If we are, append '.new' to the name
|
# If we are, append '.new' to the name
|
||||||
fileFlag=0
|
fileFlag=0
|
||||||
@@ -589,6 +631,11 @@ function mainScript() {
|
|||||||
ffquiet="-loglevel quiet"
|
ffquiet="-loglevel quiet"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# When concatenating files, we need a different $file variable
|
||||||
|
if ${concatFiles}; then
|
||||||
|
file="${concatConvert}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Respect the 'logfile' flag
|
# Respect the 'logfile' flag
|
||||||
if ${printLog}; then ffmpegLog=">> ${logFile}"; fi
|
if ${printLog}; then ffmpegLog=">> ${logFile}"; fi
|
||||||
|
|
||||||
@@ -632,29 +679,50 @@ function mainScript() {
|
|||||||
function deleteOriginalFile() {
|
function deleteOriginalFile() {
|
||||||
if ${verbose}; then v="-v" ; fi
|
if ${verbose}; then v="-v" ; fi
|
||||||
|
|
||||||
|
# first, ensure we don't delete the originals if we're in a safe rune
|
||||||
if [[ "${safeRun}" == "0" ]]; then
|
if [[ "${safeRun}" == "0" ]]; then
|
||||||
if ${deleteOriginal}; then
|
if ${deleteOriginal}; then
|
||||||
rm -f ${v} "${file}"
|
if ${concatFiles}; then
|
||||||
if [[ "${file##*.}" == "${outputFormat}" ]]; then
|
for fileToDelete in "${filesToConvert[@]}"; do
|
||||||
mv ${v} "${output}" "${outputDir}${file}"
|
rm -f ${v} "${fileToDelete}"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
rm -f ${v} "${file}"
|
||||||
|
if [[ "${file##*.}" == "${outputFormat}" ]]; then
|
||||||
|
mv ${v} "${output}" "${outputDir}${file}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# break the loop if we're concatenating files.
|
||||||
|
if ${concatFiles}; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
## RUN THE SCRIPT ##
|
## RUN THE SCRIPT ##
|
||||||
####################################
|
####################################
|
||||||
|
|
||||||
# First we need to identify the files to be converted
|
# First we need to identify the files
|
||||||
identifyFiles
|
identifyFiles
|
||||||
|
|
||||||
# Then we test that all files can be operated upon
|
# Then we test that all files can be operated upon
|
||||||
for fileToTest in "${filesToConvert[@]}"; do testFiles; done
|
for fileToTest in "${filesToConvert[@]}"; do testFiles; done
|
||||||
|
|
||||||
|
# Then we respect the '--probe' option if requested
|
||||||
|
if "${probe}"; then
|
||||||
|
for file in "${filesToConvert[@]}"; do
|
||||||
|
info "Probing ${file}"
|
||||||
|
verbose "ffprobe -v quiet -print_format json -show_format -show_streams ${file}"
|
||||||
|
parseJSON
|
||||||
|
done
|
||||||
|
safeExit
|
||||||
|
fi
|
||||||
|
|
||||||
# Then we work on the individual files. This is the fun part.
|
# Then we work on the individual files. This is the fun part.
|
||||||
for file in "${filesToConvert[@]}"; do
|
for file in "${filesToConvert[@]}"; do
|
||||||
info "Working on ${file}"
|
info "Working on ${file}"
|
||||||
|
|
||||||
# First we grab the metadata of the file and assign it to variables
|
# First we grab the metadata of the file and assign it to variables
|
||||||
parseJSON
|
parseJSON
|
||||||
# Then we set the expected output format
|
# Then we set the expected output format
|
||||||
@@ -664,6 +732,8 @@ function mainScript() {
|
|||||||
if [[ "${audioTypes[*]}" =~ "${file##*.}" ]]; then convertAudio; fi
|
if [[ "${audioTypes[*]}" =~ "${file##*.}" ]]; then convertAudio; fi
|
||||||
# Then we tell the script where to output the file
|
# Then we tell the script where to output the file
|
||||||
setOutputDirectory
|
setOutputDirectory
|
||||||
|
# Then we check if we are supposed to concatenate the files
|
||||||
|
concatFiles
|
||||||
# Then we generate the name for the new file
|
# Then we generate the name for the new file
|
||||||
setOutputFile
|
setOutputFile
|
||||||
# Then we actually do the conversion
|
# Then we actually do the conversion
|
||||||
@@ -707,6 +777,8 @@ ${bold}General Options:${reset}
|
|||||||
the FFMPEG commands to the terminal
|
the FFMPEG commands to the terminal
|
||||||
${bold}--version${reset} Output version information and exit
|
${bold}--version${reset} Output version information and exit
|
||||||
${bold}--recursive${reset} Will search recursively through directories
|
${bold}--recursive${reset} Will search recursively through directories
|
||||||
|
${bold}--probe${reset} Outputs file metadata via ffprobe in JSON format. Does no coversion.
|
||||||
|
${bold}--concat${reset} Will concatenate audio files together into a single output. Good for audiobooks.
|
||||||
|
|
||||||
${bold}File Options:${reset}
|
${bold}File Options:${reset}
|
||||||
${bold}-f, --file${reset} Specify a specific file to take actions on.
|
${bold}-f, --file${reset} Specify a specific file to take actions on.
|
||||||
@@ -753,6 +825,11 @@ Do a recursive search for all directories beneath the current one. In each
|
|||||||
directory, search for .avi files and convert them to .mp4
|
directory, search for .avi files and convert them to .mp4
|
||||||
|
|
||||||
$ convertMedia -o mp4 -i avi --recursive
|
$ convertMedia -o mp4 -i avi --recursive
|
||||||
|
|
||||||
|
Create an Apple audiobook (m4b) from all mp3 files in a directory
|
||||||
|
|
||||||
|
$ convertMedia -i mp3 -o m4b --concat
|
||||||
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,9 +884,11 @@ while [[ ${1} = -?* ]]; do
|
|||||||
--size) shift; videoSize="$1" ;;
|
--size) shift; videoSize="$1" ;;
|
||||||
--height) shift; height="$1" ;;
|
--height) shift; height="$1" ;;
|
||||||
--width) shift; width="$1" ;;
|
--width) shift; width="$1" ;;
|
||||||
|
--probe) probe=true; safeRun=1 ;;
|
||||||
--downsize720) downsize720=1 ;;
|
--downsize720) downsize720=1 ;;
|
||||||
--recursive) recursive=true ;;
|
--recursive) recursive=true ;;
|
||||||
--delete) deleteOriginal=true ;;
|
--delete) deleteOriginal=true ;;
|
||||||
|
--concat) concat=true; ;;
|
||||||
--saveDir) shift; saveDir="$1" ;;
|
--saveDir) shift; saveDir="$1" ;;
|
||||||
--bitrate) shift; bitrate="$1" ;;
|
--bitrate) shift; bitrate="$1" ;;
|
||||||
-h|--help) usage >&2; safeExit ;;
|
-h|--help) usage >&2; safeExit ;;
|
||||||
|
|||||||
Reference in New Issue
Block a user