├── .gitignore ├── LICENSE ├── README.md └── drmfreeaudible.sh /.gitignore: -------------------------------------------------------------------------------- 1 | */* 2 | *.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DRM free Audible 2 | 3 | Outputs DRM free copies of encrypted Audible AAX audiobooks in M4B and/or per chapter MP3 with M3U playlist. Retains all metadata including cover image. 4 | 5 | Accepts Audible AAX and unencrypted M4B (for MP3 conversion) 6 | 7 | ## Requirements 8 | AAX input files require Audible activation bytes placed with this script in a file named 'bytes.txt' or specified on command line using --bytes 9 | 10 | Can be obtained using https://github.com/inAudible-NG/audible-activator 11 | 12 | Dependencies : ffmpeg, AtomicParsley, jq, lame, GNU Parallel 13 | 14 | `sudo apt install ffmpeg libavcodec-extra atomicparsley jq parallel` 15 | 16 | 17 | ## Usage 18 | `./drmfreeaudible.sh [audible.aax|book.m4b] [input options] [output options]` 19 | 20 | [input options] 21 | 22 | * **--bytes=XXXXX** Audible activation bytes. 23 | * **--bytes=file.txt** File containing Audible activation bytes. 24 | 25 | [output options] (at least --m4b or --mp3 is required) 26 | * **--dryrun** Don't output or encode anything. Useful for previewing a batch job and identifying inputfiles with (some) errors. 27 | * **--noparallel** Don't use GNU Parallel for encoding MP3, much slower. Handy if you're using the machine while it processes. 28 | * **--reencode** Reencode output M4B. SLOW. Useful for reducing final file size, replacing files itunes refuses to play (or mistakes for LIVE\Podcast), and fixing files with errors. (presumes --m4b) 29 | * **--m4b** Output M4B Audiobook format. One file with chapters & cover. 30 | * **--m4bbitrate=** Set the bitrate used by --reencode (defaults to 64k). 31 | * **--mp3** Output MP3 one file per-chapter with M3U. Implied if passed an M4B file. 32 | * **--mp3bitrate=** Set the MP3 encode bitrate (defaults to 64k). 33 | 34 | 35 | ## Example Usage 36 | Create DRM Free M4B file from Audible AAX (with bytes in bytes.txt file) 37 | 38 | `./drmfreeaudible.sh book.aax --m4b` 39 | 40 | Batch process multiple Audible AAX files (with bytes in bytes.txt file) 41 | 42 | `./drmfreeaudible.sh ./my_audiable_books/*.aax --m4b` 43 | 44 | Create DRM free M4b and MP3 set from Audible AAX with bytes on cmd line. 45 | 46 | `./drmfreeaudible.sh book.aax --bytes=XXXXXX --m4b --mp3` 47 | 48 | Create per chapter MP3 with low bitrate from M4B file (no bytes required) 49 | 50 | `./drmfreeaudible.sh book.m4b --mp3 --mp3bitrate=32k` 51 | 52 | For unattened batch processing (*.aax or *.m4b as input) do a --dryrun and replace any files that show error messages if possible .. or just YOLO. Use of --reencode is reccomended as it maybe might clean up and fix some issues with source files. 53 | 54 | Some M4B files will show in iTunes as being LIVE\Podcast and not show correct run time or chapters. --reeconde corrects this. 55 | 56 | 57 | ## Installation notes (including Windows WSL) 58 | 59 | Windows users must install the WSL (Windows subsystem for Linux) from Microsoft first. See https://docs.microsoft.com/en-us/windows/wsl/install-win10 60 | 61 | This assumes you're using a debian derived distribution (or for Windows WSL users, have installed from from the Windows Store) such as Ubuntu. 62 | 63 | Start a Linux/WSL terminal and install the dependencies. 64 | 65 | `sudo apt install git ffmpeg libavcodec-extra atomicparsley jq parallel` 66 | 67 | Clone this repository. 68 | 69 | `git clone https://github.com/0xc0ffea/drmfreeaudible.git` 70 | 71 | Change to the directory you cloned the repo into 72 | 73 | `cd drmfreeaudible` 74 | 75 | If you wish to work with Audible AAX files, follow instructions above for obtaining your activation bytes and place them into a file named bytes.txt 76 | 77 | Use the script as per the Example Usage above. 78 | 79 | The finished script output is placed in a 'Book Title' named folder in the folder containing this sctipt. 80 | 81 | For Windows users, this will be in either (paste into Windows Explorer address bar) 82 | 83 | `%localappdata%\Lxss` 84 | 85 | or 86 | 87 | `\\wsl$` 88 | 89 | Then navigate to the folder you installed the script. Typically under 90 | 91 | `_your_distro_name_/home/_your_username_/drmfreeaudible` 92 | 93 | 94 | 95 | ## Anti-Piracy Notice 96 | 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. 97 | 98 | Please only use this application for gaining full access to your own audiobooks for archiving/conversion/convenience. DeDRMed audiobooks should not be uploaded to open servers, torrents, or other methods of mass distribution. No help will be given to people doing such things. Authors, retailers, and publishers all need to make a living, so that they can continue to produce audiobooks for us to hear, and enjoy. Don’t be a parasite. 99 | 100 | Borrowed from https://apprenticealf.wordpress.com/ 101 | -------------------------------------------------------------------------------- /drmfreeaudible.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SECONDS=0 3 | HELP="\ 4 | Outputs DRM free copies of encrypted Audible AAX audiobooks in M4B 5 | and/or per chapter MP3 with M3U playlist. 6 | Retains all metadata including cover image. 7 | 8 | Accepts Audible AAX and unencrypted M4B (for MP3 conversion) 9 | 10 | Requirements 11 | ============ 12 | AAX input files require Audible activation bytes placed with this script 13 | in a file named 'bytes.txt' or specified on command line using --bytes 14 | Can be obtained using https://github.com/inAudible-NG/audible-activator 15 | 16 | Dependencies : ffmpeg, AtomicParsley, jq, lame, GNU Parallel 17 | sudo apt install ffmpeg libavcodec-extra atomicparsley jq parallel 18 | 19 | 20 | Usage 21 | ===== 22 | ./${0##*/} [audible.aax|book.m4b] [input options] [output options] 23 | [input options] 24 | 25 | --bytes=XXXXX Audible activation bytes. 26 | --bytes=file.txt File containing Audible activation bytes. 27 | 28 | [output options] (at least --m4b or --mp3 is required) 29 | --dryrun Don't output or encode anything. 30 | Useful for previewing a batch job and identifying 31 | inputfiles with (some) errors. 32 | --noparallel Don't use GNU Parallel for encoding MP3, much slower but 33 | handy if you're using the machine while it processes. 34 | --reencode Reencode output M4B (slow, defaults to 64K) 35 | Useful for reducing final file size, replacing files itunes 36 | refuses to play (or mistakes for LIVE\Podcast), and fixing 37 | files with errors. (presumes --m4b) 38 | --m4b M4B Audiobook format. One file with chapters & cover. 39 | --m4bbitrate= Set the bitrate used by --reencode (defaults to 64k). 40 | --mp3 MP3 one file per-chapter with M3U. 41 | Implied if passed an M4B file. 42 | --mp3bitrate= Set the MP3 encode bitrate (defaults to 64k). 43 | 44 | 45 | Example Usage 46 | ============= 47 | Create DRM Free M4B file from Audible AAX (with bytes in bytes.txt file) 48 | ./${0##*/} book.aax --m4b 49 | 50 | Batch process multiple Audible AAX files (with bytes in bytes.txt file) 51 | ./${0##*/} ./my_audiable_books/*.aax --m4b 52 | 53 | Create DRM free M4b and MP3 set from Audible AAX with bytes on cmd line. 54 | ./${0##*/} book.aax --bytes=XXXXXX --m4b --mp3 55 | 56 | Create per chapter MP3 with low bitrate from M4B file (no bytes required) 57 | ./${0##*/} book.m4b --mp3 --mp3bitrate=32k 58 | 59 | For unattened batch processing (*.aax or *.m4b as input) do a --dryrun and 60 | replace any files that show error messages if possible .. or just YOLO. Use 61 | of --reencode is reccomended as it maybe might clean up and fix some issues 62 | with source files. 63 | 64 | 65 | Anti-Piracy Notice 66 | ================== 67 | Note that this project does NOT ‘crack’ the DRM. It simply allows the user 68 | to use their own encryption key (fetched from Audible servers) to decrypt the 69 | audiobook in the same manner that the official audiobook playing software does. 70 | 71 | Please only use this application for gaining full access to your own audiobooks 72 | for archiving/conversion/convenience. DeDRMed audiobooks should not be uploaded 73 | to open servers, torrents, or other methods of mass distribution. No help will 74 | be given to people doing such things. Authors, retailers, and publishers all 75 | need to make a living, so that they can continue to produce audiobooks for us 76 | to hear, and enjoy. Don’t be a parasite. 77 | 78 | Borrowed from https://apprenticealf.wordpress.com/ 79 | " 80 | declare -- BYTESFILE="$PWD/bytes.txt" 81 | declare -i DRYRUN=0 82 | declare -r FFMPEG_LOGLEVEL="-loglevel error" 83 | declare -i INPUT_AAX=0 84 | declare -a INPUT_FILES=() 85 | declare -i LOAD_BYTES=1 86 | declare -i OUTPUT_M4B=0 87 | declare -- OUTPUT_M4B_BITDEPTH="64k" 88 | declare -i OUTPUT_M4BRECODE=0 89 | declare -i OUTPUT_MP3=0 90 | declare -- OUTPUT_MP3_BITDEPTH="64k" 91 | declare -i OUTPUT_MP3_PARALLEL=1 92 | 93 | 94 | for arg in "$@" 95 | do 96 | case $arg in 97 | --bytes=*) 98 | if [[ -e "${arg#*=}" ]]; then 99 | BYTESFILE=${arg#*=} 100 | else 101 | LOAD_BYTES=0 102 | BYTES=${arg#*=} 103 | fi 104 | shift 105 | ;; 106 | --dryrun) 107 | DRYRUN=1 108 | shift 109 | ;; 110 | --noparallelmp3) 111 | OUTPUT_MP3_PARALLEL=0 112 | shift 113 | ;; 114 | --noparallel) 115 | OUTPUT_MP3_PARALLEL=0 116 | shift 117 | ;; 118 | --reencode) 119 | OUTPUT_M4BRECODE=1 120 | OUTPUT_M4B=1 121 | shift 122 | ;; 123 | -h|--help) 124 | echo -e "$HELP" 125 | exit 1 126 | shift 127 | ;; 128 | --m4b) 129 | OUTPUT_M4B=1 130 | shift 131 | ;; 132 | --m4bbitrate=*) 133 | OUTPUT_M4B_BITDEPTH=${arg#*=} 134 | shift 135 | ;; 136 | --mp3) 137 | OUTPUT_MP3=1 138 | shift 139 | ;; 140 | --mp3bitrate=*) 141 | OUTPUT_MP3_BITDEPTH=${arg#*=} 142 | shift 143 | ;; 144 | *) 145 | if [[ -f "$arg" ]]; then 146 | if [[ "$arg" == *".aax" ]]; then 147 | INPUT_AAX=1 148 | INPUT_FILES+=( "$arg" ) 149 | elif [[ "$arg" == *".m4b" ]]; then 150 | OUTPUT_MP3=1 151 | INPUT_FILES+=( "$arg" ) 152 | fi 153 | elif [[ -d "$arg" ]]; then 154 | # ignore directories ... 155 | # or add recusion here later 156 | # (mwhahahahaa) 157 | echo "- Ignoring directory '"$arg"'" 158 | else 159 | echo -e "\ 160 | Unknown argument '"$arg"'. 161 | Try './${0##*/} --help' for usage. 162 | " 163 | exit 1 164 | fi 165 | shift 166 | ;; 167 | esac 168 | done 169 | 170 | # no output set? 171 | if [[ "$OUTPUT_M4B" == 0 ]] && [[ "$OUTPUT_MP3" == 0 ]]; then 172 | echo -e "\ 173 | No output formats specified (--m4b,--mp3) 174 | See './${0##*/} --help' for usage. 175 | " 176 | exit 1 177 | fi 178 | 179 | 180 | # no source file? 181 | #if [ ! -f "$1" ]; then 182 | if [[ ${#INPUT_FILES[@]} == 0 ]]; then 183 | echo -e "\ 184 | No .aax or .m4b input files specified. 185 | See './${0##*/} --help' for usage. 186 | " 187 | exit 1 188 | fi 189 | 190 | # read in activation bytes if needed 191 | if [[ "$LOAD_BYTES" == 1 ]]; then 192 | if [[ "$INPUT_AAX" == 1 ]]; then 193 | if [[ -e "$BYTESFILE" ]]; then 194 | BYTES=$(sed '1q;d' "$BYTESFILE") 195 | echo "- Loaded Audible activation bytes." 196 | else 197 | echo -e "\ 198 | 199 | Could not load Audible activation bytes. 200 | 201 | AAX input files require Audible activation bytes placed with this script 202 | in a file named 'bytes.txt' or specified on command like using --bytes 203 | Can be obtained using https://github.com/inAudible-NG/audible-activator 204 | 205 | " 206 | exit 1 207 | fi 208 | fi 209 | fi 210 | 211 | #set ffmepeg abytes argument block 212 | if [ -z "$BYTES" ]; then 213 | ABYTES="" 214 | else 215 | ABYTES="-activation_bytes $BYTES" 216 | fi 217 | 218 | 219 | #Vroom Vrooom 220 | for INPUT_FILE in "${INPUT_FILES[@]}"; do 221 | SPLIT=$SECONDS 222 | # temporary folder. 223 | WORKPATH=$(mktemp -d -t ${0##*/}-XXXXXXXXXX) 224 | 225 | # Book info 226 | BOOKTITLE=$(ffprobe -v quiet -show_format $ABYTES "$INPUT_FILE" | grep "TAG:title" | cut -d"=" -f2 | tr -d '"') 227 | AUTHOR=$(ffprobe -v quiet -show_format $ABYTES "$INPUT_FILE" | grep "TAG:artist" | cut -d"=" -f2 | tr -d '"') 228 | YEAR=$(ffprobe -v quiet -show_format $ABYTES "$inpuINPUT_FILEtfile" | grep "TAG:date" | cut -d"=" -f2 | tr -d '"') 229 | COMMENT=$(ffprobe -v quiet -show_format $ABYTES "$INPUT_FILE" | grep "TAG:comment" | cut -d"=" -f2 | tr -d '"') 230 | ffmpeg $FFMPEG_LOGLEVEL $ABYTES -i "$INPUT_FILE" -f ffmetadata "$WORKPATH/metadata.txt" 231 | ARTIST_SORT=$(sed 's/.*=\(.*\)/\1/' <<<$(cat "$WORKPATH/metadata.txt" | grep -m 1 ^sort_artist | tr -d '"')) 232 | ALBUM_SORT=$(sed 's/.*=\(.*\)/\1/' <<<$(cat "$WORKPATH/metadata.txt" | grep -m 1 ^sort_album | tr -d '"')) 233 | 234 | # If a title begins with A, An, or The, we want to rename it so it sorts well 235 | TOKENWORDS=("A" "An" "The") 236 | FSBOOKTITLE="$BOOKTITLE" 237 | FSAUTHOR="$AUTHOR" 238 | for i in "${TOKENWORDS[@]}"; do 239 | if [[ "$FSBOOKTITLE" == "$i "* ]]; then 240 | FSBOOKTITLE=$(echo $FSBOOKTITLE | perl -pe "s/^$i //") 241 | # If book has a subtitle, we want the token word to go right before it 242 | if [[ "$FSBOOKTITLE" == *": "* ]]; then 243 | FSBOOKTITLE=$(echo $FSBOOKTITLE | perl -pe "s/: /, $i: /") 244 | break 245 | fi 246 | FSBOOKTITLE="$FSBOOKTITLE, $i" 247 | break 248 | fi 249 | done 250 | # Replace special characters in Book Title and Author Name with a - to make 251 | # them file name safe. I'm not actually using the Author Name in the file 252 | # name, but I figured it'd be nice to make it easy to use. 253 | FSBOOKTITLE=$(echo $FSBOOKTITLE | perl -pe 's/[<>:"\/\\\|\?\*]/-/g') 254 | FSAUTHOR=$(echo $FSAUTHOR | perl -pe 's/[<>:"\/\\\|\?\*]/-/g') 255 | 256 | # chapters 257 | ffprobe $FFMPEG_LOGLEVEL $ABYTES -i "$INPUT_FILE" -print_format json -show_chapters -loglevel error -sexagesimal > "$WORKPATH/chapters.json" 258 | readarray -t ID <<< $(jq -r '.chapters[].id' "$WORKPATH/chapters.json") 259 | readarray -t START_TIME <<< $(jq -r '.chapters[].start_time' "$WORKPATH/chapters.json") 260 | readarray -t END_TIME <<< $(jq -r '.chapters[].end_time' "$WORKPATH/chapters.json") 261 | readarray -t TITLE <<< $(jq -r '.chapters[].tags.title' "$WORKPATH/chapters.json" | tr -d '"') 262 | 263 | # Echo title (author) - runtime 264 | echo "$FSBOOKTITLE ($FSAUTHOR) - ${END_TIME[-1]}" 265 | 266 | # extract cover image 267 | COVERIMG=$WORKPATH/cover.png 268 | echo "- Extracting Cover Image" 269 | JOBCOVER="$WORKPATH/jobs_covers.sh" 270 | echo "#!/bin/bash" | tee "$JOBCOVER" 1> /dev/null 271 | chmod +x "$JOBCOVER" 272 | ffmpeg $FFMPEG_LOGLEVEL -y $ABYTES -i "$INPUT_FILE" "$COVERIMG" 273 | 274 | 275 | # M4B (direct copy with all metadata sans encryption - cover retained from original file) 276 | # use this as the source file from here on out 277 | # (mainly as /tmp is on an SSD and there will be a LOT of read threads) 278 | mkdir "$WORKPATH/m4b" 279 | # old way, did not copy over the cover 280 | #ffmpeg $FFMPEG_LOGLEVEL $ABYTES -i "$1" -vn -c:a copy "$DRMFREE" 281 | if [[ "$OUTPUT_M4BRECODE" == 0 ]]; then 282 | # ffmpeg native 283 | echo "- Creating \"$FSBOOKTITLE.m4b\"" 284 | if [[ "$DRYRUN" == 0 ]]; then 285 | DRMFREE="$WORKPATH/m4b/$FSBOOKTITLE.m4b" 286 | ffmpeg -loglevel error -stats $ABYTES -i "$INPUT_FILE" -c copy "$DRMFREE" 287 | fi 288 | else 289 | # total reencode 290 | # itunes broken m4b with no seek slider & current time stamp shown as 'Live' 291 | # this can be very slow (single stream reencode) but will probably make problematic 292 | # files much smaller. 293 | echo "- Creating \"$FSBOOKTITLE-reencode.m4b\"" 294 | if [[ "$DRYRUN" == 0 ]]; then 295 | DRMFREE="$WORKPATH/m4b/$FSBOOKTITLE-reencode.m4b" 296 | ffmpeg -loglevel error -stats $ABYTES -i "$INPUT_FILE" -vn -c:a aac -b:a $OUTPUT_M4B_BITDEPTH "$DRMFREE" 297 | AtomicParsley "$DRMFREE" --artwork "$COVERIMG" --overWrite 298 | fi 299 | fi 300 | # Dryrun referances initial file 301 | if [[ "$DRYRUN" == 1 ]]; then 302 | DRMFREE=$INPUT_FILE 303 | fi 304 | 305 | 306 | # make work file 307 | JOBENCODER="$WORKPATH/jobs_encode.sh" 308 | echo "#!/bin/bash" | tee "$JOBENCODER" 1> /dev/null 309 | chmod +x "$JOBENCODER" 310 | 311 | 312 | 313 | # MP3 (one track per chapter, 64kbps, metadata and playlist) 314 | if [[ "$OUTPUT_MP3" == 1 ]]; then 315 | echo "- Preparing MP3 Encoding Jobs" 316 | mkdir "$WORKPATH/mp3" 317 | PLAYLIST="$WORKPATH/mp3/00. $FSBOOKTITLE ($FSAUTHOR $YEAR).m3u" 318 | echo -e "#EXTM3U\n#EXTENC: UTF-8\n#EXTGENRE:Audiobook\n#EXTART:$AUTHOR\n#PLAYLIST:$BOOKTITLE ($AUTHOR $YEAR)" | tee "$PLAYLIST" 1> /dev/null 319 | 320 | for i in ${!ID[@]} 321 | do 322 | let TRACKNO=$i+1 323 | echo -e " ${START_TIME[$i]} - ${END_TIME[$i]}\t${TITLE[$i]}" 324 | 325 | # mp3 encoder job 326 | OUTPUT_ENCODE="_$TRACKNO.mp3" 327 | OUTPUT_FINAL="$(printf "%02d" $TRACKNO). $FSBOOKTITLE - ${TITLE[$i]}.mp3" 328 | COMMAND="echo \"$WORKPATH/mp3/$OUTPUT_ENCODE\" && \ 329 | ffmpeg $FFMPEG_LOGLEVEL -i \"${DRMFREE/"\$"/"\\\$"}\" -vn -c libmp3lame \ 330 | -ss ${START_TIME[$i]} -to ${END_TIME[$i]} \ 331 | -id3v2_version 4 \ 332 | -metadata title=\"${TITLE[$i]}\" \ 333 | -metadata track=\"$TRACKNO/${#ID[@]}\" \ 334 | -metadata album=\"$BOOKTITLE\" \ 335 | -metadata genre=\"Audiobook\" \ 336 | -metadata artist=\"$AUTHOR\" \ 337 | -metadata album_artist=\"$AUTHOR\" \ 338 | -metadata date=\"$YEAR\" \ 339 | -metadata comment=\"$COMMENT\" \ 340 | -metadata album-sort=\"$ALBUM_SORT\" 341 | -metadata artist-sort=\"$ARTIST_SORT\" 342 | -codec:a libmp3lame \ 343 | -b:a $OUTPUT_MP3_BITDEPTH \ 344 | \"$WORKPATH/mp3/$OUTPUT_ENCODE\"" 345 | echo -e $COMMAND | tee -a "$JOBENCODER" 1> /dev/null 346 | 347 | # cover job (set final filename here too) 348 | COMMAND="ffmpeg $FFMPEG_LOGLEVEL -i \"$WORKPATH/mp3/$OUTPUT_ENCODE\" -i \"$COVERIMG\" -c copy -map 0 -map 1 -metadata:s:v title=\"Album cover\" -metadata:s:v comment=\"Cover (Front)\" \"$WORKPATH/mp3/${OUTPUT_FINAL/"\$"/"\\\$"}\" && rm \"$WORKPATH/mp3/$OUTPUT_ENCODE\"" 349 | echo -e $COMMAND | tee -a "$JOBCOVER" 1> /dev/null 350 | 351 | # m3u line 352 | BEGSECS=$( echo "${START_TIME[$i]}" | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }' ) 353 | ENDSECS=$( echo "${END_TIME[$i]}" | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }' ) 354 | LENGTH=$( echo "scale=0;($ENDSECS-$BEGSECS+0.5)/1" | bc ) 355 | echo "#EXTINF: $LENGTH, $FSBOOKTITLE - ${TITLE[$i]}" | tee -a "$PLAYLIST" 1> /dev/null 356 | echo "$OUTPUT_FINAL" | tee -a "$PLAYLIST" 1> /dev/null 357 | done 358 | 359 | # mumble mutter stupid unnecassary citation requirement 360 | # maybe I should write a scientific on it, or something 361 | 362 | if [[ "$OUTPUT_MP3_PARALLEL" == 1 ]]; then 363 | echo -e "- Parallel Encoding: (tracks will appear out of order)" 364 | if [[ "$DRYRUN" == 0 ]]; then 365 | parallel --will-cite -a "$JOBENCODER" 366 | parallel --will-cite -a "$JOBCOVER" 367 | fi 368 | else 369 | echo -e "- Encoding:" 370 | if [[ "$DRYRUN" == 0 ]]; then 371 | (exec "$JOBENCODER") 372 | (exec "$JOBCOVER") 373 | fi 374 | fi 375 | if [[ "$DRYRUN" == 1 ]]; then 376 | echo -e " Or Not! --dryrun specified, nothing to do." 377 | fi 378 | fi 379 | 380 | # clean up 381 | rm "$JOBENCODER" 382 | rm "$JOBCOVER" 383 | if [[ "$DRYRUN" == 0 ]]; then 384 | mkdir "./$FSBOOKTITLE" -p 385 | cp $COVERIMG "./$FSBOOKTITLE/" -f 386 | if [[ "$OUTPUT_M4B" == 1 ]]; then 387 | mkdir "./$FSBOOKTITLE/m4b" -p 388 | cp $WORKPATH/m4b/* "./$FSBOOKTITLE/m4b/" -f 389 | fi 390 | 391 | if [[ "$OUTPUT_MP3" == 1 ]]; then 392 | mkdir "./$FSBOOKTITLE/mp3" -p 393 | cp $WORKPATH/mp3/* "./$FSBOOKTITLE/mp3/" -f 394 | fi 395 | fi 396 | rm -r "$WORKPATH" 397 | # loop process time 398 | SPLIT_RUN=$(($SECONDS-$SPLIT)) 399 | echo -e "- Done. processed in $(($SPLIT_RUN / 3600))hrs $((($SPLIT_RUN / 60) % 60))min $(($SPLIT_RUN % 60))sec.\n" 400 | done 401 | 402 | #total time if more than one 403 | if [[ ${#INPUT_FILES[@]} -gt "1" ]]; then 404 | echo -e "\nDone processing ${#INPUT_FILES[@]} file(s) in $(($SECONDS / 3600))hrs $((($SECONDS / 60) % 60))min $(($SECONDS % 60))sec." 405 | fi --------------------------------------------------------------------------------