Skip to content

Instantly share code, notes, and snippets.

@izderadicka
Last active September 12, 2019 08:40
Show Gist options
  • Save izderadicka/0efc16bd150d70b7c504ab15f95c11ab to your computer and use it in GitHub Desktop.
Save izderadicka/0efc16bd150d70b7c504ab15f95c11ab to your computer and use it in GitHub Desktop.
Splits large audiobooks into smaller files ( either by chapters, if available or to pieces of fixed duration) encoded with Opus audio
#!/bin/bash
# Author : <Ivan Zderadicka> [email protected]
# License: MIT
VERSION="0.2.3"
BITRATE=48
CUTOFF=12000
SEGMENT_TIME=1800
COMMON_PARAMS="-nostdin -v error"
print_help () {
cat << EOF
Splits large audiobook files into smaller parts which are then encoded with Opus codec.
Split points are either chapters defined in the audiobook or fixed size pieces.
Requires ffmpeg adn ffprobe version v >= 2.8.11
Supports input formats m4a, m4b, mp3, aax (mka should also work but not tested)
Usage: split_audiobook.sh [options] <audiobook>...
-h, --help Shows this help
-v, --version Prints version and exits
-r, --replace Replace existing output directory
-q <quality>
--quality <quality> Quality of the output - top (64kbps cutoff 20kHz), high (48kbps, cutoff 12kHz),
normal (32kbps, cutoff 12kHz), low (24kbps, cutoff 8kHz) [default: high]
-l <secs>
--length <secs> Lenght of piece in seconds (in case chapters are not defined) [default: $SEGMENT_TIME]
--activation_bytes <xxxxxxxx> Activation bytes required for aax format
EOF
}
while (( $# > 0 )); do
case $1 in
-h|--help)
print_help
exit
;;
-v|--version)
echo Version: $VERSION
exit
;;
-r|--replace)
REPLACE_DIR=1
;;
-q|--quality)
case $2 in
high)
BITRATE=48
CUTOFF=12000
;;
top)
BITRATE=64
CUTOFF=20000
;;
low)
BITRATE=24
CUTOFF=8000
;;
normal)
BITRATE=32
CUTOFF=12000
;;
*)
echo Invalid quality param $2 >&2
exit 1
;;
esac
shift
;;
--activation_bytes)
ACTIVATION_BYTES=$2
shift
;;
-l|--length)
SEGMENT_TIME=$2
shift
;;
*)
break
;;
esac
shift
done
OPUS_PARAMS="-acodec libopus -b:a ${BITRATE}k -vbr on -compression_level 10 -application audio -cutoff $CUTOFF"
temp_file=$(tempfile) || exit 1
trap "rm -f -- $temp_file" EXIT
trap "exit 2" SIGINT
wait_proc() {
while (( $(jobs -pr | wc -l ) >= $(nproc) )); do
sleep 1
done
}
while [[ $# -gt 0 ]]; do
echo Processing file $1
if [[ ! -f "$1" ]]; then
echo File $1 does not exists >&2
shift
continue
fi
ext=${1##*.}
if [[ $ext = "aax" && ${#ACTIVATION_BYTES} != 8 ]]; then
echo "Activation bytes (4 bytes = 8 chars in hexa) are needed for aax file" >&2
shift
continue
fi
if [[ -n "$ACTIVATION_BYTES" ]]; then
COMMON_PARAMS="-activation_bytes $ACTIVATION_BYTES $COMMON_PARAMS"
fi
ffprobe -v error -print_format compact=nokey=1 -show_chapters "$1" > $temp_file
dirname=${1%.*}
if [[ -n "$REPLACE_DIR" && -e "$dirname" ]]; then
rm -r "$dirname"
fi
mkdir "$dirname"
if [[ $? != 0 ]]; then
echo "Directory $dirname exists or cannot be created" >&2
shift
continue
fi
num_chapters=$(wc -l < $temp_file)
if [[ $num_chapters -gt 1 ]]; then
count=0
while IFS=\| read -r _ id _ _ start _ end chapter; do
((count++))
echo Processing chapter $count of $num_chapters
chap=${chapter/\//-}
{
ffmpeg $COMMON_PARAMS -i "$1" -ss "$start" -to "$end" -vn $OPUS_PARAMS\
-metadata title="$chapter"\
-metadata track="$count/$num_chapters"\
"$dirname/$(printf %03d $(($count-1))) - $chap.opus"
if [[ $? -ne 0 ]]; then
echo Error processing chapter $count of $num_chapters >&2
else
echo Finished chapter $count of $num_chapters
fi
} &
wait_proc
done < $temp_file
else
echo "No chapters found"
echo "Splitting file into pieces of $SEGMENT_TIME secs"
# this works fine however title and track tags cannot be sent for each part
# ffmpeg $COMMON_PARAMS -i "$1" -vn $OPUS_PARAMS -f segment -segment_time $SEGMENT_TIME\
# -reset_timestamps 1 "$dirname/%03d.opus"
if [[ $ext = "m4b" ]]; then
ext=m4a
fi
ffmpeg $COMMON_PARAMS -stats -i "$1" -vn -acodec copy -f segment -segment_time $SEGMENT_TIME\
-reset_timestamps 1 "$dirname/%03d.$ext"
count=0
num_files=$(ls -1q "$dirname" | wc -l)
echo Done with file split - number of parts is $num_files
for f in "$dirname/"*; do
((count++))
echo Processing part $count of $num_files
{
ffmpeg $COMMON_PARAMS -i "$f" $OPUS_PARAMS -metadata track=$count/$num_files\
-metadata title="Part $(($count - 1))" "${f%.*}.opus"
if [[ $? = 0 ]]; then
rm "$f"
echo Finished part $count of $num_files
else
echo Error converting file $f >&2
fi
} &
wait_proc
done
fi
# try extract cover art
ffmpeg $COMMON_PARAMS -i "$1" "$dirname/cover.jpg"
shift
done
wait
exit
@Themanwithoutaplan
Copy link

tempfile doesn't existence on OS X mktemp does return a tempfile but this seem to satisy doesn't the script.

@Themanwithoutaplan
Copy link

tempfile doesn't existence on OS X use mktemp instead. Likewise you also need to use sysctl -n hw.ncpu instead of nproc on line 90
Should then work but you might get errors when it tries to add the cover image to the created files: Could not get frame filename number 2 from pattern

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment