mirror of
https://github.com/KrumpetPirate/AAXtoMP3.git
synced 2025-04-20 02:38:38 +02:00
Compare commits
114 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e66c51dbfa | ||
|
f6ec8aa867 | ||
|
f9bc150775 | ||
|
61cfc08886 | ||
|
1338f1e5c1 | ||
|
2a5a208a2d | ||
|
8fc5e360d3 | ||
|
1544a89249 | ||
|
c289c10cd7 | ||
|
1ac6979885 | ||
|
f65fddebc2 | ||
|
cab6394c02 | ||
|
3e42539812 | ||
|
c103d4899b | ||
|
be8473ffba | ||
|
6d659ade72 | ||
|
b07737f5c4 | ||
|
9a1f0fae49 | ||
|
bf7d405d3e | ||
|
1ce836f5a2 | ||
|
72794b6785 | ||
|
c9bcf2326b | ||
|
bee00b9c58 | ||
|
00fd4d552a | ||
|
aa702cb97a | ||
|
31507139c8 | ||
|
2272d9ce99 | ||
|
e4f92461d3 | ||
|
4dd095da2a | ||
|
20289a1774 | ||
|
048e15a476 | ||
|
d5e32478c7 | ||
|
5010927559 | ||
|
b87b08ee6a | ||
|
7f1f3df020 | ||
|
fb56087a72 | ||
|
c8870e29fd | ||
|
45fd0d4852 | ||
|
12435d3e24 | ||
|
ab91343dd5 | ||
|
c6e8a06f10 | ||
|
a45c6245fe | ||
|
9b24aa1be3 | ||
|
43d96f0118 | ||
|
ce62e832ee | ||
|
d645e616fe | ||
|
bf1f384341 | ||
|
f1c4b97bc1 | ||
|
68a91bbbbb | ||
|
58ecfbc72b | ||
|
0bd127ad06 | ||
|
fb86d4291c | ||
|
b549f57228 | ||
|
e76b5442c1 | ||
|
aab2045959 | ||
|
56ca165063 | ||
|
5ea4bd10a4 | ||
|
596108d56e | ||
|
63ffc47aa5 | ||
|
b7fbe831c7 | ||
|
1663daebdc | ||
|
78d6e931ff | ||
|
9e2d84cb25 | ||
|
b78e4b59b9 | ||
|
99864fe428 | ||
|
e9b111aa8a | ||
|
3d062fdba7 | ||
|
eff626ee95 | ||
|
64713e23ea | ||
|
0f2180da3c | ||
|
051f37b3ff | ||
|
2206cf9dd8 | ||
|
88c9b1701f | ||
|
c6c5b5ee97 | ||
|
1d225d7fe2 | ||
|
d0a9ba392c | ||
|
4537c7d01d | ||
|
4dfb59e091 | ||
|
7f2309248f | ||
|
4698f7728a | ||
|
951146022f | ||
|
f9b855ea1d | ||
|
2aa1d05040 | ||
|
7310e16222 | ||
|
a68353f4eb | ||
|
af880305dd | ||
|
ab5d7c7f7c | ||
|
96d9d4aa9d | ||
|
cb17d422f3 | ||
|
afb852fdf1 | ||
|
d199d875bb | ||
|
1afa763999 | ||
|
45bd9e666f | ||
|
c45a4ac610 | ||
|
0972886b58 | ||
|
822f5c3409 | ||
|
b8bef58122 | ||
|
f98ded7ca7 | ||
|
60b762bb29 | ||
|
3c1f3692d4 | ||
|
f74ec2e75a | ||
|
f94cf1baa7 | ||
|
7aa50019a8 | ||
|
1f0b44d455 | ||
|
9ea5bf0899 | ||
|
047d7eb6f3 | ||
|
5f390b4f59 | ||
|
b7e978c8bb | ||
|
d0b5bda46d | ||
|
6171eab4f1 | ||
|
bcfdf0ac06 | ||
|
02e1132301 | ||
|
eceb4e2f09 | ||
|
4080018295 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
ACTIVATION
|
||||
.authcode
|
||||
*aax
|
||||
*jpg
|
||||
*json
|
||||
Audiobook/*
|
||||
|
338
AAXtoMP3
338
AAXtoMP3
@ -9,14 +9,14 @@ usage=$'\nUsage: AAXtoMP3 [--flac] [--aac] [--opus ] [--single] [--level <COMPRE
|
||||
[--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>]
|
||||
[--continue <CHAPTERNUMBER>] {FILES}\n'
|
||||
[--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
|
||||
dirNameScheme= # Custom directory naming scheme, default is $genre/$artist/$title
|
||||
customDNS=0
|
||||
fileNameScheme= # Custom file naming scheme, default is $title
|
||||
customFNS=0
|
||||
@ -31,6 +31,13 @@ continue=0 # Default off, If set Transcoding is skipped and cha
|
||||
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
|
||||
@ -41,8 +48,6 @@ 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.
|
||||
@ -52,7 +57,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 ) codec=copy; extension=m4a; mode=single; container=mp4; shift ;;
|
||||
-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.
|
||||
@ -77,12 +82,22 @@ while true; do
|
||||
-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.
|
||||
@ -192,14 +207,34 @@ 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
|
||||
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
|
||||
GREP=$(grep --version | grep -q GNU && echo "grep" || echo "ggrep")
|
||||
if ! [[ $(type -P "$GREP") ]]; then
|
||||
echo "$GREP (GNU grep) is not in your PATH"
|
||||
echo "Without it, this script will break."
|
||||
@ -207,9 +242,17 @@ if ! [[ $(type -P "$GREP") ]]; then
|
||||
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
|
||||
SED=$(sed --version 2>&1 | $GREP -q GNU && echo "sed" || echo "gsed")
|
||||
if ! [[ $(type -P "$SED") ]]; then
|
||||
echo "$SED (GNU sed) is not in your PATH"
|
||||
echo "Without it, this script will break."
|
||||
@ -219,7 +262,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:"
|
||||
@ -232,7 +275,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:"
|
||||
@ -290,12 +333,6 @@ if [ -z $auth_code ]; then
|
||||
auth_code=`head -1 ~/.authcode`
|
||||
fi
|
||||
fi
|
||||
# No point going on if no authcode found.
|
||||
if [ -z $auth_code ]; then
|
||||
echo "ERROR Missing authcode"
|
||||
echo "$usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# -----
|
||||
# Check the target dir for if set if it is writable
|
||||
@ -384,8 +421,8 @@ validate_aax() {
|
||||
# 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.
|
||||
output="$(ffprobe -loglevel warning -activation_bytes ${auth_code} -i "${media_file}" 2>&1)"
|
||||
# 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
|
||||
@ -398,7 +435,7 @@ validate_aax() {
|
||||
|
||||
# This is a big test only performed when the --validate switch is passed.
|
||||
if [[ "${VALIDATE}" == "1" ]]; then
|
||||
output="$(ffmpeg -hide_banner -activation_bytes ${auth_code} -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
|
||||
@ -413,12 +450,71 @@ validate_aax() {
|
||||
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"
|
||||
"$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()
|
||||
@ -432,6 +528,51 @@ save_metadata() {
|
||||
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"
|
||||
}
|
||||
@ -453,15 +594,59 @@ 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.
|
||||
@ -488,12 +673,14 @@ do
|
||||
album_artist="$(get_metadata_value album_artist)"
|
||||
fi
|
||||
fi
|
||||
title=$(get_metadata_value title | $SED 's/'\:'/'-'/g' | $SED 's/- /-/g' | xargs -0)
|
||||
title=${title:0:100}
|
||||
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
|
||||
@ -516,9 +703,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
|
||||
@ -531,8 +718,9 @@ do
|
||||
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
|
||||
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}"
|
||||
|
||||
@ -546,11 +734,12 @@ do
|
||||
# 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 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
|
||||
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 -activation_bytes "${auth_code}" -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))"
|
||||
@ -569,10 +758,10 @@ 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 -activation_bytes "${auth_code}" -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 \
|
||||
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 \
|
||||
-activation_bytes "${auth_code}" \
|
||||
${decrypt_param} \
|
||||
-i "${aax_file}" \
|
||||
-vn \
|
||||
-codec:a "${codec}" \
|
||||
@ -590,6 +779,8 @@ do
|
||||
-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
|
||||
@ -598,18 +789,38 @@ do
|
||||
# -----
|
||||
fi
|
||||
# Grab the cover art if available.
|
||||
cover_file="${output_directory}/cover.jpg"
|
||||
cover_file="${output_directory}/${currentFileNameScheme}.jpg"
|
||||
if [ "${continue}" == "0" ]; then
|
||||
if [ "$((${loglevel} > 1))" == "1" ]; then
|
||||
log "Extracting cover into ${cover_file}..."
|
||||
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
|
||||
</dev/null ffmpeg -loglevel error -activation_bytes "${auth_code}" -i "${aax_file}" -an -codec:v copy "${cover_file}"
|
||||
fi
|
||||
|
||||
extra_crop_cover=''
|
||||
cover_width=$(ffprobe -i "${cover_file}" 2>&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
|
||||
|
||||
# -----
|
||||
# OK now spit the file if that's what you want.
|
||||
# If we want multiple file we take the big mp3 and split it by chapter.
|
||||
# Not all audio encodings make sense with multiple chapter outputs. See options section
|
||||
# for more detail
|
||||
# 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"
|
||||
@ -684,34 +895,35 @@ do
|
||||
#ffmpeg version 4+ and on the output for all older versions.
|
||||
split_input=""
|
||||
split_output=""
|
||||
if [ "$(($(ffmpeg -version | head -1 | cut -d \ -f 3 | cut -d . -f 1) > 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}"
|
||||
fi
|
||||
|
||||
# Big Long chapter debug
|
||||
debug_vars "Chapter Variables:" cover_file chapter_start chapter_end chapternum chapterNameScheme chapter_title chapter_file
|
||||
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 \
|
||||
</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:s:v comment="Cover (front)" \
|
||||
-metadata track="${chapternum}" \
|
||||
-metadata title="${chapter_title}" \
|
||||
-metadata:s:a title="${chapter_title}" \
|
||||
-metadata title="${chapter}" \
|
||||
-metadata:s:a title="${chapter}" \
|
||||
-metadata:s:a track="${chapternum}" \
|
||||
-map_chapters -1 \
|
||||
-f ${container} \
|
||||
@ -742,12 +954,46 @@ do
|
||||
# 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
|
||||
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}/${fileNameScheme}.chapters.txt"
|
||||
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
|
||||
|
113
README.md
113
README.md
@ -1,8 +1,8 @@
|
||||
# AAXtoMP3
|
||||
The purpose of this software is to convert AAX files to common MP3, M4A, M4B, flac and ogg formats
|
||||
The purpose of this software is to convert AAX (or AAXC) files to common MP3, M4A, M4B, flac and ogg formats
|
||||
through a basic bash script frontend to FFMPEG.
|
||||
|
||||
Audible uses this file format to maintain DRM restrictions on their audio
|
||||
Audible uses the AAX file format to maintain DRM restrictions on their audio
|
||||
books and if you download your book through your library it will be
|
||||
stored in this format.
|
||||
|
||||
@ -13,33 +13,32 @@ create a method for you to download and store your books just in case
|
||||
Audible fails for some reason.
|
||||
|
||||
## Requirements
|
||||
* bash 4.3.42 or later tested
|
||||
* ffmpeg version 2.8.3 or later
|
||||
* libmp3lame (came from lame package on Arch, not sure where else this is stored)
|
||||
* grep Some OS distributions do not have it installed.
|
||||
* sed Some OS versions will need to install gnu sed.
|
||||
* bash 3.2.57 or later tested
|
||||
* ffmpeg version 2.8.3 or later (4.4 or later if the input file is `.aaxc`)
|
||||
* libmp3lame - (typically 'lame' in your system's package manager)
|
||||
* GNU grep - macOS or BSD users may need to install through package manager
|
||||
* GNU sed - see above
|
||||
* GNU find - see above
|
||||
* jq - only if `--use-audible-cli-data` is set or if converting an .aaxc file
|
||||
* mp4art used to add cover art to m4a and m4b files. Optional
|
||||
* mediainfo used to add additional media tags like narrator. Optional
|
||||
|
||||
## OSX
|
||||
Thanks to thibaudcolas, this script has been tested on OSX 10.11.6 El Capitan. YMMV, but it should work for
|
||||
conversions in OSX. It is recommended that you install GNU grep using 'brew install grep' for chapter padding to work.
|
||||
|
||||
## AUR
|
||||
Thanks to kbabioch, this script has also been packaged in the [AUR](https://aur.archlinux.org/packages/aaxtomp3-git/). Note that you will still need to extract your activation bytes before use.
|
||||
|
||||
## Usage(s)
|
||||
```
|
||||
bash AAXtoMP3 [-f|--flac] [-o|--opus] [-a|-aac] [-s|--single] [--level <COMPRESSIONLEVEL>] [-c|--chaptered] [-e:mp3] [-e:m4a] [-e:m4b] [-A|--authcode <AUTHCODE>] [-n|--no-clobber] [-t|--target_dir <PATH>] [-C|--complete_dir <PATH>] [-V|--validate] [-d|--debug] [-h|--help] [--continue <CHAPTERNUMBER>] <AAX INPUT_FILES>...
|
||||
bash AAXtoMP3 [-f|--flac] [-o|--opus] [-a|--aac] [-s|--single] [--level <COMPRESSIONLEVEL>] [-c|--chaptered] [-e:mp3] [-e:m4a] [-e:m4b] [-A|--authcode <AUTHCODE>] [-n|--no-clobber] [-t|--target_dir <PATH>] [-C|--complete_dir <PATH>] [-V|--validate] [--use-audible-cli-data]] [-d|--debug] [-h|--help] [--continue <CHAPTERNUMBER>] <AAX/AAXC INPUT_FILES>...
|
||||
```
|
||||
or if you want to get guided through the options
|
||||
```
|
||||
bash interactiveAAXtoMP3 [-a|--advanced] [-h|--help]
|
||||
```
|
||||
|
||||
* **<AAX INPUT_FILES>**... are considered input file(s), useful for batching!
|
||||
|
||||
## Options
|
||||
## Options for AAXtoMP3
|
||||
* **-f** or **--flac** Flac Encoding and as default produces a single file.
|
||||
* **-o** or **--opus** Ogg/Opus Encoding defaults to multiple file output by chapter. The extension is .ogg
|
||||
* **-a** or **--aac** AAC Encoding and produce a m4a single files output.
|
||||
* **-A** or **--authcode <AUTHCODE>** for this execution of the command use the provided <AUTHCODE> to decode the AAX file.
|
||||
* **-A** or **--authcode <AUTHCODE>** for this execution of the command use the provided <AUTHCODE> to decode the AAX file. Not needed if the source file is .aaxc.
|
||||
* **-n** or **--no-clobber** If set and the target directory already exists, AAXtoMP3 will exit without overwriting anything.
|
||||
* **-t** or **--target_dir <PATH>** change the default output location to the named <PATH>. Note the default location is ./Audiobook of the directory to which each AAX file resides.
|
||||
* **-C** or **--complete_dir <PATH>** a directory to place aax files after they have been decoded successfully. Note make a back up of your aax files prior to using this option. Just in case something goes wrong.
|
||||
@ -57,23 +56,33 @@ bash AAXtoMP3 [-f|--flac] [-o|--opus] [-a|-aac] [-s|--single] [--level <COMPRESS
|
||||
* **--dir-naming-scheme <STRING>** or **-D** Use a custom directory naming scheme, with variables. See [below](#custom-naming-scheme) for more info.
|
||||
* **--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.
|
||||
* **-h** or **--help** Get a help prompt.
|
||||
This script presents you the options you chose last time as default.
|
||||
When you get asked for the aax-file you may just drag'n'drop it to the terminal.
|
||||
|
||||
### [AUTHCODE]
|
||||
**Your** Audible auth code (it won't correctly decode otherwise) (required).
|
||||
### AUTHCODE
|
||||
**Your** Audible auth code (it won't correctly decode otherwise) (not required to decode the `aaxc` format).
|
||||
|
||||
#### Determining your own AUTHCODE
|
||||
You will need your authentication code that comes from Audible's servers. This
|
||||
will be used by ffmpeg to perform the initial audio convert. You can obtain
|
||||
this string from a tool like
|
||||
[audible-activator](https://github.com/inAudible-NG/audible-activator).
|
||||
[audible-activator](https://github.com/inAudible-NG/audible-activator) or like [audible-cli](https://github.com/mkb79/audible-cli).
|
||||
|
||||
#### Specifying the AUTHCODE.
|
||||
In order of __precidence__.
|
||||
1. __--authcode [AUTHCODE]__ The command line option. With the highest precedence.
|
||||
2. __.authcode__ If this file is placed in the current working directory and contains only the authcode it is used if the above is not.
|
||||
3. __~/.authcode__ a global config file for all the tools. And is used as the default if none of the above are specified.
|
||||
__Note:__ At least one of the above must be exist. The code must also match the encoding for the user that owns the AAX file(s). If the authcode does not match the AAX file no transcoding will occur.
|
||||
__Note:__ At least one of the above must be exist if converting `aax` files. The code must also match the encoding for the user that owns the AAX file(s). If the authcode does not match the AAX file no transcoding will occur.
|
||||
|
||||
### MP3 Encoding
|
||||
* This is the **default** encoding
|
||||
@ -145,8 +154,13 @@ 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/)
|
||||
#### FFMPEG,FFPROBE
|
||||
__Ubuntu, Linux Mint, Debian__
|
||||
```
|
||||
@ -154,6 +168,20 @@ 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:
|
||||
@ -188,16 +216,21 @@ __MacOS__
|
||||
brew install ffmpeg
|
||||
brew install gnu-sed
|
||||
brew install grep
|
||||
brew install findutils
|
||||
```
|
||||
|
||||
#### mp4art/mp4chaps
|
||||
_Note: This is an optional dependency._
|
||||
_Note: This is an optional dependency, required for adding cover art to m4a and b4b files only._
|
||||
|
||||
__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.
|
||||
@ -224,6 +257,42 @@ __MacOS__
|
||||
```
|
||||
brew install mediainfo
|
||||
```
|
||||
## AAXC files
|
||||
The AAXC format is a new Audible encryption format, meant to replace the old AAX.
|
||||
The encryption has been updated, and now to decrypt the file the authcode
|
||||
is not sufficient, we need two "keys" which are unique for each audiobook.
|
||||
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
|
||||
title, additional chapters division (Opening and End credits, Copyright and
|
||||
more). Those information are avaiable via a non-public audible API. This
|
||||
[repo](https://github.com/mkb79/Audible) provides a python API wrapper, and the
|
||||
[audible-cli](https://github.com/mkb79/audible-cli) packege makes easy to get
|
||||
more info. In particular the flags **--cover --cover-size 1215 --chapter**
|
||||
downloads a better-quality cover (.jpg) and detailed chapter infos (.json).
|
||||
More info are avaiable on the package page.
|
||||
|
||||
Some books might not be avaiable in the old `aax` format, but only in the newer
|
||||
`aaxc` format. In that case, you can use [audible-cli](https://github.com/mkb79/audible-cli)
|
||||
to download them. For example, to download all the books in your library in the newer `aaxc` format, as well as
|
||||
chapters's title and an HQ cover: `audible download --all --aaxc --cover --cover-size 1215 --chapter`.
|
||||
|
||||
To make AAXtoMP3 use the additional data, specify the **--use-audible-cli-data**
|
||||
flag: it expects the cover and the chapter files (and the voucher, if converting
|
||||
an aaxc file) to be in the same location of the AAX file. The naming of these
|
||||
files must be the one set by audible-cli. When converting aaxc files, the variable
|
||||
is automatically set, so be sure to follow the instructions in this paragraph.
|
||||
|
||||
For more information on how to use the `audible-cli` package, check out the git page [audible-cli](https://github.com/mkb79/audible-cli).
|
||||
|
||||
Please note that right now audible-cli is in dev stage, so keep in mind that the
|
||||
naming scheme of the additional files, the flags syntax and other things can
|
||||
change without warning.
|
||||
|
||||
|
||||
## Anti-Piracy Notice
|
||||
|
159
interactiveAAXtoMP3
Normal file
159
interactiveAAXtoMP3
Normal file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ===Note for contributors========================================================================================================================
|
||||
|
||||
# This script interactively asks the user for the options to call AAXtoMP3 with. This first version does not include all options of AAXtoMP3
|
||||
# since I tried to keep the dialog short, but I added an --advanced option, which is unused right now, but might be used in the future to add
|
||||
# more options which only show up if explicitely wanted.
|
||||
# If you want to add functionality please consider, whether the functionality you add might belong to the advanced options.
|
||||
|
||||
# ===Variables====================================================================================================================================
|
||||
|
||||
# Help message
|
||||
help=$'\nUsage: interactiveAAXtoMP3 [--advanced] [--help]\n
|
||||
--advanced More options
|
||||
--help Print this message\n'
|
||||
summary="" # This will contain a summary of the options allready set.
|
||||
call="./AAXtoMP3" # This will contain the call for AAXtoMP3.
|
||||
advanced=0 # Toggles advanced options on or off.
|
||||
|
||||
# ===Options======================================================================================================================================
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
# Advanced options.
|
||||
-a | --advanced ) advanced=1; shift ;;
|
||||
# Command synopsis.
|
||||
-h | --help ) echo -e "$help"; exit ;;
|
||||
# Anything else stops command line processing.
|
||||
* ) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ===Cross platform compatible use grep and sed===================================================================================================
|
||||
|
||||
# ===Detect which annoying version of grep we have===
|
||||
GREP=$(grep --version | grep -q GNU && echo "grep" || echo "ggrep")
|
||||
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 sed we have===
|
||||
SED=$(sed --version 2>&1 | $GREP -q GNU && echo "sed" || echo "gsed")
|
||||
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
|
||||
|
||||
# ===Get options from last time===================================================================================================================
|
||||
|
||||
# ===Set default values===
|
||||
lastcodec="mp3"
|
||||
lastcompression="4"
|
||||
lastchapters="yes"
|
||||
lastauthcode=""
|
||||
lastloglevel="1"
|
||||
|
||||
# ===Get Values from last time===
|
||||
if [ -f ".interactivesave" ]; then
|
||||
for ((i=1;i<=$(wc -l .interactivesave | cut -d " " -f 1);i++)) do
|
||||
line=$(head -$i .interactivesave | tail -1)
|
||||
case $(echo $line | cut -d " " -f 1 | $SED 's/.$//') in
|
||||
codec ) lastcodec="$(echo $line | cut -d " " -f 2)";;
|
||||
compression ) lastcompression="$(echo $line | cut -d " " -f 2)";;
|
||||
chapters ) lastchapters="$(echo $line | cut -d " " -f 2)";;
|
||||
authcode ) lastauthcode="$(echo $line | cut -d " " -f 2)";;
|
||||
loglevel ) lastloglevel="$(echo $line | cut -d " " -f 2)";;
|
||||
* ) rm .interactivesave; exit 1;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# ===Get options for AAXtoMP3=====================================================================================================================
|
||||
|
||||
# ===Codec===
|
||||
while true; do
|
||||
clear;
|
||||
read -e -p "codec (mp3/m4a/m4b/flac/aac/opus): " -i "$lastcodec" codec
|
||||
case "$codec" in
|
||||
mp3 ) summary="$summary""codec: $codec"; call="$call -e:mp3"; break;;
|
||||
m4a ) summary="$summary""codec: $codec"; call="$call -e:m4a"; break;;
|
||||
m4b ) summary="$summary""codec: $codec"; call="$call -e:m4b"; break;;
|
||||
flac ) summary="$summary""codec: $codec"; call="$call --flac"; break;;
|
||||
aac ) summary="$summary""codec: $codec"; call="$call --aac"; break;;
|
||||
opus ) summary="$summary""codec: $codec"; call="$call --opus"; break;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ===Compression===
|
||||
while true; do
|
||||
clear; echo -e "$summary"
|
||||
case "$codec" in
|
||||
mp3 ) maxlevel=9;;
|
||||
flac ) maxlevel=12;;
|
||||
opus ) maxlevel=10;;
|
||||
* ) break;;
|
||||
esac
|
||||
read -e -p "compression level (0-$maxlevel): " -i "$lastcompression" compression
|
||||
if [[ $compression =~ ^[0-9]+$ ]] && [[ "$compression" -ge "0" ]] && [[ "$compression" -le "$maxlevel" ]]; then
|
||||
summary="$summary""\ncompression level: $compression"
|
||||
call="$call --level $compression"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# ===Chapters===
|
||||
while true; do
|
||||
clear; echo -e "$summary"
|
||||
read -e -p "chapters (yes/no/chapternumber to continue with): " -i "$lastchapters" chapters
|
||||
case "$chapters" in
|
||||
^[0-9]+$ ) summary="$summary""\nchapters: $chapters"; call="$call -c --continue ${chapters}"; break;;
|
||||
yes ) summary="$summary""\nchapters: $chapters"; call="$call -c"; break;;
|
||||
no ) summary="$summary""\nchapters: $chapters"; call="$call -s"; break;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ===Authcode===
|
||||
if ! [ -r .authcode ] || [ -r ~/.authcode ]; then
|
||||
clear; echo -e "$summary"
|
||||
read -e -p "Authcode: " -i "$lastauthcode" authcode
|
||||
summary="$summary""\nauthcode: $authcode"
|
||||
call="$call -A $authcode"
|
||||
fi
|
||||
|
||||
# ===Loglevel===
|
||||
while true; do
|
||||
clear; echo -e "$summary"
|
||||
read -e -p "loglevel (0/1/2/3): " -i "$lastloglevel" loglevel
|
||||
if [[ $loglevel =~ ^[0-9]+$ ]] && [[ "$loglevel" -ge "0" ]] && [[ "$loglevel" -le "3" ]]; then
|
||||
summary="$summary""\nloglevel: $loglevel"
|
||||
call="$call -l $loglevel"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# ===File===
|
||||
clear; echo -e "$summary"
|
||||
read -p "aax-file: " file
|
||||
file="${file%\'}" #remove suffix ' if file is given via drag'n'drop
|
||||
file="${file#\'}" #remove prefix ' if file is given via drag'n'drop
|
||||
savefile="$summary"
|
||||
summary="$summary""\naax-file: $file"
|
||||
call="$call $(echo $file | $SED "s;~;$HOME;")"
|
||||
|
||||
# ===Summerize chosen options and call AAXtoMP3===================================================================================================
|
||||
|
||||
# ===Summary===
|
||||
clear; echo -e "$summary\n"
|
||||
echo -e "$call\n"
|
||||
|
||||
# ===Save chosen options===
|
||||
echo -e $savefile | $SED "s;\ level:;:;" > .interactivesave
|
||||
|
||||
# ===Call AAXtoMP3===
|
||||
$call
|
Loading…
x
Reference in New Issue
Block a user