Split by Silence, ffmpeg

Split by silence (very fragile)

silence detection v1

input="80s.m4a"
quiet=(-hide_banner -loglevel info -nostats)
ffmpeg "${quiet[@]}" -i "${input}" -af silencedetect=n=-50dB:d=1 -f null - 2> | awk '{ print $4, $5}' | grep "silence_" > detect.log

Returns

silence_start: 447.672
silence_end: 449.415
silence_start: 674.288
silence_end: 677.722
silence_start: 677.899
...

Now, my 1st song start is probably 1st silence_end (-ss), having the spliting command in mind

ffmpeg "${quiet}" -i "${input}" -ss 449.415 -to 674.288 -c:a copy test.m4a

Rename to -ss and -to

sed -i 's/silence_end:/-ss/g' detect.log
sed -i 's/silence_start:/-to/g' detect.log

silence detection v2, real one

ffmpeg "${quiet[@]}" -i "${input}" -af silencedetect=n=-50dB:d=0.5 -f null - 2>&1 | \
awk '{ print $4, $5}' | grep "silence_" | sed 's/silence_end:/-ss/g' | sed 's/silence_start:/-to/g' \
| tail -n +2 > detect.log

Returns

-ss 449.415
-to 674.288
-ss 677.722
-to 677.899
...

Tail shall skip 1st line.

Add empty line

echo >> detect.log

Note: If 1st is to be ignored, that means that the file is required to start with some silence

Test loop

awk_round () {
awk 'BEGIN{printf "%."'$1'"f\n", "'$2'"}'
}

c="1"
cat detect.log | while read _ ssvar; read _ tovar; do
    #pad name
    printf -v name "%03d" "$c"
    # action
    ssvar="$(awk_round "5" "$ssvar")"
    tovar="$(awk_round "5" "$tovar")"
    echo "-ss $ssvar -to $tovar $name.m4a"
    (( c++ ))
done

The split loop, real one

awk_round () {
awk 'BEGIN{printf "%."'$1'"f\n", "'$2'"}'
}
round () { 
ssvar="$(awk_round "5" "$ssvar")"
tovar="$(awk_round "5" "$tovar")"
}

c="1"
quiet=(-hide_banner -loglevel info -nostats -nostdin)
cat detect.log | while read _ ssvar; read _ tovar; do
    #pad name
    printf -v name "%03d" "$c"
    # action

    echo; echo "-ss $ssvar -to $tovar"
    echo "--------------------------------------"
    if [ -z "$tovar" ]; then 
        round && ffmpeg "${quiet[@]}" -i "${input}" -ss "$ssvar" -c:a copy "${name}".m4a
    else
        round && ffmpeg "${quiet[@]}" -i "${input}" -ss "$ssvar" -to "$tovar" -c:a copy "${name}".m4a
    fi
    (( c++ ))
done

Split by yt ‘chapters’ file

Example ‘list.txt’

0:00 As It Was - Harry Styles
2:39 Stay - The Kid LAROI with Justin Bieber
4:57 Bad Habits - Ed Sheeran 
8:42 Unstoppable - Sia
12:17 Memories - Maroon 5

Add some empty lines

echo "\n\n" >> list.txt

Test loop (dual) - should return the split commands

loop () {
    list="$1"
    tail -n +${c} "$list" | while read -r ssvar name ; read -r tovar _ ; do
        # action
        [ -z "${ssvar// }" ] && break
        #pad counter
        printf -v ccc "%03d" "$c"
        if [ -z "${tovar// }" ]; then
            #echo "empty tovar"
            echo "-ss $ssvar ${c}_${name}.m4a"
        else
            echo "-ss $ssvar -to $tovar ${ccc}-${name}.m4a"
        fi
        (( c = c + 2 ))
    done
}
c="1"; loop list.txt; c="2"; loop list.txt

Actual spliter script

#!/bin/bash

# spliter
# split local 'm4a audio file' by local 'list.txt' (yt chapters thing)
# example list.txt

    # 0:00 As It Was - Harry Styles
    # 2:39 Stay - The Kid LAROI with Justin Bieber
    # 4:57 Bad Habits - Ed Sheeran 
    # 8:42 Unstoppable - Sia
    # 12:17 Memories - Maroon 5
    # 15:22 Dance Monkey - Tones and I
    # 18:49 Girls Like You - Maroon 5

# usage: spliter master.m4a list.txt

input="$1"; list="$2"

# Add some empty lines
echo "\n\n" >> "$list"

loop () {

    tail -n +${c} "$list" | while read -r ssvar name ; read -r tovar _ ; do

        # action
        [ -z "${ssvar// }" ] && break
        #pad counter
        printf -v ccc "%03d" "$c"
        if [ -z "${tovar// }" ]; then
            #echo "empty tovar"
            echo "-ss $ssvar ${c}_${name}.m4a"
            ffmpeg "${quiet[@]}" -i "${input}" -ss "$ssvar" -c:a copy "${ccc}_${name}".m4a
        else
            echo "-ss $ssvar -to $tovar ${ccc}-${name}.m4a"
            ffmpeg "${quiet[@]}" -i "${input}" -ss "$ssvar" -to "$tovar" -c:a copy "${ccc}_${name}".m4a
        fi
        (( c = c + 2 ))

    done
}
quiet=(-hide_banner -loglevel quiet -nostats -nostdin)
c="1"; loop; c="2"; loop