diff --git a/AAXtoMP3 b/AAXtoMP3 index 0a6c2e0..adc91ef 100755 --- a/AAXtoMP3 +++ b/AAXtoMP3 @@ -9,14 +9,14 @@ usage=$'\nUsage: AAXtoMP3 [--flac] [--aac] [--opus ] [--single] [--level ] [--no-clobber] [--target_dir ] [--complete_dir ] [--validate] [--loglevel ] [--keep-author ] [--author ] [--{dir,file,chapter}-naming-scheme ] -[--use-audible-cli-data] [--audible-cli-library-file ] [--continue ] {FILES}\n' +[--use-audible-cli-data] [--continue ] {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/$artist/$title +dirNameScheme= # Custom directory naming scheme, default is $genre/$author/$title customDNS=0 fileNameScheme= # Custom file naming scheme, default is $title customFNS=0 @@ -34,10 +34,6 @@ 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 @@ -48,6 +44,8 @@ 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. @@ -57,7 +55,7 @@ while true; do # 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 ;; + -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. @@ -84,20 +82,12 @@ while true; do --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. @@ -222,16 +212,6 @@ else 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 @@ -262,7 +242,7 @@ fi # ----- # Detect ffmpeg and ffprobe -if [[ "x$(type -P "$FFMPEG")" == "x" ]]; then +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:" @@ -275,7 +255,7 @@ fi # ----- # Detect ffmpeg and ffprobe -if [[ "x$(type -P "$FFPROBE")" == "x" ]]; then +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:" @@ -422,7 +402,7 @@ validate_aax() { 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)" + output="$(ffprobe -loglevel warning ${decrypt_param} -i "${media_file}" 2>&1)" # If invalid then say something. if [[ $? != "0" ]] ; then @@ -435,7 +415,7 @@ validate_aax() { # 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)" + output="$(ffmpeg -hide_banner ${decrypt_param} -i "${media_file}" -vn -f null - 2>&1)" if [[ $? != "0" ]] ; then log "ERROR: Invalid File: ${media_file}" else @@ -497,15 +477,6 @@ validate_extra_files() { 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" } @@ -514,7 +485,7 @@ validate_extra_files() { save_metadata() { local media_file media_file="$1" - "$FFPROBE" -i "$media_file" 2> "$metadata_file" + 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() @@ -536,42 +507,9 @@ save_metadata() { # 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. + # 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}" \ - | $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 + | tr -d ':' >> "$metadata_file" fi debug "Metadata file $metadata_file" debug_file "$metadata_file" @@ -679,8 +617,6 @@ do 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 @@ -703,9 +639,9 @@ do # 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}" + output_directory="${targetdir}/${currentDirNameScheme}/" else - output_directory="$(dirname "${aax_file}")/${currentDirNameScheme}" + output_directory="$(dirname "${aax_file}")/${currentDirNameScheme}/" fi # Define the output_file @@ -718,9 +654,8 @@ do 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 + log "Noclobber enabled but directory '${output_directory}' exists. Exiting to avoid overwriting" + exit 0 fi mkdir -p "${output_directory}" @@ -739,7 +674,7 @@ do # 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)" + 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))" @@ -758,8 +693,8 @@ do 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}"' - 0))" == "1" ]; then @@ -789,7 +722,8 @@ do # ----- fi # Grab the cover art if available. - cover_file="${output_directory}/${currentFileNameScheme}.jpg" + 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. @@ -797,27 +731,21 @@ do 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 - &1 | $GREP -Po "[0-9]+(?=x[0-9]+)" | tail -n 1) - 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) @@ -895,7 +823,7 @@ do #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 + 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}" @@ -909,7 +837,7 @@ do if [ "$((${loglevel} > 1))" == "1" ]; then log "Splitting chapter ${chapternum}/${chaptercount} start:${chapter_start%?}(s) end:${chapter_end}(s)" fi - /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" + 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 - 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 - 0))" == "1" ]; then diff --git a/README.md b/README.md index ee45eda..48be47c 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Audible fails for some reason. ## Usage(s) ``` -bash AAXtoMP3 [-f|--flac] [-o|--opus] [-a|--aac] [-s|--single] [--level ] [-c|--chaptered] [-e:mp3] [-e:m4a] [-e:m4b] [-A|--authcode ] [-n|--no-clobber] [-t|--target_dir ] [-C|--complete_dir ] [-V|--validate] [--use-audible-cli-data]] [-d|--debug] [-h|--help] [--continue ] ... +bash AAXtoMP3 [-f|--flac] [-o|--opus] [-a|-aac] [-s|--single] [--level ] [-c|--chaptered] [-e:mp3] [-e:m4a] [-e:m4b] [-A|--authcode ] [-n|--no-clobber] [-t|--target_dir ] [-C|--complete_dir ] [-V|--validate] [--use-audible-cli-data]] [-d|--debug] [-h|--help] [--continue ] ... ``` or if you want to get guided through the options ``` @@ -57,10 +57,6 @@ bash interactiveAAXtoMP3 [-a|--advanced] [-h|--help] * **--file-naming-scheme <STRING>** or **-F** Use a custom file naming scheme, with variables. See [below](#custom-naming-scheme) for more info. * **--chapter-naming-scheme <STRING>** Use a custom chapter naming scheme, with variables. See [below](#custom-naming-scheme) for more info. * **--use-audible-cli-data** Use additional data got with mkb79/audible-cli. See [below](#audible-cli-integration) for more info. Needed for the files in the `aaxc` format. -* **--audible-cli-library-file** or **-L** Path of the library-file, generated by mkb79/audible-cli (`audible library export -o ./library.tsv`). Only available if `--use-audible-cli-data` is set. This file is required to parse additional metadata such as `$series` or `$series_sequence`. -* **--ffmpeg-path** Set the ffmpeg/ffprobe binaries folder. Both of them must be executable and in the same folder. -* **--ffmpeg-name** Set a custom name for the ffmpeg binary. Must be executable and in path, or in custom path specified by --ffmpeg-path. -* **--ffprobe-name** Set a custom name for the ffprobe binary. Must be executable and in path, or in custom path specified by --ffmpeg-path. ## Options for interactiveAAXtoMP3 * **-a** or **--advanced** Get more options to choose. Not used right now. @@ -154,10 +150,6 @@ So you can use `--dir-naming-scheme '$(date +%Y)/$artist'`, but using `--file-na * If you want shorter chapter names, use `--chapter-naming-scheme '$(printf %0${#chaptercount}d $chapternum) $chapter'`: only chapter number and chapter name * If you want to append the narrator name to the title, use `--dir-naming-scheme '$genre/$artist/$title-$narrator' --file-naming-scheme '$title-$narrator'` * If you don't want to have the books separated by author, use `--dir-naming-scheme '$genre/$title'` -* To be able to use `$series` or `$series_sequence` in the schemes the following is required: - * `--use-audible-cli-data` is set - * you have pre-generated the library-file via `audible library export -o ./library.tsv` - * you have set the path to the generated library-file via `--audible-cli-library-file ./library.tsv` ### Installing Dependencies. In general, take a look at [command-not-found.com](https://command-not-found.com/) @@ -168,20 +160,6 @@ sudo apt-get update sudo apt-get install ffmpeg libav-tools x264 x265 bc ``` -In Debian-based system's repositories the ffmpeg version is often outdated. If you want -to convert .aaxc files, you need at least ffmpeg 4.4. So if your installed version -needs to be updated, you can either install a custom repository that has the newer version, -compile ffmpeg from source or download pre-compiled binaries. -You can then tell AAXtoMP3 to use the compiled binaries with the `--ffmpeg-path` flag. -You need to specify the folder where the ffmpeg and ffprobe binaries are. Make sure -they are both executable. - -If you have snapd installed, you can also install a recent version of 4.4 from the edge channel: -``` -snap install ffmpeg --edge -``` -In this case you will need to confiure a custom path _and_ binary name for ffprobe, `--ffmpeg-path /snap/bin/ --ffprobe-name ffmpeg.ffprobe`. - __Fedora__ Fedora users need to enable the rpm fusion repository to install ffmpeg. Version 22 and upwards are currently supported. The following command works independent of your current version: @@ -220,17 +198,13 @@ brew install findutils ``` #### mp4art/mp4chaps -_Note: This is an optional dependency, required for adding cover art to m4a and b4b files only._ +_Note: This is an optional dependency._ __Ubuntu, Linux Mint, Debian__ ``` sudo apt-get update sudo apt-get install mp4v2-utils ``` - -On Debian and Ubuntu the mp4v2-utils package has been deprecated and removed, as the upsteam project is no longer maintained. -The package was removed in Debian Buster, and Ubuntu Focal [ 20.04 ]. - __CentOS, RHEL & Fedora__ ``` # CentOS/RHEL and Fedora users make sure that you have enabled atrpms repository in system. Let’s begin installing FFmpeg as per your operating system. @@ -265,7 +239,6 @@ Since getting those keys is not simple, for now the method used to get them is handled by the package audible-cli, that stores them in a file when downloading the aaxc file. This means that in order to decrypt the aaxc files, they must be downloaded with audible-cli. -Note that you need at least [ffmpeg 4.4](#ffmpegffprobe). ## Audible-cli integration Some information are not present in the AAX file. For example the chapters's