mirror of
https://github.com/KrumpetPirate/AAXtoMP3.git
synced 2024-11-17 18:58:58 +01:00
1015 lines
42 KiB
Bash
Executable File
1015 lines
42 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
|
||
# ========================================================================
|
||
# Command Line Options
|
||
|
||
# Usage Synopsis.
|
||
usage=$'\nUsage: AAXtoMP3 [--flac] [--aac] [--opus ] [--single] [--level <COMPRESSIONLEVEL>]
|
||
[--chaptered] [-e:mp3] [-e:m4a] [-e:m4b] [--authcode <AUTHCODE>] [--no-clobber]
|
||
[--target_dir <PATH>] [--complete_dir <PATH>] [--validate] [--loglevel <LOGLEVEL>]
|
||
[--keep-author <N>] [--author <AUTHOR>] [--{dir,file,chapter}-naming-scheme <STRING>]
|
||
[--use-audible-cli-data] [--audible-cli-library-file <LIBRARY_PATH>] [--continue <CHAPTERNUMBER>] {FILES}\n'
|
||
codec=libmp3lame # Default encoder.
|
||
extension=mp3 # Default encoder extension.
|
||
level=-1 # Compression level. Can be given for mp3, flac and opus. -1 = default/not specified.
|
||
mode=chaptered # Multi file output
|
||
auth_code= # Required to be set via file or option.
|
||
targetdir= # Optional output location. Note default is basedir of AAX file.
|
||
dirNameScheme= # Custom directory naming scheme, default is $genre/$author/$title
|
||
customDNS=0
|
||
fileNameScheme= # Custom file naming scheme, default is $title
|
||
customFNS=0
|
||
chapterNameScheme= # Custom chapter naming scheme, default is '$title-$(printf %0${#chaptercount}d $chapternum) $chapter' (BookTitle-01 Chapter 1)
|
||
customCNS=0
|
||
completedir= # Optional location to move aax files once the decoding is complete.
|
||
container=mp3 # Just in case we need to change the container. Used for M4A to M4B
|
||
VALIDATE=0 # Validate the input aax file(s) only. No Transcoding of files will occur
|
||
loglevel=1 # Loglevel: 0: Show progress only; 1: default; 2: a little more information, timestamps; 3: debug
|
||
noclobber=0 # Default off, clobber only if flag is enabled
|
||
continue=0 # Default off, If set Transcoding is skipped and chapter splitting starts at chapter continueAt.
|
||
continueAt=1 # Optional chapter to continue splitting the chapters.
|
||
keepArtist=-1 # Default off, if set change author metadata to use the passed argument as field
|
||
authorOverride= # Override the author, ignoring the metadata
|
||
audibleCli=0 # Default off, Use additional data gathered from mkb79/audible-cli
|
||
aaxc_key= # Initialize variables, in case we need them in debug_vars
|
||
aaxc_iv= # Initialize variables, in case we need them in debug_vars
|
||
ffmpegPath= # Set a custom path, useful for using the updated version that supports aaxc
|
||
ffmpegName=ffmpeg # Set a custom ffmpeg binary name, useful tailoring to local setup
|
||
ffprobeName=ffprobe # Set a custom ffprobe binary name, useful tailoring to local setup
|
||
library_file= # Libraryfile generated by mkb79/audible-cli
|
||
|
||
# -----
|
||
# Code tip Do not have any script above this point that calls a function or a binary. If you do
|
||
# the $1 will no longer be a ARGV element. So you should only do basic variable setting above here.
|
||
#
|
||
# Process the command line options. This allows for un-ordered options. Sorta like a getops style
|
||
while true; do
|
||
case "$1" in
|
||
# Flac encoding
|
||
-f | --flac ) codec=flac; extension=flac; mode=single; container=flac; shift ;;
|
||
# Ogg Format
|
||
-o | --opus ) codec=libopus; extension=opus; container=ogg; shift ;;
|
||
# If appropriate use only a single file output.
|
||
-s | --single ) mode=single; shift ;;
|
||
# If appropriate use only a single file output.
|
||
-c | --chaptered ) mode=chaptered; shift ;;
|
||
# This is the same as --single option.
|
||
-e:mp3 ) codec=libmp3lame; extension=mp3; mode=single; container=mp3; shift ;;
|
||
# Identical to --acc option.
|
||
-e:m4a | -a | --aac ) codec=copy; extension=m4a; mode=single; container=mp4; shift ;;
|
||
# Similar to --aac but specific to audio books
|
||
-e:m4b ) codec=copy; extension=m4b; mode=single; container=mp4; shift ;;
|
||
# Change the working dir from AAX directory to what you choose.
|
||
-t | --target_dir ) targetdir="$2"; shift 2 ;;
|
||
# Use a custom directory naming scheme, with variables.
|
||
-D | --dir-naming-scheme ) dirNameScheme="$2"; customDNS=1; shift 2 ;;
|
||
# Use a custom file naming scheme, with variables.
|
||
-F | --file-naming-scheme ) fileNameScheme="$2"; customFNS=1; shift 2 ;;
|
||
# Use a custom chapter naming scheme, with variables.
|
||
--chapter-naming-scheme ) chapterNameScheme="$2"; customCNS=1; shift 2 ;;
|
||
# Move the AAX file to a new directory when decoding is complete.
|
||
-C | --complete_dir ) completedir="$2"; shift 2 ;;
|
||
# Authorization code associate with the AAX file(s)
|
||
-A | --authcode ) auth_code="$2"; shift 2 ;;
|
||
# Don't overwrite the target directory if it already exists
|
||
-n | --no-clobber ) noclobber=1; shift ;;
|
||
# Extremely verbose output.
|
||
-d | --debug ) loglevel=3; shift ;;
|
||
# Set loglevel.
|
||
-l | --loglevel ) loglevel="$2"; shift 2 ;;
|
||
# Validate ONLY the aax file(s) No transcoding occurs
|
||
-V | --validate ) VALIDATE=1; shift ;;
|
||
# continue splitting chapters at chapter continueAt
|
||
--continue ) continueAt="$2"; continue=1; shift 2 ;;
|
||
# Use additional data got with mkb79/audible-cli
|
||
--use-audible-cli-data ) audibleCli=1; shift ;;
|
||
# Path of the library-file, generated by mkb79/audible-cli (audible library export -o ./library.tsv)
|
||
-L | --audible-cli-library-file ) library_file="$2"; shift 2 ;;
|
||
# Compression level
|
||
--level ) level="$2"; shift 2 ;;
|
||
# Keep author number n
|
||
--keep-author ) keepArtist="$2"; shift 2 ;;
|
||
# Author override
|
||
--author ) authorOverride="$2"; shift 2 ;;
|
||
# Ffmpeg path override
|
||
--ffmpeg-path ) ffmpegPath="$2"; shift 2 ;;
|
||
# Ffmpeg name override
|
||
--ffmpeg-name ) ffmpegName="$2"; shift 2 ;;
|
||
# Ffprobe name override
|
||
--ffprobe-name ) ffprobeName="$2"; shift 2 ;;
|
||
# Command synopsis.
|
||
-h | --help ) printf "$usage" $0 ; exit ;;
|
||
# Standard flag signifying the end of command line processing.
|
||
-- ) shift; break ;;
|
||
# Anything else stops command line processing.
|
||
* ) break ;;
|
||
|
||
esac
|
||
done
|
||
|
||
# -----
|
||
# Empty argv means we have nothing to do so lets bark some help.
|
||
if [ "$#" -eq 0 ]; then
|
||
printf "$usage" $0
|
||
exit 1
|
||
fi
|
||
|
||
# Setup safer bash script defaults.
|
||
set -o errexit -o noclobber -o nounset -o pipefail
|
||
|
||
# ========================================================================
|
||
# Utility Functions
|
||
|
||
# -----
|
||
# debug
|
||
# debug "Some longish message"
|
||
debug() {
|
||
if [ $loglevel == 3 ] ; then
|
||
echo "$(date "+%F %T%z") DEBUG ${1}"
|
||
fi
|
||
}
|
||
|
||
# -----
|
||
# debug dump contents of a file to STDOUT
|
||
# debug "<full path to file>"
|
||
debug_file() {
|
||
if [ $loglevel == 3 ] ; then
|
||
echo "$(date "+%F %T%z") DEBUG"
|
||
echo "=Start=========================================================================="
|
||
cat "${1}"
|
||
echo "=End============================================================================"
|
||
fi
|
||
}
|
||
|
||
# -----
|
||
# debug dump a list of internal script variables to STDOUT
|
||
# debug_vars "Some Message" var1 var2 var3 var4 var5
|
||
debug_vars() {
|
||
if [ $loglevel == 3 ] ; then
|
||
msg="$1"; shift ; # Grab the message
|
||
args=("$@") # Grab the rest of the args
|
||
|
||
# determine the length of the longest key
|
||
l=0
|
||
for (( n=0; n<${#args[@]}; n++ )) ; do
|
||
(( "${#args[$n]}" > "$l" )) && l=${#args[$n]}
|
||
done
|
||
|
||
# Print the Debug Message
|
||
echo "$(date "+%F %T%z") DEBUG ${msg}"
|
||
echo "=Start=========================================================================="
|
||
|
||
# Using the max length of a var name we dynamically create the format.
|
||
fmt="%-"${l}"s = %s\n"
|
||
|
||
for (( n=0; n<${#args[@]}; n++ )) ; do
|
||
eval val="\$${args[$n]}" ; # We save off the value of the var in question for ease of coding.
|
||
|
||
echo "$(printf "${fmt}" ${args[$n]} "${val}" )"
|
||
done
|
||
echo "=End============================================================================"
|
||
fi
|
||
}
|
||
|
||
# -----
|
||
# log
|
||
log() {
|
||
if [ "$((${loglevel} > 1))" == "1" ] ; then
|
||
echo "$(date "+%F %T%z") ${1}"
|
||
else
|
||
echo "${1}"
|
||
fi
|
||
}
|
||
|
||
# -----
|
||
#progressbar produces a progressbar in the style of
|
||
# process: |####### | XX% (part/total unit)
|
||
# which is gonna be overwritten by the next line.
|
||
|
||
progressbar() {
|
||
#get input
|
||
part=${1}
|
||
total=${2}
|
||
|
||
#compute percentage and make print_percentage the same length regardless of the number of digits.
|
||
percentage=$((part*100/total))
|
||
if [ "$((percentage<10))" = "1" ]; then print_percentage=" $percentage"
|
||
elif [ "$((percentage<100))" = "1" ]; then print_percentage=" $percentage"
|
||
else print_percentage="$percentage"; fi
|
||
|
||
#draw progressbar with one # for every 5% and blank spaces for the missing part.
|
||
progressbar=""
|
||
for (( n=0; n<(percentage/5); n++ )) ; do progressbar="$progressbar#"; done
|
||
for (( n=0; n<(20-(percentage/5)); n++ )) ; do progressbar="$progressbar "; done
|
||
|
||
#print progressbar
|
||
echo -ne "Chapter splitting: |$progressbar| $print_percentage% ($part/$total chapters)\r"
|
||
}
|
||
# Print out what we have already after command line processing.
|
||
debug_vars "Command line options as set" codec extension mode container targetdir completedir auth_code keepArtist authorOverride audibleCli
|
||
|
||
# ========================================================================
|
||
# Variable validation
|
||
|
||
if [ $(uname) = 'Linux' ]; then
|
||
GREP="grep"
|
||
FIND="find"
|
||
SED="sed"
|
||
else
|
||
GREP="ggrep"
|
||
FIND="gfind"
|
||
SED="gsed"
|
||
fi
|
||
|
||
# Use custom ffmpeg (and ffprobe) binary ( --ffmpeg-path flag)
|
||
if [ -n "$ffmpegPath" ]; then
|
||
FFMPEG="$ffmpegPath/${ffmpegName}"
|
||
FFPROBE="$ffmpegPath/${ffprobeName}"
|
||
else
|
||
FFMPEG="${ffmpegName}"
|
||
FFPROBE="${ffprobeName}"
|
||
fi
|
||
|
||
debug_vars "ffmpeg/ffprobe paths" FFMPEG FFPROBE
|
||
|
||
# -----
|
||
# Detect which annoying version of grep we have
|
||
if ! [[ $(type -P "$GREP") ]]; then
|
||
echo "$GREP (GNU grep) is not in your PATH"
|
||
echo "Without it, this script will break."
|
||
echo "On macOS, you may want to try: brew install grep"
|
||
exit 1
|
||
fi
|
||
|
||
# -----
|
||
# Detect which annoying version of find we have
|
||
if ! [[ $(type -P "$FIND") ]]; then
|
||
echo "$FIND (GNU find) is not in your PATH"
|
||
echo "Without it, this script will break."
|
||
echo "On macOS, you may want to try: brew install findutils"
|
||
exit 1
|
||
fi
|
||
|
||
# -----
|
||
# Detect which annoying version of sed we have
|
||
if ! [[ $(type -P "$SED") ]]; then
|
||
echo "$SED (GNU sed) is not in your PATH"
|
||
echo "Without it, this script will break."
|
||
echo "On macOS, you may want to try: brew install gnu-sed"
|
||
exit 1
|
||
fi
|
||
|
||
# -----
|
||
# Detect ffmpeg and ffprobe
|
||
if [[ "x$(type -P "$FFMPEG")" == "x" ]]; then
|
||
echo "ERROR ffmpeg was not found on your env PATH variable"
|
||
echo "Without it, this script will break."
|
||
echo "INSTALL:"
|
||
echo "MacOS: brew install ffmpeg"
|
||
echo "Ubuntu: sudo apt-get update; sudo apt-get install ffmpeg libav-tools x264 x265 bc"
|
||
echo "Ubuntu (20.04): sudo apt-get update; sudo apt-get install ffmpeg x264 x265 bc"
|
||
echo "RHEL: yum install ffmpeg"
|
||
exit 1
|
||
fi
|
||
|
||
# -----
|
||
# Detect ffmpeg and ffprobe
|
||
if [[ "x$(type -P "$FFPROBE")" == "x" ]]; then
|
||
echo "ERROR ffprobe was not found on your env PATH variable"
|
||
echo "Without it, this script will break."
|
||
echo "INSTALL:"
|
||
echo "MacOS: brew install ffmpeg"
|
||
echo "Ubuntu: sudo apt-get update; sudo apt-get install ffmpeg libav-tools x264 x265 bc"
|
||
echo "RHEL: yum install ffmpeg"
|
||
exit 1
|
||
fi
|
||
|
||
|
||
# -----
|
||
# Detect if we need mp4art for cover additions to m4a & m4b files.
|
||
if [[ "x${container}" == "xmp4" && "x$(type -P mp4art)" == "x" ]]; then
|
||
echo "WARN mp4art was not found on your env PATH variable"
|
||
echo "Without it, this script will not be able to add cover art to"
|
||
echo "m4b files. Note if there are no other errors the AAXtoMP3 will"
|
||
echo "continue. However no cover art will be added to the output."
|
||
echo "INSTALL:"
|
||
echo "MacOS: brew install mp4v2"
|
||
echo "Ubuntu: sudo apt-get install mp4v2-utils"
|
||
fi
|
||
|
||
# -----
|
||
# Detect if we need mp4chaps for adding chapters to m4a & m4b files.
|
||
if [[ "x${container}" == "xmp4" && "x$(type -P mp4chaps)" == "x" ]]; then
|
||
echo "WARN mp4chaps was not found on your env PATH variable"
|
||
echo "Without it, this script will not be able to add chapters to"
|
||
echo "m4a/b files. Note if there are no other errors the AAXtoMP3 will"
|
||
echo "continue. However no chapter data will be added to the output."
|
||
echo "INSTALL:"
|
||
echo "MacOS: brew install mp4v2"
|
||
echo "Ubuntu: sudo apt-get install mp4v2-utils"
|
||
fi
|
||
|
||
# -----
|
||
# Detect if we need mediainfo for adding description and narrator
|
||
if [[ "x$(type -P mediainfo)" == "x" ]]; then
|
||
echo "WARN mediainfo was not found on your env PATH variable"
|
||
echo "Without it, this script will not be able to add the narrator"
|
||
echo "and description tags. Note if there are no other errors the AAXtoMP3"
|
||
echo "will continue. However no such tags will be added to the output."
|
||
echo "INSTALL:"
|
||
echo "MacOS: brew install mediainfo"
|
||
echo "Ubuntu: sudo apt-get install mediainfo"
|
||
fi
|
||
|
||
# -----
|
||
# Obtain the authcode from either the command line, local directory or home directory.
|
||
# See Readme.md for details on how to acquire your personal authcode for your personal
|
||
# audible AAX files.
|
||
if [ -z $auth_code ]; then
|
||
if [ -r .authcode ]; then
|
||
auth_code=`head -1 .authcode`
|
||
elif [ -r ~/.authcode ]; then
|
||
auth_code=`head -1 ~/.authcode`
|
||
fi
|
||
fi
|
||
|
||
# -----
|
||
# Check the target dir for if set if it is writable
|
||
if [[ "x${targetdir}" != "x" ]]; then
|
||
if [[ ! -w "${targetdir}" || ! -d "${targetdir}" ]] ; then
|
||
echo "ERROR Target Directory does not exist or is not writable: \"$targetdir\""
|
||
echo "$usage"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# -----
|
||
# Check the completed dir for if set if it is writable
|
||
if [[ "x${completedir}" != "x" ]]; then
|
||
if [[ ! -w "${completedir}" || ! -d "${completedir}" ]] ; then
|
||
echo "ERROR Complete Directory does not exist or is not writable: \"$completedir\""
|
||
echo "$usage"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# -----
|
||
# Check whether the loglevel is valid
|
||
if [ "$((${loglevel} < 0 || ${loglevel} > 3 ))" = "1" ]; then
|
||
echo "ERROR loglevel has to be in the range from 0 to 3!"
|
||
echo " 0: Show progress only"
|
||
echo " 1: default"
|
||
echo " 2: a little more information, timestamps"
|
||
echo " 3: debug"
|
||
echo "$usage"
|
||
exit 1
|
||
fi
|
||
# -----
|
||
# If a compression level is given, check whether the given codec supports compression level specifiers and whether the level is valid.
|
||
if [ "${level}" != "-1" ]; then
|
||
if [ "${codec}" == "flac" ]; then
|
||
if [ "$((${level} < 0 || ${level} > 12 ))" = "1" ]; then
|
||
echo "ERROR Flac compression level has to be in the range from 0 to 12!"
|
||
echo "$usage"
|
||
exit 1
|
||
fi
|
||
elif [ "${codec}" == "libopus" ]; then
|
||
if [ "$((${level} < 0 || ${level} > 10 ))" = "1" ]; then
|
||
echo "ERROR Opus compression level has to be in the range from 0 to 10!"
|
||
echo "$usage"
|
||
exit 1
|
||
fi
|
||
elif [ "${codec}" == "libmp3lame" ]; then
|
||
if [ "$((${level} < 0 || ${level} > 9 ))" = "1" ]; then
|
||
echo "ERROR MP3 compression level has to be in the range from 0 to 9!"
|
||
echo "$usage"
|
||
exit 1
|
||
fi
|
||
else
|
||
echo "ERROR This codec doesnt support compression levels!"
|
||
echo "$usage"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# -----
|
||
# Clean up if someone hits ^c or the script exits for any reason.
|
||
trap 'rm -r -f "${working_directory}"' EXIT
|
||
|
||
# -----
|
||
# Set up some basic working files ASAP. Note the trap will clean this up no matter what.
|
||
working_directory=`mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir'`
|
||
metadata_file="${working_directory}/metadata.txt"
|
||
|
||
# -----
|
||
# Validate the AAX and extract the metadata associated with the file.
|
||
validate_aax() {
|
||
local media_file
|
||
media_file="$1"
|
||
|
||
# Test for existence
|
||
if [[ ! -r "${media_file}" ]] ; then
|
||
log "ERROR File NOT Found: ${media_file}"
|
||
return 1
|
||
else
|
||
if [[ "${VALIDATE}" == "1" ]]; then
|
||
log "Test 1 SUCCESS: ${media_file}"
|
||
fi
|
||
fi
|
||
|
||
# Clear the errexit value we want to capture the output of the ffprobe below.
|
||
set +e errexit
|
||
|
||
# Take a look at the aax file and see if it is valid. If the source file is aaxc, we give ffprobe additional flags
|
||
output="$("$FFPROBE" -loglevel warning ${decrypt_param} -i "${media_file}" 2>&1)"
|
||
|
||
# If invalid then say something.
|
||
if [[ $? != "0" ]] ; then
|
||
# No matter what lets bark that something is wrong.
|
||
log "ERROR: Invalid File: ${media_file}"
|
||
elif [[ "${VALIDATE}" == "1" ]]; then
|
||
# If the validate option is present then lets at least state what is valid.
|
||
log "Test 2 SUCCESS: ${media_file}"
|
||
fi
|
||
|
||
# This is a big test only performed when the --validate switch is passed.
|
||
if [[ "${VALIDATE}" == "1" ]]; then
|
||
output="$("$FFMPEG" -hide_banner ${decrypt_param} -i "${media_file}" -vn -f null - 2>&1)"
|
||
if [[ $? != "0" ]] ; then
|
||
log "ERROR: Invalid File: ${media_file}"
|
||
else
|
||
log "Test 3 SUCCESS: ${media_file}"
|
||
fi
|
||
fi
|
||
|
||
# Dump the output of the ffprobe command.
|
||
debug "$output"
|
||
|
||
# Turn it back on. ffprobe is done.
|
||
set -e errexit
|
||
}
|
||
|
||
validate_extra_files() {
|
||
local extra_media_file extra_find_command
|
||
extra_media_file="$1"
|
||
# Bash trick to delete, non greedy, from the end up until the first '-'
|
||
extra_title="${extra_media_file%-*}"
|
||
|
||
# Using this is not ideal, because if the naming scheme is changed then
|
||
# this part of the script will break
|
||
# AAX file: BookTitle-LC_128_44100_stereo.aax
|
||
# Cover file: BookTitle_(1215).jpg
|
||
# Chapter file: BookTitle-chapters.json
|
||
|
||
# Chapter
|
||
extra_chapter_file="${extra_title}-chapters.json"
|
||
|
||
# Cover
|
||
extra_dirname="$(dirname "${extra_media_file}")"
|
||
extra_find_command='$FIND "${extra_dirname}" -maxdepth 1 -regex ".*/${extra_title##*/}_([0-9]+)\.jpg"'
|
||
# We want the output of the find command, we will turn errexit on later
|
||
set +e errexit
|
||
extra_cover_file="$(eval ${extra_find_command})"
|
||
extra_eval_comm="$(eval echo ${extra_find_command})"
|
||
set -e errexit
|
||
|
||
if [[ "${aaxc}" == "1" ]]; then
|
||
# bash trick to get file w\o extention (delete from end to the first '.')
|
||
extra_voucher="${extra_media_file%.*}.voucher"
|
||
if [[ ! -r "${extra_voucher}" ]] ; then
|
||
log "ERROR File NOT Found: ${extra_voucher}"
|
||
return 1
|
||
fi
|
||
aaxc_key=$(jq -r '.content_license.license_response.key' "${extra_voucher}")
|
||
aaxc_iv=$(jq -r '.content_license.license_response.iv' "${extra_voucher}")
|
||
fi
|
||
|
||
debug_vars "Audible-cli variables" extra_media_file extra_title extra_chapter_file extra_cover_file extra_find_command extra_eval_comm extra_dirname extra_voucher aaxc_key aaxc_iv
|
||
|
||
# Test for chapter file existence
|
||
if [[ ! -r "${extra_chapter_file}" ]] ; then
|
||
log "ERROR File NOT Found: ${extra_chapter_file}"
|
||
return 1
|
||
fi
|
||
if [[ "x${extra_cover_file}" == "x" ]] ; then
|
||
log "ERROR Cover File NOT Found"
|
||
return 1
|
||
fi
|
||
|
||
# Test for library file
|
||
if [[ ! -r "${library_file}" ]] ; then
|
||
library_file_exists=0
|
||
debug "library file not found"
|
||
else
|
||
library_file_exists=1
|
||
debug "library file found"
|
||
fi
|
||
|
||
debug "All expected audible-cli related file are here"
|
||
}
|
||
|
||
# -----
|
||
# Inspect the AAX and extract the metadata associated with the file.
|
||
save_metadata() {
|
||
local media_file
|
||
media_file="$1"
|
||
"$FFPROBE" -i "$media_file" 2> "$metadata_file"
|
||
if [[ $(type -P mediainfo) ]]; then
|
||
echo "Mediainfo data START" >> "$metadata_file"
|
||
# Mediainfo output is structured like ffprobe, so we append it to the metadata file and then parse it with get_metadata_value()
|
||
# mediainfo "$media_file" >> "$metadata_file"
|
||
# Or we only get the data we are intrested in:
|
||
# Description
|
||
echo "Track_More :" "$(mediainfo --Inform="General;%Track_More%" "$media_file")" >> "$metadata_file"
|
||
# Narrator
|
||
echo "nrt :" "$(mediainfo --Inform="General;%nrt%" "$media_file")" >> "$metadata_file"
|
||
# Publisher
|
||
echo "pub :" "$(mediainfo --Inform="General;%pub%" "$media_file")" >> "$metadata_file"
|
||
echo "Mediainfo data END" >> "$metadata_file"
|
||
fi
|
||
if [[ "${audibleCli}" == "1" ]]; then
|
||
# If we use data we got with audible-cli, we delete conflicting chapter infos
|
||
$SED -i '/^ Chapter #/d' "${metadata_file}"
|
||
# Some magic: we parse the .json generated by audible-cli.
|
||
# to get the output structure like the one generated by ffprobe,
|
||
# we use some characters (#) as placeholder, add some new lines,
|
||
# put a ',' after the start value, we calculate the end of each chapter
|
||
# as start+length, and we convert (divide) the time stamps from ms to s.
|
||
# Then we delete all ':' and '/' since they make a filename invalid.
|
||
jq -r '.content_metadata.chapter_info.chapters[] | "Chapter # start: \(.start_offset_ms/1000), end: \((.start_offset_ms+.length_ms)/1000) \n#\n# Title: \(.title)"' "${extra_chapter_file}" \
|
||
| $SED 's@[:/]@@g' >> "$metadata_file"
|
||
# In case we want to use a single file m4b we need to extract the
|
||
# chapter titles from the .json generated by audible–cli and store
|
||
# them correctly formatted for mp4chaps in a chapter.txt
|
||
if [ "${mode}" == "single" ]; then
|
||
# Creating a temp file to store the chapter data collected in save_metadata, as the output
|
||
# folder will only be defined after save_metadata has been executed.
|
||
# This file is only required when using audible-cli data and executing in single mode to
|
||
# get proper chapter titles in single file m4b output.
|
||
tmp_chapter_file="${working_directory}/chapter.txt"
|
||
jq -r \
|
||
'def pad(n): tostring | if (n > length) then ((n - length) * "0") + . else . end;
|
||
.content_metadata.chapter_info.chapters |
|
||
reduce .[] as $c ([]; if $c.chapters? then .+[$c | del(.chapters)]+[$c.chapters] else .+[$c] end) | flatten |
|
||
to_entries |
|
||
.[] |
|
||
"CHAPTER\((.key))=\((((((.value.start_offset_ms / (1000*60*60)) /24 | floor) *24 ) + ((.value.start_offset_ms / (1000*60*60)) %24 | floor)) | pad(2))):\(((.value.start_offset_ms / (1000*60)) %60 | floor | pad(2))):\(((.value.start_offset_ms / 1000) %60 | floor | pad(2))).\((.value.start_offset_ms % 1000 | pad(3)))
|
||
CHAPTER\((.key))NAME=\(.value.title)"' "${extra_chapter_file}" > "${tmp_chapter_file}"
|
||
fi
|
||
|
||
# get extra meta data from library.tsv
|
||
if [[ "${library_file_exists}" == 1 ]]; then
|
||
asin=$(jq -r '.content_metadata.content_reference.asin' "${extra_chapter_file}")
|
||
if [[ ! -z "${asin}" ]]; then
|
||
lib_entry=$($GREP "^${asin}" "${library_file}")
|
||
if [[ ! -z "${lib_entry}" ]]; then
|
||
series_title=$(echo "${lib_entry}" | awk -F '\t' '{print $6}')
|
||
series_sequence=$(echo "${lib_entry}" | awk -F '\t' '{print $7}')
|
||
$SED -i "/^ Metadata:/a\\
|
||
series : ${series_title}\\
|
||
series_sequence : ${series_sequence}" "${metadata_file}"
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
debug "Metadata file $metadata_file"
|
||
debug_file "$metadata_file"
|
||
}
|
||
|
||
# -----
|
||
# Reach into the meta data and extract a specific value.
|
||
# This is a long pipe of transforms.
|
||
# This finds the first occurrence of the key : value pair.
|
||
get_metadata_value() {
|
||
local key
|
||
key="$1"
|
||
# Find the key in the meta data file # Extract field value # Remove the following /'s "(Unabridged) <blanks> at start end and multiples.
|
||
echo "$($GREP --max-count=1 --only-matching "${key} *: .*" "$metadata_file" | cut -d : -f 2- | $SED -e 's#/##g;s/ (Unabridged)//;s/^[[:blank:]]\+//g;s/[[:blank:]]\+$//g' | $SED 's/[[:blank:]]\+/ /g')"
|
||
}
|
||
|
||
# -----
|
||
# specific variant of get_metadata_value bitrate is important for transcoding.
|
||
get_bitrate() {
|
||
get_metadata_value bitrate | $GREP --only-matching '[0-9]\+'
|
||
}
|
||
|
||
# Save the original value, since in the for loop we overwrite
|
||
# $audibleCli in case the file is aaxc. If the file is the
|
||
# old aax, reset the variable to be the one passed by the user
|
||
originalAudibleCliVar=$audibleCli
|
||
# ========================================================================
|
||
# Main Transcode Loop
|
||
for aax_file
|
||
do
|
||
# If the file is in aaxc format, set the proper variables
|
||
if [[ ${aax_file##*.} == "aaxc" ]]; then
|
||
# File is the new .aaxc
|
||
aaxc=1
|
||
audibleCli=1
|
||
else
|
||
# File is the old .aax
|
||
aaxc=0
|
||
# If some previous file in the loop are aaxc, the $audibleCli variable has been overwritten, so we reset it to the original one
|
||
audibleCli=$originalAudibleCliVar
|
||
fi
|
||
|
||
debug_vars "Variables set based on file extention" aaxc originalAudibleCliVar audibleCli
|
||
|
||
# No point going on if no authcode found and the file is aax.
|
||
# If we use aaxc as input, we do not need it
|
||
# if the string $auth_code is null and the format is not aaxc; quit. We need the authcode
|
||
if [ -z $auth_code ] && [ "${aaxc}" = "0" ]; then
|
||
echo "ERROR Missing authcode, can't decode $aax_file"
|
||
echo "$usage"
|
||
exit 1
|
||
fi
|
||
|
||
# Validate the input aax file. Note this happens no matter what.
|
||
# It's just that if the validate option is set then we skip to next file.
|
||
# If however validate is not set and we proceed with the script any errors will
|
||
# case the script to stop.
|
||
|
||
# If the input file is aaxc, we need to first get the audible_key and audible_iv
|
||
# We get them in the function validate_extra_files
|
||
|
||
if [[ ${audibleCli} == "1" ]] ; then
|
||
# If we have additional files (obtained via audible-cli), be sure that they
|
||
# exists and they are in the correct location.
|
||
validate_extra_files "${aax_file}"
|
||
fi
|
||
|
||
# Set the needed params to decrypt the file. Needed in all command that require ffprobe or ffmpeg
|
||
# After validate_extra_files, since the -audible_key and -audible_iv are read in that function
|
||
if [[ ${aaxc} == "1" ]] ; then
|
||
decrypt_param="-audible_key ${aaxc_key} -audible_iv ${aaxc_iv}"
|
||
else
|
||
decrypt_param="-activation_bytes ${auth_code}"
|
||
fi
|
||
|
||
validate_aax "${aax_file}"
|
||
if [[ ${VALIDATE} == "1" ]] ; then
|
||
# Don't bother doing anything else with this file.
|
||
continue
|
||
fi
|
||
|
||
# -----
|
||
# Make sure everything is a variable. Simplifying Command interpretation
|
||
save_metadata "${aax_file}"
|
||
genre=$(get_metadata_value genre)
|
||
if [ "x${authorOverride}" != "x" ]; then
|
||
#Manual Override
|
||
artist="${authorOverride}"
|
||
album_artist="${authorOverride}"
|
||
else
|
||
if [ "${keepArtist}" != "-1" ]; then
|
||
# Choose artist from the one that are present in the metadata. Comma separated list of names
|
||
# remove leading space; 'C. S. Lewis' -> 'C.S. Lewis'
|
||
artist="$(get_metadata_value artist | cut -d',' -f"$keepArtist" | $SED -E 's|^ ||g; s|\. +|\.|g; s|((\w+\.)+)|\1 |g')"
|
||
album_artist="$(get_metadata_value album_artist | cut -d',' -f"$keepArtist" | $SED -E 's|^ ||g; s|\. +|\.|g; s|((\w+\.)+)|\1 |g')"
|
||
else
|
||
# The default
|
||
artist=$(get_metadata_value artist)
|
||
album_artist="$(get_metadata_value album_artist)"
|
||
fi
|
||
fi
|
||
title=$(get_metadata_value title)
|
||
title=${title:0:128}
|
||
bitrate="$(get_bitrate)k"
|
||
album="$(get_metadata_value album)"
|
||
album_date="$(get_metadata_value date)"
|
||
copyright="$(get_metadata_value copyright)"
|
||
series="$(get_metadata_value series)"
|
||
series_sequence="$(get_metadata_value series_sequence)"
|
||
|
||
# Get more tags with mediainfo
|
||
if [[ $(type -P mediainfo) ]]; then
|
||
narrator="$(get_metadata_value nrt)"
|
||
description="$(get_metadata_value Track_More)"
|
||
publisher="$(get_metadata_value pub)"
|
||
else
|
||
narrator=""
|
||
description=""
|
||
publisher=""
|
||
fi
|
||
|
||
# Define the output_directory
|
||
if [ "${customDNS}" == "1" ]; then
|
||
currentDirNameScheme="$(eval echo "${dirNameScheme}")"
|
||
else
|
||
# The Default
|
||
currentDirNameScheme="${genre}/${artist}/${title}"
|
||
fi
|
||
|
||
# If we defined a target directory, use it. Otherwise use the location of the AAX file
|
||
if [ "x${targetdir}" != "x" ] ; then
|
||
output_directory="${targetdir}/${currentDirNameScheme}"
|
||
else
|
||
output_directory="$(dirname "${aax_file}")/${currentDirNameScheme}"
|
||
fi
|
||
|
||
# Define the output_file
|
||
if [ "${customFNS}" == "1" ]; then
|
||
currentFileNameScheme="$(eval echo "${fileNameScheme}")"
|
||
else
|
||
# The Default
|
||
currentFileNameScheme="${title}"
|
||
fi
|
||
output_file="${output_directory}/${currentFileNameScheme}.${extension}"
|
||
|
||
if [[ "${noclobber}" = "1" ]] && [[ -d "${output_directory}" ]]; then
|
||
log "Noclobber enabled but directory '${output_directory}' exists. Skipping to avoid overwriting"
|
||
rm -f "${metadata_file}" "${tmp_chapter_file}"
|
||
continue
|
||
fi
|
||
mkdir -p "${output_directory}"
|
||
|
||
if [ "$((${loglevel} > 0))" = "1" ]; then
|
||
# Fancy declaration of which book we are decoding. Including the AUTHCODE.
|
||
dashline="----------------------------------------------------"
|
||
log "$(printf '\n----Decoding---%s%s--%s--' "${title}" "${dashline:${#title}}" "${auth_code}")"
|
||
log "Source: ${aax_file}"
|
||
fi
|
||
|
||
# Big long DEBUG output. Fully describes the settings used for transcoding.
|
||
# Note this is a long debug command. It's not critical to operation. It's purely for people debugging
|
||
# and coders wanting to extend the script.
|
||
debug_vars "Book and Variable values" title auth_code aaxc aaxc_key aaxc_iv mode aax_file container codec bitrate artist album_artist album album_date genre copyright narrator description publisher currentDirNameScheme output_directory currentFileNameScheme output_file metadata_file working_directory
|
||
|
||
|
||
# Display the total length of the audiobook in format hh:mm:ss
|
||
# 10#$var force base-10 interpretation. By default it's base-8, so values like 08 or 09 are not octal numbers
|
||
total_length="$("$FFPROBE" -v error ${decrypt_param} -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${aax_file}" | cut -d . -f 1)"
|
||
hours="$((total_length/3600))"
|
||
if [ "$((hours<10))" = "1" ]; then hours="0$hours"; fi
|
||
minutes="$((total_length/60-60*10#$hours))"
|
||
if [ "$((minutes<10))" = "1" ]; then minutes="0$minutes"; fi
|
||
seconds="$((total_length-3600*10#$hours-60*10#$minutes))"
|
||
if [ "$((seconds<10))" = "1" ]; then seconds="0$seconds"; fi
|
||
log "Total length: $hours:$minutes:$seconds"
|
||
|
||
# If level != -1 specify a compression level in ffmpeg.
|
||
compression_level_param=""
|
||
if [ "${level}" != "-1" ]; then
|
||
compression_level_param="-compression_level ${level}"
|
||
fi
|
||
|
||
# -----
|
||
if [ "${continue}" == "0" ]; then
|
||
# This is the main work horse command. This is the primary transcoder.
|
||
# This is the primary transcode. All the heavy lifting is here.
|
||
debug '"$FFMPEG" -loglevel error -stats ${decrypt_param} -i "${aax_file}" -vn -codec:a "${codec}" -ab ${bitrate} -map_metadata -1 -metadata title="${title}" -metadata artist="${artist}" -metadata album_artist="${album_artist}" -metadata album="${album}" -metadata date="${album_date}" -metadata track="1/1" -metadata genre="${genre}" -metadata copyright="${copyright}" "${output_file}"'
|
||
</dev/null "$FFMPEG" -loglevel error \
|
||
-stats \
|
||
${decrypt_param} \
|
||
-i "${aax_file}" \
|
||
-vn \
|
||
-codec:a "${codec}" \
|
||
${compression_level_param} \
|
||
-ab ${bitrate} \
|
||
-map_metadata -1 \
|
||
-metadata title="${title}" \
|
||
-metadata artist="${artist}" \
|
||
-metadata album_artist="${album_artist}" \
|
||
-metadata album="${album}" \
|
||
-metadata date="${album_date}" \
|
||
-metadata track="1/1" \
|
||
-metadata genre="${genre}" \
|
||
-metadata copyright="${copyright}" \
|
||
-metadata description="${description}" \
|
||
-metadata composer="${narrator}" \
|
||
-metadata publisher="${publisher}" \
|
||
-metadata series="${series}" \
|
||
-metadata series_sequence="${series_sequence}" \
|
||
-f ${container} \
|
||
"${output_file}"
|
||
if [ "$((${loglevel} > 0))" == "1" ]; then
|
||
log "Created ${output_file}."
|
||
fi
|
||
# -----
|
||
fi
|
||
# Grab the cover art if available.
|
||
cover_file="${output_directory}/cover.jpg"
|
||
if [ "${continue}" == "0" ]; then
|
||
if [ "${audibleCli}" == "1" ]; then
|
||
# We have a better quality cover file, copy it.
|
||
if [ "$((${loglevel} > 1))" == "1" ]; then
|
||
log "Copy cover file to ${cover_file}..."
|
||
fi
|
||
cp "${extra_cover_file}" "${cover_file}"
|
||
else
|
||
# Audible-cli not used, extract the cover from the aax file
|
||
if [ "$((${loglevel} > 1))" == "1" ]; then
|
||
log "Extracting cover into ${cover_file}..."
|
||
fi
|
||
</dev/null "$FFMPEG" -loglevel error -activation_bytes "${auth_code}" -i "${aax_file}" -an -codec:v copy "${cover_file}"
|
||
fi
|
||
fi
|
||
|
||
extra_crop_cover=''
|
||
cover_width=$(ffprobe -i "${cover_file}" 2>&1 | $GREP -Po "[0-9]+(?=x[0-9]+)")
|
||
if (( ${cover_width} % 2 == 1 )); then
|
||
if [ "$((${loglevel} > 1))" == "1" ]; then
|
||
log "Cover ${cover_file} has odd width ${cover_width}, setting extra_crop_cover to make even."
|
||
fi
|
||
# We now set a variable, ${extra_crop_cover}, which contains an additional
|
||
# ffmpeg flag. It crops the cover so the width and the height is divisible by two.
|
||
# Set the flag only if we use a cover art with an odd width.
|
||
extra_crop_cover='-vf crop=trunc(iw/2)*2:trunc(ih/2)*2'
|
||
fi
|
||
|
||
# -----
|
||
# If mode=chaptered, split the big converted file by chapter and remove it afterwards.
|
||
# Not all audio encodings make sense with multiple chapter outputs (see options section)
|
||
if [ "${mode}" == "chaptered" ]; then
|
||
# Playlist m3u support
|
||
playlist_file="${output_directory}/${currentFileNameScheme}.m3u"
|
||
if [ "${continue}" == "0" ]; then
|
||
if [ "$((${loglevel} > 0))" == "1" ]; then
|
||
log "Creating PlayList ${currentFileNameScheme}.m3u"
|
||
fi
|
||
echo '#EXTM3U' > "${playlist_file}"
|
||
fi
|
||
|
||
# Determine the number of chapters.
|
||
chaptercount=$($GREP -Pc "Chapter.*start.*end" $metadata_file)
|
||
if [ "$((${loglevel} > 0))" == "1" ]; then
|
||
log "Extracting ${chaptercount} chapter files from ${output_file}..."
|
||
if [ "${continue}" == "1" ]; then
|
||
log "Continuing at chapter ${continueAt}:"
|
||
fi
|
||
fi
|
||
chapternum=1
|
||
#start progressbar for loglevel 0 and 1
|
||
if [ "$((${loglevel} < 2))" == "1" ]; then
|
||
progressbar 0 ${chaptercount}
|
||
fi
|
||
# We pipe the metadata_file in read.
|
||
# Example of the section that we are interested in:
|
||
#
|
||
# Chapter #0:0: start 0.000000, end 1928.231474
|
||
# Metadata:
|
||
# title : Chapter 1
|
||
#
|
||
# Then read the line in these variables:
|
||
# first Chapter
|
||
# _ #0:0:
|
||
# _ start
|
||
# chapter_start 0.000000,
|
||
# _ end
|
||
# chapter_end 1928.231474
|
||
while read -r -u9 first _ _ chapter_start _ chapter_end
|
||
do
|
||
# Do things only if the line starts with 'Chapter'
|
||
if [[ "${first}" = "Chapter" ]]; then
|
||
# The next line (Metadata:...) gets discarded
|
||
read -r -u9 _
|
||
# From the line 'title : Chapter 1' we save the third field and those after in chapter
|
||
read -r -u9 _ _ chapter
|
||
|
||
# The formatting of the chapters names and the file names.
|
||
# Chapter names are used in a few place.
|
||
# Define the chapter_file
|
||
if [ "${customCNS}" == "1" ]; then
|
||
chapter_title="$(eval echo "${chapterNameScheme}")"
|
||
else
|
||
# The Default
|
||
chapter_title="${title}-$(printf %0${#chaptercount}d $chapternum) ${chapter}"
|
||
fi
|
||
chapter_file="${output_directory}/${chapter_title}.${extension}"
|
||
|
||
# Since the .aax file allready got converted we can use
|
||
# -acodec copy, which is much faster than a reencodation.
|
||
# Since there is an issue when using copy on flac, where
|
||
# the duration of the chapters gets shown as if they where
|
||
# as long as the whole audiobook.
|
||
chapter_codec=""
|
||
if test "${extension}" = "flac"; then
|
||
chapter_codec="flac "${compression_level_param}""
|
||
else
|
||
chapter_codec="copy"
|
||
fi
|
||
|
||
#Since there seems to be a bug in some older versions of ffmpeg, which makes, that -ss and -to
|
||
#have to be apllied to the output file, this makes, that -ss and -to get applied on the input for
|
||
#ffmpeg version 4+ and on the output for all older versions.
|
||
split_input=""
|
||
split_output=""
|
||
if [ "$(($("$FFMPEG" -version | $SED -E 's/[^0-9]*([0-9]).*/\1/g;1q') > 3))" = "1" ]; then
|
||
split_input="-ss ${chapter_start%?} -to ${chapter_end}"
|
||
else
|
||
split_output="-ss ${chapter_start%?} -to ${chapter_end}"
|
||
fi
|
||
|
||
# Big Long chapter debug
|
||
debug_vars "Chapter Variables:" cover_file chapter_start chapter_end chapternum chapter chapterNameScheme chapter_title chapter_file
|
||
if [ "$((${continueAt} > ${chapternum}))" = "0" ]; then
|
||
# Extract chapter by time stamps start and finish of chapter.
|
||
# This extracts based on time stamps start and end.
|
||
if [ "$((${loglevel} > 1))" == "1" ]; then
|
||
log "Splitting chapter ${chapternum}/${chaptercount} start:${chapter_start%?}(s) end:${chapter_end}(s)"
|
||
fi
|
||
</dev/null "$FFMPEG" -loglevel quiet \
|
||
-nostats \
|
||
${split_input} \
|
||
-i "${output_file}" \
|
||
-i "${cover_file}" \
|
||
${extra_crop_cover} \
|
||
${split_output} \
|
||
-map 0:0 \
|
||
-map 1:0 \
|
||
-acodec ${chapter_codec} \
|
||
-metadata:s:v title="Album cover" \
|
||
-metadata:s:v comment="Cover (front)" \
|
||
-metadata track="${chapternum}" \
|
||
-metadata title="${chapter}" \
|
||
-metadata:s:a title="${chapter}" \
|
||
-metadata:s:a track="${chapternum}" \
|
||
-map_chapters -1 \
|
||
-f ${container} \
|
||
"${chapter_file}"
|
||
# -----
|
||
if [ "$((${loglevel} < 2))" == "1" ]; then
|
||
progressbar ${chapternum} ${chaptercount}
|
||
fi
|
||
# OK lets get what need for the next chapter in the Playlist m3u file.
|
||
# Playlist creation.
|
||
duration=$(echo "${chapter_end} - ${chapter_start%?}" | bc)
|
||
echo "#EXTINF:${duration%.*},${title} - ${chapter}" >> "${playlist_file}"
|
||
echo "${chapter_title}.${extension}" >> "${playlist_file}"
|
||
fi
|
||
chapternum=$((chapternum + 1 ))
|
||
fi
|
||
done 9< "$metadata_file"
|
||
|
||
# Clean up of working directory stuff.
|
||
rm "${output_file}"
|
||
if [ "$((${loglevel} > 1))" == "1" ]; then
|
||
log "Done creating chapters for ${output_directory}."
|
||
else
|
||
#ending progress bar
|
||
echo ""
|
||
fi
|
||
else
|
||
# Perform file tasks on output file.
|
||
# ----
|
||
# ffmpeg seems to copy only chapter position, not chapter names.
|
||
# use already created chapter.txt from save_metadata() in case
|
||
# audible-cli data is used else use ffprobe to extract from m4b
|
||
if [[ ${container} == "mp4" && $(type -P mp4chaps) ]]; then
|
||
if [ "${audibleCli}" == "1" ]; then
|
||
mv "${tmp_chapter_file}" "${output_directory}/${currentFileNameScheme}.chapters.txt"
|
||
else
|
||
"$FFPROBE" -i "${aax_file}" -print_format csv -show_chapters 2>/dev/null | awk -F "," '{printf "CHAPTER%d=%02d:%02d:%02.3f\nCHAPTER%dNAME=%s\n", NR, $5/60/60, $5/60%60, $5%60, NR, $8}' > "${output_directory}/${currentFileNameScheme}.chapters.txt"
|
||
fi
|
||
$SED -i 's/\,000/\.000/' "${output_directory}/${currentFileNameScheme}.chapters.txt"
|
||
mp4chaps -i "${output_file}"
|
||
fi
|
||
fi
|
||
|
||
if [ -f "${cover_file}" ]; then
|
||
log "Adding cover art"
|
||
# FFMPEG does not support MPEG-4 containers fully #
|
||
if [ "${container}" == "mp4" ] ; then
|
||
mp4art --add "${cover_file}" "${output_file}"
|
||
# FFMPEG for everything else #
|
||
else
|
||
# Create temporary output file name - ensure extention matches previous appropriate output file to keep ffmpeg happy
|
||
cover_output_file="${output_file%.*}.cover.${output_file##*.}"
|
||
# Copy audio stream from current output, and video stream from cover file, setting appropriate metadata
|
||
</dev/null "$FFMPEG" -loglevel quiet \
|
||
-nostats \
|
||
-i "${output_file}" \
|
||
-i "${cover_file}" \
|
||
-map 0:a:0 \
|
||
-map 1:v:0 \
|
||
-acodec copy \
|
||
-vcodec copy \
|
||
-id3v2_version 3 \
|
||
-metadata:s:v title="Album cover" \
|
||
-metadata:s:v comment="Cover (front)" \
|
||
"${cover_output_file}"
|
||
# Replace original output file with version including cover
|
||
mv "${cover_output_file}" "${output_file}"
|
||
fi
|
||
fi
|
||
|
||
# -----
|
||
# Announce that we have completed the transcode
|
||
if [ "$((${loglevel} > 0))" == "1" ]; then
|
||
log "Complete ${title}"
|
||
fi
|
||
# Lastly get rid of any extra stuff.
|
||
rm "${metadata_file}"
|
||
|
||
# Move the aax file if the decode is completed and the --complete_dir is set to a valid location.
|
||
# Check the target dir for if set if it is writable
|
||
if [[ "x${completedir}" != "x" ]]; then
|
||
if [ "$((${loglevel} > 0))" == "1" ]; then
|
||
log "Moving Transcoded ${aax_file} to ${completedir}"
|
||
fi
|
||
mv "${aax_file}" "${completedir}"
|
||
fi
|
||
|
||
done
|