get-tracks/get-tracks.sh

257 lines
4.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env sh
# From a single byte in hexadecimal per line
# to lines ending with 0a (hex for '\n').
regroup_lines() awk '
BEGIN {
line_start=1
}
{
if (line_start == 1)
line = $1;
else
line = line " " $1;
line_start = 0;
if ($1 == "0a") {
print line;
line_start = 1
}
}
END {
if (line_start == 0)
print line
}
'
# From to '
simple_quote() sed "s/e2 80 99/27/g"
# From / to '-'
replace_slashes() sed "s/2f/2d/g"
remove_multibyte_characters() sed "s/e2 80 .. //g"
# Convert input into hexadecimal and a single byte per line.
to_hex_one_column() xxd -p -c 1
# Reverse hexadecimal to original value.
from_hex() xxd -p -r
# Remove non ascii characters, convert "" to "'", or remove invalid filename characters.
to_ascii(){
to_hex_one_column | # Convert input into hexadecimal and a single byte per line.
regroup_lines | # Required to easily match multi-byte characters.
simple_quote | # Convert "" to "'".
replace_slashes | # Replace / by ' - ', since it isn't a valid filename character.
remove_multibyte_characters | # Remove non ascii values.
from_hex # Convert back from hex.
}
process_end_of_songs() awk -v NONUMBER="$NONUMBER" -v SEPARATOR="$SEPARATOR" '
BEGIN {
OFS=" "
}
{
if (NR > 1) {
print timestamp, $1, title;
}
timestamp = $1;
if (NONUMBER == 1) {
title = $2
}
else {
if (NR < 10) {
title = "0" NR SEPARATOR $2
}
else {
title = NR SEPARATOR $2
}
}
for (i=3; i <= NF; i++) {
title = title " " $i
}
}
END {
print timestamp, "END_OF_FILE", title;
}
'
first_column_to_seconds() awk '
{
# from 10:30 to 630
n = split ($1, arr, ":")
for (i = 0; i <= n; i++) {
if (i == 0) {
v = arr[n-i];
}
else if (i == 1) {
v += 60 * arr[n-i];
}
else if (i == 2) {
v += 3600 * arr[n-i];
}
}
$1 = v;
print;
v = 0;
}
'
# Get a more usable time representation for the beginning and the end of songs.
process_time_file(){ to_ascii | first_column_to_seconds | process_end_of_songs ; }
run_ffmpeg(){
file=$1
from=$2
to=$3
final_title=$4
LOG_LEVEL="-loglevel error"
FROM="-ss $from"
TO=""
if [ "$to" != "" ]; then
TO="-to $to"
fi
INPUT_FILE="$file"
OUTPUT_FILE="$final_title"
case "v$VERBOSITY" in
v0)
;;
v1)
echo "extracting '$final_title'"
;;
v2)
echo "ffmpeg $LOG_LEVEL $FROM $TO -i $INPUT_FILE '$OUTPUT_FILE'"
;;
*)
echo "verbosity is not set properly" >&2
exit 1
;;
esac
if [ "$SIMULATION" = "" ]; then
$(< /dev/null ffmpeg $LOG_LEVEL $FROM $TO -i "$INPUT_FILE" "$OUTPUT_FILE")
fi
}
rip(){
audio_file="$1"
time_file="$2"
process_time_file < "$time_file" | while read LINE; do
track_start=$(echo $LINE | cut -d ' ' -f 1)
track_end=$(echo $LINE | cut -d ' ' -f 2)
track_title=$(echo $LINE | cut -d ' ' -f 3-)
if [ "$track_end" = "END_OF_FILE" ]; then
track_end=""
fi
run_ffmpeg "${audio_file}" "${track_start}" "${track_end}" "${track_title}.${FORMAT}"
done
}
usage(){
cat <<END
Get tracks:
usage: $0 <single-file-playlist> <song-list>
Debug mode (displays starting and ending times for each song):
usage: $0 <song-list>
Format for <song-list>:
0:00 First track
1:30 Second track
Environment variables:
- SIMULATION [empty or not]
do not invoke ffmpeg
- NONUMBER [empty or 1]
do not write song numbers
- FORMAT [mp3,ogg,opus,…]
see the ffmpeg documentation
- SEPARATOR [separator] (default: ' - ')
separator between number and name
example with SEPARATOR='_': 01_intro.opus 02_blah.opus…
- HEADERS [empty or 1]
print environment parameters (verbosity, simulation, etc.)
- VERBOSITY [0-3] (default: 1)
0: no output except errors from ffmpeg
1: simple indications on the current track being extracted
2: print actual ffmpeg commands the script currently runs
END
}
if [ $# -lt 1 ]; then
usage
exit 0
fi
header(){
if [ "$HEADERS" = "1" ]; then
echo $*
fi
}
if [ "$FORMAT" = "" ]; then
header "default FORMAT: opus"
FORMAT="opus"
else
header "FORMAT: $FORMAT"
fi
if [ "$VERBOSITY" = "" ]; then
header "default VERBOSITY: 1"
VERBOSITY=1
else
header "VERBOSITY level: $VERBOSITY"
fi
if [ "$NONUMBER" = "" ]; then
header "default NONUMBER: disabled"
NONUMBER=0
# Assume that there should be a separator.
if [ "$SEPARATOR" = "" ]; then
header "default SEPARATOR: ' - '"
SEPARATOR=" - "
else
header "SEPARATOR: '$SEPARATOR'"
fi
else
header "NONUMBER: won't prefix tracks"
SEPARATOR=""
fi
if [ "$SIMULATION" != "" ]; then
header "SIMULATION envvar is set: this is a simulation."
fi
</dev/null xxd -p >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
echo "xxd: you don't have an xxd program with '-p' option." 1>&2
exit 1
fi
</dev/null xxd -r >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
echo "xxd: you don't have an xxd program with '-r' option." 1>&2
exit 1
fi
case $# in
1) process_time_file < "$1" ;;
2) rip "$1" "$2" ;;
*) usage 1>&2 ; exit 1 ;;
esac