Skip to content

Instantly share code, notes, and snippets.

@hoehermann
Last active September 7, 2022 21:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hoehermann/8b6fa0682bd9192e835d1b7ba8daa4b6 to your computer and use it in GitHub Desktop.
Save hoehermann/8b6fa0682bd9192e835d1b7ba8daa4b6 to your computer and use it in GitHub Desktop.
This script mutes Spotify when it plays advertisements.
#!/usr/bin/env python3
# encoding: utf-8
import sys
import dbus
import dbus.mainloop.glib
import subprocess
import time
import re
from gi.repository import GLib
# copied from https://github.com/avindra/pacmd-python
def parseList(output):
lines = output.split('\n')
first = lines.pop(0)
if first.startswith('Unknown command'):
raise Exception(first)
data = {}
currentItem = {}
currentIndex = -1
patternNewItem = re.compile('(\* )?index: (\d+)')
patternKeyValue = re.compile('\t*([^:]+):(?: (.+))?')
patternEnumerating = re.compile('^\tproperties:')
patternProps = re.compile('(\S+) = "(.+)"')
enumeratingProps = None
lastKey = None
for line in lines:
matchNewItem = patternNewItem.search(line)
matchKeyValue = patternKeyValue.search(line)
matchEnumerating = patternEnumerating.search(line)
if enumeratingProps:
matchProps = patternProps.search(line)
if matchProps:
enumeratingProps[matchProps.group(1)] = matchProps.group(2)
continue
else: # reset
currentItem['properties'] = enumeratingProps
enumeratingProps = None
if matchEnumerating:
enumeratingProps = { 'device-api' : None }
elif matchNewItem:
isActive = matchNewItem.group(1) == '* '
index = matchNewItem.group(2)
# Finalize object and reset
if currentIndex != -1:
data[currentIndex] = currentItem
currentItem = {}
currentIndex = index
if isActive:
data['activeItem'] = index
elif matchKeyValue:
parsedKey = matchKeyValue.group(1).strip()
parsedValue = matchKeyValue.group(2)
# skip until we're out of "ports".
# not currently supported
if lastKey == 'ports' and parsedKey != 'active port':
continue
lastKey = parsedKey
currentItem[parsedKey] = parsedValue
elif lastKey and lastKey != 'ports': # If it gets to here, we can assume its a multiline attr
currentItem[lastKey] += re.sub('^\s+', '\n', line)
# Last item will need to be pushed manually
data[currentIndex] = currentItem
return data
def pacmd_list_sink_inputs():
p = subprocess.Popen(['pacmd','list-sink-inputs'], stdout=subprocess.PIPE, encoding='utf-8')
out, err = p.communicate()
return parseList(out)
def pacmd_set_sink_input_mute(index, mutestate):
p = subprocess.Popen(['pacmd','set-sink-input-mute', index, mutestate])
out, err = p.communicate()
def pulse_mute_spotify(mutestate):
inputs = pacmd_list_sink_inputs()
# spotify may allocate more than one output
spotify_indices = [
index for index, sink in inputs.items()
if 'properties' in sink and sink['properties']['application.process.binary'] == 'spotify'
]
for spotify_index in spotify_indices:
pacmd_set_sink_input_mute(spotify_index, mutestate)
print('Set mutestate to %s for %s'%(mutestate, str(spotify_indices)))
def is_advert(metadata):
""" Whether the current song is an advertisement. """
if 'Metadata' in metadata:
m = metadata['Metadata']
artist = m['xesam:artist'][0]
return artist == ""
else:
return False
def propertiesChanged_handler(sender=None, metadata=None, sig=None):
time.sleep(0.5) # event comes a bit earlier than actual content change
is_ad = is_advert(metadata)
pulse_mute_spotify('1' if is_ad else '0')
def main():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
try:
remote_object = bus.get_object(
"org.mpris.MediaPlayer2.spotify",
"/org/mpris/MediaPlayer2"
)
change_manager = dbus.Interface(
remote_object,
'org.freedesktop.DBus.Properties'
)
change_manager.connect_to_signal(
"PropertiesChanged",
propertiesChanged_handler
)
except dbus.exceptions.DBusException as dbe:
if (dbe.get_dbus_name() == "org.freedesktop.DBus.Error.ServiceUnknown"):
print("Please start Spotify first. (%s)"%(dbe.get_dbus_message()))
sys.exit(1)
loop = GLib.MainLoop()
loop.run()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment