summaryrefslogtreecommitdiff
path: root/dotfiles/system/.local/bin/AAXtoMP3
diff options
context:
space:
mode:
Diffstat (limited to 'dotfiles/system/.local/bin/AAXtoMP3')
-rwxr-xr-xdotfiles/system/.local/bin/AAXtoMP3908
1 files changed, 0 insertions, 908 deletions
diff --git a/dotfiles/system/.local/bin/AAXtoMP3 b/dotfiles/system/.local/bin/AAXtoMP3
deleted file mode 100755
index adc91ef..0000000
--- a/dotfiles/system/.local/bin/AAXtoMP3
+++ /dev/null
@@ -1,908 +0,0 @@
-#!/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] [--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
-
-# -----
-# 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 ;;
- # Apple m4a music format.
- -a | --aac ) codec=copy; extension=m4a; mode=single; container=m4a; 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 ) 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 ;;
- # Compression level
- --level ) level="$2"; shift 2 ;;
- # Keep author number n
- --keep-author ) keepArtist="$2"; shift 2 ;;
- # Author override
- --author ) authorOverride="$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
-
-
-# -----
-# 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
-
- 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 ':' 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}" \
- | tr -d ':' >> "$metadata_file"
- 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)"
-
- # 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. Exiting to avoid overwriting"
- exit 0
- 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}" \
- -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"
- extra_crop_cover=''
- 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}"
-
- # 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.
- # Since the standard (in the aax file) image resolution is 512, we set the flag
- # only if we use a custom cover art.
- extra_crop_cover='-vf crop=trunc(iw/2)*2:trunc(ih/2)*2'
- 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
-
- # -----
- # 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.
- if [[ ${container} == "mp4" && $(type -P mp4chaps) ]]; then
- ffprobe -i "${aax_file}" -print_format csv -show_chapters 2>/dev/null | awk -F "," '{printf "CHAPTER%02d=%02d:%02d:%02.3f\nCHAPTER%02dNAME=%s\n", NR, $5/60/60, $5/60%60, $5%60, NR, $8}' > "${output_directory}/${currentFileNameScheme}.chapters.txt"
- mp4chaps -i "${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