-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathspotdl.sh
executable file
·130 lines (104 loc) · 5.2 KB
/
spotdl.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/bin/bash
### SPOTIFY RECORDER
# command -v jq >/dev/null 2>&1 || { echo >&2 "jq was not found"; exit 1; }
command -v spotify >/dev/null 2>&1 || { echo >&2 "spotify was not found"; exit 1; }
command -v grep >/dev/null 2>&1 || { echo >&2 "grep was not found"; exit 1; }
command -v tr >/dev/null 2>&1 || { echo >&2 "tr was not found"; exit 1; }
command -v pactl >/dev/null 2>&1 || { echo >&2 "pactl was not found"; exit 1; }
command -v ffmpeg >/dev/null 2>&1 || { echo >&2 "ffmpeg was not found"; exit 1; }
command -v mp3splt >/dev/null 2>&1 || { echo >&2 "mp3splt was not found"; exit 1; }
# Check if sound server is pipewire
pactl info | grep -i pipewire && pipewire=1 || pipewire=0
printf "Sound Server : "
[ $pipewire = 1 ] && echo "PipeWire" || echo "PulseAudio"
if [ $pipewire = 1 ]; then
command -v pw-record >/dev/null 2>&1 || { echo >&2 "pw-record was not found"; exit 1; }
else
command -v parecord >/dev/null 2>&1 || { echo >&2 "parecord was not found"; exit 1; }
fi
# Get Spotify's pulseaudio sink ID
get_spotify_sink(){
# spotify_sink=$(LANG=en python3 pactl-json-parser/pactl_parser.py | jq 'to_entries[] | {sink:.key} + {value:.value.Properties["media.name"]} | if (.value | contains("Spotify")) then .sink | tonumber else empty end' | tail -f -n1)
# spotify_sink=$(pactl list sink-inputs | grep -E "Input #|media.name" | xargs | grep -Eoi "#[0-9]* media.name = Spotify" | grep -oi "[0-9]*") # 8x faster
spotify_sink=$(pactl list sink-inputs | grep -E "Input #|media.name" | tr -d "[:space:]" | tr -d "\"" | grep -Eoi "#[0-9]*media.name=Spotify" | grep -oi "[0-9]*") # 1.3x even faster + remove xargs dependency
}
uri=$1
filepath="$2"
duration="$3"
verbose="$4"
if [ -z "$uri" ] || [ -z "$filepath" ] || [ -z "$duration" ]; then
echo "Invalid usage, missing uri, filepath or song duration."
echo "Usage : $0 <URI> <filepath> <song duration> [verbose]"
fi
tmp_folder="songs_build"
mkdir -p $tmp_folder
mkdir -p $(dirname $filepath)
tmp_filepath="$tmp_folder/${uri/*:/}"
if [ ! -z $verbose ]; then
echo "* URI : $uri"
echo "* filepath : $filepath"
echo "* duration : $duration"
echo "* Verbose : $verbose"
echo "* tmp_filepath : $tmp_filepath"
fi
if [ $pipewire = 0 ]; then
# Get default output
pactl_default_output=$(pactl get-default-sink)
# pactl_default_source=$(pactl get-default-source)
module_name="rec-play"
if ! pactl list sinks | grep "$module_name" > /dev/null;
then
printf "[ ] Loading new module %s" "$module_name"
record_id=$(pactl load-module module-combine-sink sink_name="$module_name" slaves="$pactl_default_output" sink_properties=device.description="[spotify-recorder]Record-and-Play") # channels=2 channel_map=stereo remix=no
else
record_id=$(pactl list short modules | grep "$module_name" | grep -Eo "^[0-9]*")
fi
if ! pactl list sinks | grep "$module_name" > /dev/null; then
printf "\r[!] Error : Loading new module %s\n" "$module_name"
exit 1;
elif [ -n "$verbose" ]; then
printf "\r[x] Module loaded %s : %s\n" "$module_name" "$record_id"
else
printf "\r"
fi
fi
# Set default sink for starting recording before spotify sink is spotted
# pactl set-default-sink "$module_name"
# Start Spotify using URI
pkill spotify
sleep 0.1
spotify --uri="$uri" > /dev/null 2>&1 &
# Start recording before spotify_sink is spotted
[ $pipewire = 0 ] && (parecord --latency-msec=20 --device="$module_name".monitor --record --fix-channels --fix-format --fix-rate "$tmp_filepath.rec" || (printf "[!] Error : Recording\n"; exit 1)) &
# (pw-record --latency=20ms --target="$module_name".monitor "$tmp_filepath.rec" || (printf "[!] Error : Recording\n"; exit 0)) &
# Wait until Spotify's sink is spotted
while [ -z "$spotify_sink" ];
do
get_spotify_sink
done
if [ $pipewire = 0 ]; then
pactl move-sink-input "$spotify_sink" "$module_name"
else
# using `pw-top` to match spotify's format and rate
pw-record --latency=20ms --volume=1.0 --format=f32 --channel-map stereo --latency=20ms --rate 44100 --target="$spotify_sink" "$tmp_filepath.rec" &
fi
# Start recording
# parecord --latency-msec=1 --monitor-stream="$spotify_sink" --record --fix-channels --fix-format --fix-rate "$tmp_filepath.rec" &
printf "==> Recording %s as \"%s\" for %s seconds\r" "$uri" "$filepath" "$duration"
# Wait till the end & stop
sleep "$duration"
# 0.05 to 0.1 lost (but we don't care as spotify takes some time to play)
pkill spotify
[ $pipewire = 1 ] && pkill pw-record || pkill parecord
# Convert file & Trim it
[ -z $verbose ] && verbose_flags="-hide_banner -loglevel error"
ffmpeg $verbose_flags -y -i "$tmp_filepath.rec" -acodec mp3 -b:a 320k "$tmp_filepath.mp3"
[ -z $verbose ] && verbose_flags="-q"
mp3splt $verbose_flags -r -p rm -p min=0.3 -p trackmin="$(echo "$duration"-2 | bc)" "$tmp_filepath".mp3 | ( [ -z "$verbose" ] || grep -Ev "^ info:" )
[ ! -f "$tmp_filepath"_trimmed.mp3 ] && f="$tmp_filepath.mp3" || f="$tmp_filepath"_trimmed.mp3;
mv "$f" "$filepath"
printf "\033[K[+] File saved at %s\n" "$filepath"
# Back to default settings
[ $pipewire = 0 ] && { pactl unload-module "$record_id" || { echo "Can't unload module $record_id"; exit 1; }; }
# pactl set-default-sink "$pactl_default_output"
exit 0