-
Notifications
You must be signed in to change notification settings - Fork 84
/
spotify-adkiller.sh
executable file
·523 lines (439 loc) · 15.4 KB
/
spotify-adkiller.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
#!/bin/bash
# Official Spotify-AdKiller Repository: https://github.com/SecUpwN/Spotify-AdKiller/
# CHANGELOG: https://github.com/SecUpwN/Spotify-AdKiller/blob/master/CHANGELOG.md
# Feel free to contribute improvements and suggestions to this funky script!
# Spotify AdKiller main script
# Please make sure to consult the attached README file before using this script
# IMPORTANT REMINDER! CAREFULLY READ AND UNDERSTAND THIS PART. THANK YOU.
# -----------------------------------------------------------------------
# Spotify is a fantastic service and worth every penny.
# This script is *NOT* meant to circumvent buying premium.
# Please do consider switching to premium to support Spotify!
# -----------------------------------------------------------------------
# This product is not endorsed, certified or otherwise approved in any way
# by Spotify. Spotify is the registered trade mark of the Spotify Group.
# -----------------------------------------------------------------------
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------
## VARIABLES
# !! PLEASE DO NOT MODIFY THIS SECTION. USE THE CONFIG_FILE INSTEAD !!
# !! DEFAULT CONFIG PATH: "$HOME/.config/Spotify-AdKiller" !!
# config
CONFIG_PATH="${XDG_CONFIG_HOME:-$HOME/.config}/Spotify-AdKiller"
CONFIG_FILE="$CONFIG_PATH/Spotify-AdKiller.cfg"
# settings
BINARY="spotify"
# initialization
INITIALRUN=1
ADMUTE=0
PAUSED=0
LOCPLAY=0
PAUSESIGNAL=0
ADFINISHED=0
## FUNCTIONS
debuginfo(){
if [[ "$DEBUG" = "1" ]]
then
echo "$1"
fi
}
notify_send(){
notify-send -i spotify-client "Spotify-AdKiller" "$1"
}
report_error(){
echo "ERROR: $1"
notify_send "ERROR: $1"
}
print_horiz_line(){
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' -
}
read_config(){
[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE"
}
# Makes sure a string ends with a slash. This can be necessary when trying to find files in a directory via find.
append_missing_slash() {
echo "${@%/}/"
}
set_musicdir(){
if [[ "$automute" == "automute_simple" ]]; then
return
fi
if [[ -z "$CUSTOM_MUSIC" ]]; then
# get XDG default directories
test -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" \
&& source "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs"
LOCAL_MUSIC="$(append_missing_slash "${XDG_MUSIC_DIR}")"
if [[ -z "$LOCAL_MUSIC" ]]; then
report_error "Default music folder not found. Please set a custom location.
Switching to simple automute (no local playback)"
CUSTOM_MODE="simple"
fi
else
LOCAL_MUSIC="$(append_missing_slash "${CUSTOM_MUSIC}")"
fi
echo "## Music path: $LOCAL_MUSIC ##"
if [[ -z "$(find -L "$LOCAL_MUSIC" -iname "*.mp3" 2> /dev/null )" ]]; then
report_error "No music found in the specified location. Please check the settings.
Switching to simple automute (no local playback)"
CUSTOM_MODE="simple"
fi
}
set_player(){
if [[ -n "$CUSTOM_PLAYER" ]]; then
PLAYER="$CUSTOM_PLAYER"
LOOPOPT="$CUSTOM_LOOPOPT"
elif type cvlc > /dev/null 2>&1; then
# vlc volume ranges from 0..256
PLAYER="cvlc --play-and-exit --volume=$((256*VOLUME/100))"
LOOPOPT="--repeat"
elif type mplayer > /dev/null 2>&1; then
PLAYER="mplayer -vo null --volume=$VOLUME"
LOOPOPT="-loop"
elif type mpv > /dev/null 2>&1; then
PLAYER="mpv --vo null --volume=$VOLUME"
LOOPOPT="--loop=inf"
elif type mpg321 > /dev/null 2>&1; then
PLAYER="mpg321 -g $VOLUME"
LOOPOPT="--loop 0"
elif type avplay > /dev/null 2>&1; then
# custom volume not supported
PLAYER="avplay -nodisp -autoexit"
LOOPOPT="-loop 0"
elif type ffplay > /dev/null 2>&1; then
# custom volume not supported
PLAYER="ffplay -nodisp -autoexit"
LOOPOPT="-loop 0"
else
if [[ "$CUSTOM_MODE" != "simple" ]]; then
report_error "No supported audio player detected. Please install one of the supported \
players or define a custom player by setting CUSTOM_PLAYER.
Switching to simple automute (no local playback)"
CUSTOM_MODE="simple"
fi
fi
[[ -n "$PLAYER" ]] && echo "## Using $(echo "$PLAYER" | cut -d' ' -f1) for local playback ##"
}
set_volume(){
if [[ -z "$CUSTOM_VOLUME" ]]
then
VOLUME="100"
else
VOLUME="$CUSTOM_VOLUME"
fi
}
set_mode(){
case "$CUSTOM_MODE" in
continuous) automute="automute_continuous"
;;
interstitial) automute="automute_interstitial"
;;
simple) automute="automute_simple"
;;
"") automute="automute_continuous"
;;
*) report_error "Unsupported CUSTOM_MODE"
exit 1
;;
esac
echo "## Ad block mode: $automute ##"
}
set_windowid(){
WINDOWID=$(xdotool search --classname "$BINARY" | tail -1)
if [[ -z "$WINDOWID" ]]; then
echo "Spotify not active. Exiting."
exit 1
fi
}
setup_vars(){
set_windowid
set_mode
set_musicdir
set_volume
set_player
}
get_track_info(){
DBUSOUTPUT=$(dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 \
org.freedesktop.DBus.Properties.Get string:'org.mpris.MediaPlayer2.Player' string:'Metadata')
DBUS_TRACK=$(echo "$DBUSOUTPUT" | grep xesam:trackNumber -A 1 | grep variant | awk '{ printf $3; }')
}
get_pactl_info(){
pacmd list-sink-inputs | grep -B 25 "application.process.binary = \"$BINARY\""
}
get_state(){
get_track_info
# check if track paused
debuginfo "$(get_pactl_info)"
if get_pactl_info | grep 'state: CORKED' > /dev/null 2>&1; then
# wait and recheck
sleep 0.75
if get_pactl_info | grep 'state: CORKED' > /dev/null 2>&1; then
echo "PAUSED: Yes"
PAUSED="1"
fi
get_track_info
else
echo "PAUSED: No"
PAUSED="0"
fi
if [[ "$DBUS_TRACK" == "0" ]]; then
echo "AD: Yes"
AD="1"
else
echo "AD: No"
AD="0"
fi
# check if local player running
if ps -p "$ALTPID" > /dev/null 2>&1
then
echo "LOCAL: Yes"
LOCPLAY="1"
else
echo "LOCAL: No"
LOCPLAY="0"
fi
debuginfo "admute: $ADMUTE; pausesignal: $PAUSESIGNAL; adfinished: $ADFINISHED"
}
get_pactl_nr(){
LC_ALL=C pacmd list-sink-inputs | awk -v binary="$BINARY" '
$1 == "index:" {idx = $2}
$1 == "application.process.binary" && $3 == "\"" binary "\"" {print idx; exit}
'
# awk script by Glenn Jackmann (http://askubuntu.com/users/10127/)
# first posted on http://askubuntu.com/a/180661
}
mute(){
debuginfo "pactl: mute"
for PACTLNR in $(get_pactl_nr); do
pactl set-sink-input-mute "$PACTLNR" yes > /dev/null 2>&1
echo "## Muting sink $PACTLNR ##"
done
ADMUTE=1
}
unmute(){
debuginfo "pactl: unmute"
for PACTLNR in $(get_pactl_nr); do
pactl set-sink-input-mute "$PACTLNR" no > /dev/null 2>&1 ## unmute
echo "## Unmuting sink $PACTLNR ##"
done
ADMUTE=0
}
stop_localplayback(){
kill -s TERM "$ALTPID" 2> /dev/null
[[ -f "$PIDFILE" ]] && PLAYER_PID="$(cat "$PIDFILE")"
kill -s TERM "$PLAYER_PID" 2> /dev/null
}
spotify_dbus(){
dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 \
org.mpris.MediaPlayer2.Player."$1" > /dev/null 2>&1
debuginfo "dbus: $1"
}
player(){
RANDOM_TRACK="$(find -L "$LOCAL_MUSIC" -iname "*.mp3" 2> /dev/null | sort --random-sort | head -1)"
notify_send "Playing ${RANDOM_TRACK##$LOCAL_MUSIC/}"
${PLAYER} $2 "$RANDOM_TRACK" > /dev/null 2>&1 & # Play random track
PLAYER_PID="$!" # Get PLAYER PID
echo "$PLAYER_PID" > "$PIDFILE" # Store player PID
wait "$PLAYER_PID" # Wait for player to
# exit before continuing
spotify_dbus $1 # Continue Spotify playback.
# This triggers the xprop spy and
# subsequent actions like unmuting
}
automute_continuous(){
# no ad, first track
if [[ "$AD" = "0" && "$PAUSED" = "0" && "$ADMUTE" = "0" && \
"$INITIALRUN" = "1" ]]
then
echo "## Initial run ##"
unmute
INITIALRUN="0"
# no ad, regular track
elif [[ "$AD" = "0" && "$PAUSED" = "0" && "$ADMUTE" = "0" && \
"$INITIALRUN" = "0" ]]
then
echo "## Regular track ##"
# no ad, regular pause
elif [[ "$AD" = "0" && "$PAUSED" = "1" && "$ADMUTE" = "0" && \
"$INITIALRUN" = "0" ]]
then
echo "## Paused by User ##"
# ad finished
elif [[ "$AD" = "0" && "$PAUSED" = "0" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" && "$PAUSESIGNAL" = "0" && "$ADFINISHED" = "0" ]]
then
echo "## Pausing Spotify until local playback finished / user interrupt ##"
ADFINISHED=1
spotify_dbus Pause
# ad finished, next track paused
elif [[ "$AD" = "0" && "$PAUSED" = "1" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" && "$PAUSESIGNAL" = "0" && "$ADFINISHED" = "1" ]]
then
echo "## Paused by AdKiller ##"
PAUSESIGNAL=1
spotify_dbus Pause
# ad, manual pause
elif [[ "$AD" = "0" && "$PAUSED" = "1" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" && "$PAUSESIGNAL" = "0" && "$ADFINISHED" = "0" ]]
then
echo "## Paused during ad by User ##"
notify_send "Ad is still on. Please wait for a moment."
spotify_dbus Play
# ad, manual pause
elif [[ "$AD" = "1" && "$PAUSED" = "1" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" && "$PAUSESIGNAL" = "0" && "$ADFINISHED" = "0" ]]
then
echo "## Paused during ad by User ##"
notify_send "Ad is still on. Please wait for a moment."
spotify_dbus Play
# ad finished, user unpaused/switched track
elif [[ "$AD" = "0" && "$PAUSED" = "0" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" && "$PAUSESIGNAL" = "1" && "$ADFINISHED" = "1" ]]
then
echo "## Interrupting local playback ##"
notify_send "Local playback interrupted"
stop_localplayback
unmute
PAUSESIGNAL=0
ADFINISHED=0
# ad finished, local playback finished
elif [[ "$AD" = "0" && "$PAUSED" = "0" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "0" && "$PAUSESIGNAL" = "1" && "$ADFINISHED" = "1" ]]
then
echo "## Switching back to Spotify ##"
unmute
PAUSESIGNAL=0
ADFINISHED=0
# ad still on, local playback finished
elif [[ "$AD" = "1" && "$PAUSED" = "0" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "0" && "$PAUSESIGNAL" = "0" && "$ADFINISHED" = "0" ]]
then
echo "## Playing next local track ##"
mute
player Play > /dev/null 2>&1 &
ALTPID="$!"
# ad started
elif [[ "$AD" = "1" && "$PAUSED" = "0" && "$ADMUTE" = "0" && \
"$LOCPLAY" = "0" && "$PAUSESIGNAL" = "0" && "$ADFINISHED" = "0" ]]
then
echo "## Switching to local playback ##"
mute
player Play > /dev/null 2>&1 &
ALTPID="$!"
# second ad / manual unpause while ad is on
elif [[ "$AD" = "1" && "$PAUSED" = "0" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" && "$PAUSESIGNAL" = "0" && "$ADFINISHED" = "0" ]]
then
echo "## Keep local playback running ##"
# any other unknown condition -> restore state
else
echo "## Restoring state ##"
unmute
ADMUTE=0
PAUSED=0
LOCPLAY=0
PAUSESIGNAL=0
ADFINISHED=0
fi
}
automute_simple(){
if [[ "$AD" = "0" ]]; then
unmute
elif [[ "$AD" = "1" ]]; then
mute
fi
}
automute_interstitial(){
# no ad, first track
if [[ "$AD" = "0" && "$PAUSED" = "0" && "$ADMUTE" = "0" && \
"$INITIALRUN" = "1" ]]
then
echo "## Initial run ##"
unmute
INITIALRUN="0"
# no ad, regular track
elif [[ "$AD" = "0" && "$PAUSED" = "0" && "$ADMUTE" = "0" && \
"$INITIALRUN" = "0" ]]
then
echo "## Regular track ##"
# no ad, regular pause
elif [[ "$AD" = "0" && "$PAUSED" = "1" && "$ADMUTE" = "0" && \
"$INITIALRUN" = "0" ]]
then
echo "## Paused by User ##"
# ad finished
elif [[ "$AD" = "0" && "$PAUSED" = "0" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" ]]
then
echo "## Interrupting local playback ##"
stop_localplayback
unmute
# ad, manual pause
elif [[ "$AD" = "0" && "$PAUSED" = "1" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" ]]
then
echo "## Paused by User during ad ##"
notify_send "Ad is still on. Please wait for a moment."
spotify_dbus Play
# ad started
elif [[ "$AD" = "1" && "$PAUSED" = "0" && "$ADMUTE" = "0" && \
"$LOCPLAY" = "0" ]]
then
echo "## Switching to local playback ##"
mute
player Play "$LOOPOPT" > /dev/null 2>&1 &
ALTPID="$!"
# second ad / manual unpause while ad is on
elif [[ "$AD" = "1" && "$PAUSED" = "0" && "$ADMUTE" = "1" && \
"$LOCPLAY" = "1" ]]
then
echo "## Keep local playback running ##"
# any other unknown condition -> restore state
else
echo "## Restoring state ##"
unmute
ADMUTE=0
PAUSED=0
LOCPLAY=0
PAUSESIGNAL=0
ADFINISHED=0
fi
}
restore_settings(){
echo "## Restoring settings ##"
# terminate PLAYER if still running
stop_localplayback
[[ -f "$PIDFILE" ]] && rm "$PIDFILE"
# I would love to restore the mute state here but unfortunately it's impossible.
# `pactl` can only control active sinks, i.e. if Spotify isn't running we can't
# control its mute state. So we have to resort to unmuting Spotify on every initial
# run of this script (INITIALRUN=1)
# However, we can always unmute the Spotify sink if it is running.
if pgrep spotify &>/dev/null; then unmute; fi
}
## PREPARATION
# read configuration file
read_config
# create PIDFILE for inter-process-communication
PIDFILE="$(mktemp -u --tmpdir "${0##*/}.XXXXXXXX")"
# make sure to restore settings upon exit
trap restore_settings EXIT
# setup player, music directory, and volume
setup_vars
## MAIN
while read XPROPOUTPUT; do
get_state
$automute
print_horiz_line
done < <(xprop -spy -id "$WINDOWID" _NET_WM_NAME)
# we use process substitution instead of piping
# to avoid executing the loop in a subshell
echo "Spotify not active. Exiting."