281 lines
5.2 KiB
Bash
Executable File
281 lines
5.2 KiB
Bash
Executable File
#!/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"
|
||
}
|
||
|
||
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 "'".
|
||
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 "'".
|
||
remove_multibyte_characters | # Remove non ascii values.
|
||
from_hex # Convert back from hex.
|
||
}
|
||
|
||
process_durations(){
|
||
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_durations
|
||
}
|
||
|
||
run_ffmpeg(){
|
||
file=$1
|
||
from=$2
|
||
duration=$3
|
||
final_title=$4
|
||
|
||
LOG_LEVEL="-loglevel error"
|
||
FROM="-ss $from"
|
||
if [ "$duration" = "" ]; then
|
||
DURATION=""
|
||
else
|
||
DURATION="-t $duration"
|
||
fi
|
||
INPUT_FILE="-i $file"
|
||
OUTPUT_FILE="$final_title"
|
||
|
||
case "v$VERBOSITY" in
|
||
v0)
|
||
;;
|
||
v1)
|
||
echo "extracting '$final_title'"
|
||
;;
|
||
v2)
|
||
echo "ffmpeg $LOG_LEVEL $FROM $DURATION $INPUT_FILE '$OUTPUT_FILE'"
|
||
;;
|
||
*)
|
||
echo "verbosity is not set properly" >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
if [ "$SIMULATION" = "" ]; then
|
||
$(< /dev/null ffmpeg $LOG_LEVEL $FROM $DURATION $INPUT_FILE "$OUTPUT_FILE")
|
||
fi
|
||
}
|
||
|
||
rip(){
|
||
track_start_s=0
|
||
track_end_s=0
|
||
|
||
audio_file="$1"
|
||
time_file="$2"
|
||
|
||
process_time_file < "$time_file" | while read LINE; do
|
||
track_start_s=$(echo $LINE | cut -d ' ' -f 1)
|
||
track_end_s=$(echo $LINE | cut -d ' ' -f 2)
|
||
track_title=$(echo $LINE | cut -d ' ' -f 3-)
|
||
if [ "$track_end_s" != "END_OF_FILE" ]; then
|
||
track_duration=$(echo "$track_end_s - $track_start_s" | bc)
|
||
else
|
||
track_duration=""
|
||
fi
|
||
|
||
run_ffmpeg "${audio_file}" "${track_start_s}" "${track_duration}" "${track_title}.${FORMAT}"
|
||
done
|
||
}
|
||
|
||
usage(){
|
||
echo "usage: $0 command"
|
||
echo "command: show <song-list>"
|
||
echo "command: rip <single-file-playlist> <song-list>"
|
||
echo
|
||
echo "song-list line format example: 1:30 My second track of the playlist"
|
||
echo "show output format: start end title"
|
||
echo
|
||
echo "envvar: SIMULATION, if non empty, do not invoke ffmpeg"
|
||
echo "envvar: NONUMBER, if equals 1, do not write song number"
|
||
echo "envvar: FORMAT [mp3,ogg,opus,…], see the ffmpeg documentation"
|
||
echo "envvar: SEPARATOR [separator] (default: ' - '), write song number, with this separator"
|
||
echo " example with SEPARATOR='_': song names will be 01_song.opus 02_song.opus…"
|
||
echo "envvar: VERBOSITY [0-3] (default: 1)"
|
||
echo " VERBOSITY 0: no output exept errors from ffmpeg"
|
||
echo " VERBOSITY 1: simple indications on the current track being extracted"
|
||
echo " VERBOSITY 2: print actual ffmpeg commands the script currently runs"
|
||
}
|
||
|
||
if [ $# -lt 1 ]; then
|
||
usage
|
||
exit 0
|
||
fi
|
||
|
||
command=$1
|
||
shift
|
||
|
||
if [ "$FORMAT" = "" ]; then
|
||
echo "default FORMAT: opus"
|
||
FORMAT="opus"
|
||
else
|
||
echo "FORMAT: $FORMAT"
|
||
fi
|
||
|
||
if [ "$VERBOSITY" = "" ]; then
|
||
echo "default VERBOSITY: 1"
|
||
VERBOSITY=1
|
||
else
|
||
echo "VERBOSITY level: $VERBOSITY"
|
||
fi
|
||
|
||
if [ "$NONUMBER" = "" ]; then
|
||
echo "default NONUMBER: disabled"
|
||
NONUMBER=0
|
||
|
||
# Assume that there should be a separator.
|
||
if [ "$SEPARATOR" = "" ]; then
|
||
echo "default SEPARATOR: ' - '"
|
||
SEPARATOR=" - "
|
||
else
|
||
echo "SEPARATOR: '$SEPARATOR'"
|
||
fi
|
||
else
|
||
echo "NONUMBER: won't prefix tracks"
|
||
SEPARATOR=""
|
||
fi
|
||
|
||
if [ "$SIMULATION" != "" ]; then
|
||
echo "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 "x-${command}" in
|
||
x-show)
|
||
|
||
# Takes the audio file in first parameter
|
||
if [ $# -ne 1 ]; then
|
||
echo "Usage: $0 show time-stamps-file" >&2
|
||
exit 1
|
||
fi
|
||
|
||
process_time_file < "$1"
|
||
;;
|
||
|
||
x-rip)
|
||
|
||
# Takes the audio file in first parameter
|
||
if [ $# -ne 2 ]; then
|
||
echo "Usage: $0 show music-file time-stamps-file" >&2
|
||
exit 1
|
||
fi
|
||
|
||
rip "$1" "$2"
|
||
;;
|
||
|
||
*)
|
||
usage 1>&2
|
||
exit 1
|
||
esac
|