mirror of
https://github.com/KrumpetPirate/AAXtoMP3.git
synced 2025-07-09 20:37:30 +02:00
Compare commits
205 Commits
Author | SHA1 | Date | |
---|---|---|---|
70ae2cef12 | |||
9058cf144e | |||
1e9c42aa83 | |||
82c0ef0f6b | |||
c3584923ca | |||
f6661862de | |||
b5408310a2 | |||
3c909baf5e | |||
012749071e | |||
36839646c4 | |||
68168f6495 | |||
aaaea1515f | |||
836c6b0722 | |||
4a8df563eb | |||
184cece622 | |||
1c4d67202a | |||
93478c797d | |||
6646b84e72 | |||
2805aca990 | |||
7bef1bd569 | |||
9e1b33904a | |||
fb48899cb7 | |||
239ae1aa3f | |||
fcef788bc4 | |||
33a8d254bd | |||
de3fbadab6 | |||
e6f34b5f97 | |||
2b31c7defa | |||
c4c5470b6b | |||
1e7802ac3f | |||
ad2bbe5da4 | |||
0a5813e7db | |||
ad86f187f8 | |||
ffbc592bf8 | |||
8f0f8ec3e1 | |||
7b602eaf5d | |||
bcaec020ce | |||
440c6302fb | |||
3fb377a107 | |||
ce3b5a5c18 | |||
4870dc334a | |||
712b69d8ec | |||
7be5ee8749 | |||
d11257410b | |||
8a82d9f661 | |||
db65efaf67 | |||
0c9fa05aab | |||
fd8a51d474 | |||
cbf75faa58 | |||
d56369d45a | |||
277bbaec13 | |||
84fb5bf004 | |||
82b865f55f | |||
87a7a53352 | |||
c0fa0489cd | |||
d16a47049b | |||
3b4648519b | |||
ffb7e40961 | |||
dc5309ebc3 | |||
9a674d0127 | |||
9e3a2a3787 | |||
9989aa5ad6 | |||
b9f21a54f1 | |||
a6f19f8aa1 | |||
d007e21227 | |||
32aacd2b42 | |||
c4f2da205a | |||
04b7d9ff45 | |||
2ef05cda00 | |||
84d708f217 | |||
60bf8b0d87 | |||
624ca434e0 | |||
71f54b35e5 | |||
a3ac4fb834 | |||
640ba915a9 | |||
2d5902ed6e | |||
1dfd8ae755 | |||
9f8cda5899 | |||
7b77b944cf | |||
7d7dd0f91c | |||
3edb1b2fc8 | |||
93ea984a53 | |||
29a6481664 | |||
010dd7fb2d | |||
abafa6285c | |||
5c2afd7d6c | |||
243289c3ab | |||
847f5fb721 | |||
13c240ab53 | |||
3c7a5f1ac1 | |||
06d49e9f27 | |||
8b0f9a4989 | |||
61cb5b43d8 | |||
0d5f3b4098 | |||
b506b7a12a | |||
09e82c0ed6 | |||
7a78db51a8 | |||
67a190b0a6 | |||
e9f06c8089 | |||
35308ccb11 | |||
4040fb7ab9 | |||
a92705fbbf | |||
8de33e9c10 | |||
a6da4412b8 | |||
3b66ef4443 | |||
89ab63a235 | |||
1c108acf4b | |||
fab6141ccf | |||
6d2768613c | |||
101861f41c | |||
1af3f0ebcc | |||
2f8bb35b68 | |||
8ebd16ae8b | |||
db48c6a970 | |||
03f1a58638 | |||
9d20609f3c | |||
71e140259b | |||
c2082ccfc0 | |||
de6d5db3f2 | |||
8327f15ac2 | |||
1727d10be3 | |||
09145b4df2 | |||
84335828d9 | |||
eaf75eb2e2 | |||
11292dc4c4 | |||
6d1a751b81 | |||
1f2cd830d6 | |||
67d4d05b52 | |||
6f343c9191 | |||
ac656f9723 | |||
f2c78da132 | |||
5e8cffd0ae | |||
73f8b3c1da | |||
fdf9a08f67 | |||
c3b53b6e56 | |||
5cca255ae5 | |||
e848404d06 | |||
e754e83b6b | |||
cd60926a58 | |||
2ac1ba67ec | |||
b952cab104 | |||
0efe3fa28e | |||
5ec1fcd2d9 | |||
66dc8953ad | |||
e69b212982 | |||
aa1509b6ca | |||
ccfaf07021 | |||
dee0d7e9a0 | |||
41a2803e18 | |||
10ca1e53e3 | |||
f976940798 | |||
710ce0dfbe | |||
8887bee900 | |||
6ed19fcde2 | |||
219255826b | |||
ee0c971544 | |||
a691b5fd54 | |||
db014f8f51 | |||
1d1f738713 | |||
120f02fe07 | |||
2ae7e326bb | |||
e3d9a9ea35 | |||
15945b18b1 | |||
1031e3ae1d | |||
626bab226a | |||
83e83d959f | |||
d7752b4779 | |||
214dd5a46c | |||
76baaac18a | |||
e5d1588825 | |||
639940a497 | |||
004094f842 | |||
30cbe5d0f0 | |||
cd0e5546f4 | |||
e5c45ed119 | |||
cfaa5b94ff | |||
5a7078bc99 | |||
c4eb954e11 | |||
8b391a0bd0 | |||
d0eb3540ed | |||
ab8db09eed | |||
bacdde31e3 | |||
0c2c439d55 | |||
ed084692a2 | |||
da3ca76930 | |||
a09e502574 | |||
304594e845 | |||
bcd6ffd14b | |||
1132602f65 | |||
176c30c42c | |||
de01589b94 | |||
972123a932 | |||
1ac66d1a94 | |||
15abb34c0e | |||
6c33459f24 | |||
f06b768c55 | |||
2fc7329bf5 | |||
f7c4d0dce8 | |||
1017f9a6e7 | |||
c500062c96 | |||
05274634d4 | |||
240068be87 | |||
ee01461f9a | |||
8002baf1c5 | |||
56b6b6ad98 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
ACTIVATION
|
||||
.authcode
|
||||
*aax
|
||||
Audiobook/*
|
||||
|
808
AAXtoMP3
808
AAXtoMP3
@ -1,100 +1,768 @@
|
||||
#!/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>]
|
||||
[--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
|
||||
|
||||
# -----
|
||||
# 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 ;;
|
||||
# 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
|
||||
|
||||
codec=libmp3lame
|
||||
extension=mp3
|
||||
mode=chaptered
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "Usage: bash AAXtoMP3.sh [--flac] [--single] AUTHCODE {FILES}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$1" = '--flac' ]]
|
||||
then
|
||||
codec=flac
|
||||
extension=flac
|
||||
shift
|
||||
fi
|
||||
|
||||
if [[ "$1" == '--single' ]]
|
||||
then
|
||||
mode=single
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ ! -f .authcode ]; then
|
||||
auth_code=$1
|
||||
shift
|
||||
else
|
||||
auth_code=`head -1 .authcode`
|
||||
fi
|
||||
# ========================================================================
|
||||
# Utility Functions
|
||||
|
||||
# -----
|
||||
# debug
|
||||
# debug "Some longish message"
|
||||
debug() {
|
||||
echo "$(date "+%F %T%z") ${1}"
|
||||
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
|
||||
|
||||
# ========================================================================
|
||||
# Variable validation
|
||||
|
||||
# -----
|
||||
# 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
|
||||
|
||||
# -----
|
||||
# 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
|
||||
# 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
|
||||
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.
|
||||
output="$(ffprobe -loglevel warning -activation_bytes ${auth_code} -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 -activation_bytes ${auth_code} -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
|
||||
}
|
||||
|
||||
# -----
|
||||
# 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"
|
||||
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
|
||||
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"
|
||||
normalize_whitespace "$(grep --max-count=1 --only-matching "${key} *: .*" "$metadata_file" | cut -d : -f 2 | sed -e 's#/##g;s/ (Unabridged)//' | tr -s '[:blank:]' ' ')"
|
||||
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]\+'
|
||||
get_metadata_value bitrate | $GREP --only-matching '[0-9]\+'
|
||||
}
|
||||
|
||||
normalize_whitespace() {
|
||||
echo $*
|
||||
}
|
||||
|
||||
for path
|
||||
# ========================================================================
|
||||
# Main Transcode Loop
|
||||
for aax_file
|
||||
do
|
||||
debug "Decoding ${path} with auth code ${auth_code}..."
|
||||
|
||||
save_metadata "${path}"
|
||||
genre=$(get_metadata_value genre)
|
||||
artist=$(get_metadata_value artist)
|
||||
title=$(get_metadata_value title)
|
||||
output_directory="$(dirname "${path}")/${genre}/${artist}/${title}"
|
||||
mkdir -p "${output_directory}"
|
||||
full_file_path="${output_directory}/${title}.${extension}"
|
||||
# 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.
|
||||
validate_aax "${aax_file}"
|
||||
if [[ ${VALIDATE} == "1" ]] ; then
|
||||
# Don't bother doing anything else with this file.
|
||||
continue
|
||||
fi
|
||||
|
||||
</dev/null ffmpeg -loglevel error -stats -activation_bytes "${auth_code}" -i "${path}" -vn -codec:a "${codec}" -ab "$(get_bitrate)k" -map_metadata -1 -metadata title="${title}" -metadata artist="${artist}" -metadata album_artist="$(get_metadata_value album_artist)" -metadata album="$(get_metadata_value album)" -metadata date="$(get_metadata_value date)" -metadata track="1/1" -metadata genre="${genre}" -metadata copyright="$(get_metadata_value copyright)" "${full_file_path}"
|
||||
# -----
|
||||
# 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 | $SED 's/'\:'/'-'/g' | $SED 's/- /-/g' | xargs -0)
|
||||
title=${title:0:100}
|
||||
bitrate="$(get_bitrate)k"
|
||||
album="$(get_metadata_value album)"
|
||||
album_date="$(get_metadata_value date)"
|
||||
copyright="$(get_metadata_value copyright)"
|
||||
|
||||
debug "Created ${full_file_path}."
|
||||
# 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
|
||||
|
||||
if [ "${mode}" == "chaptered" ]; then
|
||||
debug "Extracting chapter files from ${full_file_path}..."
|
||||
# Define the output_directory
|
||||
if [ "${customDNS}" == "1" ]; then
|
||||
currentDirNameScheme="$(eval echo "${dirNameScheme}")"
|
||||
else
|
||||
# The Default
|
||||
currentDirNameScheme="${genre}/${artist}/${title}"
|
||||
fi
|
||||
|
||||
while read -r -u9 first _ _ start _ end
|
||||
do
|
||||
if [[ "${first}" = "Chapter" ]]
|
||||
then
|
||||
read -r -u9 _
|
||||
read -r -u9 _ _ chapter
|
||||
chapter_file="${output_directory}/${title} - ${chapter}.${extension}"
|
||||
</dev/null ffmpeg -loglevel error -stats -i "${full_file_path}" -ss "${start%?}" -to "${end}" -codec:a copy -metadata track="${chapter}" "${chapter_file}"
|
||||
fi
|
||||
done 9< "$metadata_file"
|
||||
rm "${full_file_path}"
|
||||
debug "Done creating chapters. Chaptered files contained in ${output_directory}."
|
||||
# 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 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)"
|
||||
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 -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 \
|
||||
-stats \
|
||||
-activation_bytes "${auth_code}" \
|
||||
-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"
|
||||
if [ "${continue}" == "0" ]; then
|
||||
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
|
||||
# -----
|
||||
# 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" ]; 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
|
||||
|
||||
cover_path="${output_directory}/cover.jpg"
|
||||
debug "Extracting cover into ${cover_path}..."
|
||||
</dev/null ffmpeg -loglevel error -activation_bytes "${auth_code}" -i "${path}" -an -codec:v copy "${cover_path}"
|
||||
debug "Done."
|
||||
rm "${metadata_file}"
|
||||
# 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 | head -1 | cut -d \ -f 3 | cut -d . -f 1) > 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
|
||||
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}" \
|
||||
${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_title}" \
|
||||
-metadata:s:a title="${chapter_title}" \
|
||||
-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}/${fileNameScheme}.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
|
||||
|
218
README.md
218
README.md
@ -1,5 +1,5 @@
|
||||
# AAXtoMP3
|
||||
The purpose of this software is to convert AAX files to a more common MP3 format
|
||||
The purpose of this software is to convert AAX 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
|
||||
@ -12,48 +12,222 @@ your **personal** Audible account. The purpose of this software is to
|
||||
create a method for you to download and store your books just in case
|
||||
Audible fails for some reason.
|
||||
|
||||
## Setup
|
||||
You will need your four byte 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).
|
||||
|
||||
## 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.
|
||||
* 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.
|
||||
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
|
||||
## Usage(s)
|
||||
```
|
||||
bash AAXtoMP3 <AUTHCODE> {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] [-d|--debug] [-h|--help] [--continue <CHAPTERNUMBER>] <AAX INPUT_FILES>...
|
||||
```
|
||||
* AUTHCODE: **your** Audible auth code (it won't correctly decode otherwise) (required)
|
||||
* Everything else is considered an input file, useful for batching!
|
||||
|
||||
You can also convert the output to FLAC encoding instead of MP3 by doing the following *in order*:
|
||||
```
|
||||
bash AAXtoMP3 --flac <AUTHCODE> {INPUT_FILES}
|
||||
```
|
||||
Note that FLAC encoding is typically a little faster, at the cost of compatibility with some players.
|
||||
* **<AAX INPUT_FILES>**... are considered input file(s), useful for batching!
|
||||
|
||||
If you wish to convert to a single file you can add --single to the input. This will prevent chaptered content from being extracted.
|
||||
## Options
|
||||
* **-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.
|
||||
* **-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.
|
||||
* **-V** or **--validate** Perform 2 validation tests on the supplied aax files. This is more extensive than the normal validation as we attempt to transcode the aax file to a null file. This can take a long period of time. However it is useful when inspecting a large set of aax files prior to transcoding. As download errors are common with Audible servers.
|
||||
* **-e:mp3** Identical to defaults.
|
||||
* **-e:m4a** Create a m4a audio file. This is identical to --aac
|
||||
* **-e:m4b** Create a m4b audio file. This is the book version of the m4a format.
|
||||
* **-s** or **--single** Output a single file for the entire book. If you only want a single ogg file for instance.
|
||||
* **-c** or **--chaptered** Output a single file per chapter. The `--chaptered` will only work if it follows the `--aac -e:m4a -e:m4b --flac` options.
|
||||
* **--continue <CHAPTERNUMBER>** If the splitting into chapters gets interrupted (e.g. by a weak battery on your laptop) you can go on where the process got interrupted. Just delete the last chapter (which was incompletely generated) and redo the task with "--continue <CHAPTERNUMBER>" where CHAPTERNUMBER is the chapter that got interrupted.
|
||||
* **--level <COMPRESSIONLEVEL>** Set compression level. May be given for mp3, flac and opus.
|
||||
* **--keep-author <FIELD>** If a book has multiple authors and you don't want all of them in the metadata, with this flag you can specify a specific author (1 is the first, 2 is the second...) to keep while discarding the others.
|
||||
* **--author <AUTHOR>** Manually set the author metadata field, useful if you have multiple books of the same author but the name reported is different (eg. spacing, accents..). Has precedence over `--keep-author`.
|
||||
* **-l** or **--loglevel <LOGLEVEL>** Set loglevel: 0 = progress only, 1 (default) = more information, output of chapter splitting progress is limitted to a progressbar, 2 = more information, especially on chapter splitting, 3 = debug mode
|
||||
* **--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.
|
||||
|
||||
Additionally, if you have a .authcode file available in the current working directory, it will read the first line of
|
||||
that line and treat it like your auth_code. When you do this you do not need to specify an AUTHCODE input.
|
||||
|
||||
Here is the full usage (NOTE: Order matters!)
|
||||
### [AUTHCODE]
|
||||
**Your** Audible auth code (it won't correctly decode otherwise) (required).
|
||||
|
||||
#### 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).
|
||||
|
||||
#### 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.
|
||||
|
||||
### MP3 Encoding
|
||||
* This is the **default** encoding
|
||||
* Produces 1 or more mp3 files for the AAX title.
|
||||
* The default mode is **chaptered**
|
||||
* If you want a mp3 file per chapter do not use the **--single** option.
|
||||
* A m3u playlist file will also be created in this instance in the case of **default** chaptered output.
|
||||
* **--level** has to be in range 0-9, where 9 is fastest and 0 is highest quality. Please note: The quality can **never** become higher than the qualitiy of the original aax file!
|
||||
|
||||
### Ogg/Opus Encoding
|
||||
* Can be done by using the **-o** or **--opus** command line switches
|
||||
* The default mode is **chaptered**
|
||||
* Opus coded files are stored in the ogg container format for better compatibility.
|
||||
* **--level** has to be in range 0-10, where 0 is fastest and 10 is highest quality. Please note: The quality can **never** become higher than the qualitiy of the original aax file!
|
||||
|
||||
### AAC Encoding
|
||||
* Can be done by using the **-a** or **--aac** command line switches
|
||||
* The default mode is **single**
|
||||
* Designed to be the successor of the MP3 format
|
||||
* Generally achieves better sound quality than MP3 at the same bit rate.
|
||||
* This will only produce 1 audio file as output.
|
||||
|
||||
### FLAC Encoding
|
||||
* Can be done by using the **-f** or **--flac** command line switches
|
||||
* The default mode is **single**
|
||||
* FLAC is an open format with royalty-free licensing
|
||||
* This will only produce 1 audio file as output. If you want a flac file per chapter do use **-c** or **--chaptered**.
|
||||
* **--level** has to be in range 0-12, where 0 is fastest and 12 is highest compression. Since flac is lossless, the quality always remains the same.
|
||||
|
||||
### M4A and M4B Containers
|
||||
* These containers were created by Apple Inc. They were meant to be the successor to mp3.
|
||||
* M4A is a container that is meant to hold music and is typically of a higher bitrate.
|
||||
* M4B is a container that is meant to hold audiobooks and is typically has bitrates of 64k and 32k.
|
||||
* Both formats are chaptered
|
||||
* Both support coverart internal
|
||||
* The default mode is **single**
|
||||
|
||||
### Validating AAX files
|
||||
* The **--validate** option will result in only a validation pass over the supplied aax file(s). No transcoding will occur. This is useful when you wish to ensure you have a proper download of your personal Audible audio books. With this option all supplied books are validated.
|
||||
* If you do NOT supply the **--validate** option all audio books are still validated when they are processed. However if there is an invalid audio book in the supplied list of books the processing will stop at that point.
|
||||
* A third test is performed on the file where the entire file is inspected to see if it is valid. This is a lengthy process. However it will not break the script when an invalid file is found.
|
||||
* The 3 test current are:
|
||||
1. aax present
|
||||
1. meta data header in file is valid and complete
|
||||
1. entire file is valid and complete. _only executed with the **--validate** option._
|
||||
|
||||
### Defaults
|
||||
* Default out put directory is the base directory of each file listed. Plus the genre, Artist and Title of the Audio Book.
|
||||
* The default codec is mp3
|
||||
* The default output is by chapter.
|
||||
|
||||
### Custom naming scheme
|
||||
The following flags can modify the default naming scheme:
|
||||
* **--dir-naming-scheme** or **-D**
|
||||
* **--file-naming-scheme** or **-F**
|
||||
* **--chapter-naming-scheme**
|
||||
|
||||
Each flag takes a string as argument. If the string contains a variable defined in the script (eg. artist, title, chapter, narrator...), the corresponding value is used.
|
||||
The default options correspond to the following flags:
|
||||
* `--dir-naming-scheme '$genre/$artist/$title'`
|
||||
* `--file-naming-scheme '$title'`
|
||||
* `--chapter-naming-scheme '$title-$(printf %0${#chaptercount}d $chapternum) $chapter'`
|
||||
|
||||
Additional notes:
|
||||
* If a command substitution is present in the passed string, (for example `$(printf %0${#chaptercount}d $chapternum)`, used to pad with zeros the chapter number), the commands are executed.
|
||||
So you can use `--dir-naming-scheme '$(date +%Y)/$artist'`, but using `--file-naming-scheme '$(rm -rf /)'` is a really bad idea. Be careful.
|
||||
* You can use basic text, like `--dir-naming-scheme 'Converted/$title'`
|
||||
* You can also use shell variables as long as you escape them properly: `CustomGenre=Horror ./AAXtoMP3 --dir-naming-scheme "$CustomGenre/\$artist/\$title" *.aax`
|
||||
* 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'`
|
||||
|
||||
### Installing Dependencies.
|
||||
#### FFMPEG,FFPROBE
|
||||
__Ubuntu, Linux Mint, Debian__
|
||||
```
|
||||
bash AAXtoMP3 [--flac] [--single] AUTHCODE {FILES}
|
||||
sudo apt-get update
|
||||
sudo apt-get install ffmpeg libav-tools x264 x265 bc
|
||||
```
|
||||
|
||||
__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:
|
||||
```
|
||||
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
|
||||
```
|
||||
Afterwards use the package manager to install ffmpeg:
|
||||
```
|
||||
sudo dnf install ffmpeg
|
||||
```
|
||||
|
||||
__RHEL or compatible like CentOS__
|
||||
|
||||
RHEL version 6 and 7 are currently able to use rpm fusion.
|
||||
In order to use rpm fusion you have to enable EPEL, see http://fedoraproject.org/wiki/EPEL
|
||||
|
||||
Add the rpm fusion repositories in version 6
|
||||
```
|
||||
sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-6.noarch.rpm https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-6.noarch.rpm
|
||||
```
|
||||
or version 7:
|
||||
```
|
||||
sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-7.noarch.rpm
|
||||
```
|
||||
then install ffmpeg:
|
||||
```
|
||||
sudo yum install ffmpeg
|
||||
```
|
||||
|
||||
__MacOS__
|
||||
```
|
||||
brew install ffmpeg
|
||||
brew install gnu-sed
|
||||
brew install grep
|
||||
```
|
||||
|
||||
#### mp4art/mp4chaps
|
||||
_Note: This is an optional dependency._
|
||||
|
||||
__Ubuntu, Linux Mint, Debian__
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install mp4v2-utils
|
||||
```
|
||||
__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.
|
||||
yum install mp4v2-utils
|
||||
```
|
||||
__MacOS__
|
||||
```
|
||||
brew install mp4v2
|
||||
```
|
||||
|
||||
#### mediainfo
|
||||
_Note: This is an optional dependency._
|
||||
|
||||
__Ubuntu, Linux Mint, Debian__
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install mediainfo
|
||||
```
|
||||
__CentOS, RHEL & Fedora__
|
||||
```
|
||||
yum install mediainfo
|
||||
```
|
||||
__MacOS__
|
||||
```
|
||||
brew install mediainfo
|
||||
```
|
||||
|
||||
|
||||
## Anti-Piracy Notice
|
||||
Note that this project does NOT ‘crack’ the DRM. It simply allows the user to
|
||||
Note that this project **does NOT ‘crack’** the DRM. It simply allows the user to
|
||||
use their own encryption key (fetched from Audible servers) to decrypt the
|
||||
audiobook in the same manner that the official audiobook playing software does.
|
||||
|
||||
|
1
_config.yml
Normal file
1
_config.yml
Normal file
@ -0,0 +1 @@
|
||||
theme: jekyll-theme-slate
|
Reference in New Issue
Block a user