Skip to content

Instantly share code, notes, and snippets.

@hoehermann
Last active November 13, 2024 00:22
Show Gist options
  • Select an option

  • Save hoehermann/8b6fa0682bd9192e835d1b7ba8daa4b6 to your computer and use it in GitHub Desktop.

Select an option

Save hoehermann/8b6fa0682bd9192e835d1b7ba8daa4b6 to your computer and use it in GitHub Desktop.

Revisions

  1. hoehermann revised this gist Nov 13, 2024. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions spotisilencer.py
    Original file line number Diff line number Diff line change
    @@ -13,9 +13,11 @@
    def pulse_mute_spotify(mutestate):
    with pulsectl.Pulse('spotisilencer') as pulse:
    for sink_input in pulse.sink_input_list():
    if (sink_input.name == 'Spotify'):
    # sink_input.name in ['Spotify', 'Playback'] is ambiguous
    binary = sink_input.proplist.get('application.process.binary', None)
    if (binary == 'spotify'):
    pulse.sink_input_mute(sink_input.index, mutestate)
    print(f'Set mutestate to {mutestate} for {sink_input}.')
    print(f'Set mutestate to {mutestate} for sink ({sink_input}) of {binary}.')

    def is_advert(metadata):
    """ Whether the current song is an advertisement. """
  2. hoehermann revised this gist Oct 28, 2024. 1 changed file with 8 additions and 75 deletions.
    83 changes: 8 additions & 75 deletions spotisilencer.py
    Original file line number Diff line number Diff line change
    @@ -8,81 +8,14 @@
    import time
    import re
    from gi.repository import GLib

    # copied from https://github.com/avindra/pacmd-python
    # TODO: switch to https://github.com/mk-fg/python-pulse-control
    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(r'(\* )?index: (\d+)')
    patternKeyValue = re.compile('\t*([^:]+):(?: (.+))?')
    patternEnumerating = re.compile('^\tproperties:')
    patternProps = re.compile(r'(\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(r'^\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()
    import pulsectl # https://github.com/mk-fg/python-pulse-control

    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 'application.process.binary' in sink['properties'] 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)))
    with pulsectl.Pulse('spotisilencer') as pulse:
    for sink_input in pulse.sink_input_list():
    if (sink_input.name == 'Spotify'):
    pulse.sink_input_mute(sink_input.index, mutestate)
    print(f'Set mutestate to {mutestate} for {sink_input}.')

    def is_advert(metadata):
    """ Whether the current song is an advertisement. """
    @@ -96,7 +29,7 @@ def is_advert(metadata):
    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')
    pulse_mute_spotify(is_ad)

    def main():
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    @@ -116,7 +49,7 @@ def main():
    )
    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()))
    print(f"Please start Spotify first. ({dbe.get_dbus_message()})")
    sys.exit(1)
    loop = GLib.MainLoop()
    loop.run()
  3. hoehermann revised this gist Oct 1, 2024. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions spotisilencer.py
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,7 @@
    from gi.repository import GLib

    # copied from https://github.com/avindra/pacmd-python
    # TODO: switch to https://github.com/mk-fg/python-pulse-control
    def parseList(output):
    lines = output.split('\n')
    first = lines.pop(0)
    @@ -18,10 +19,10 @@ def parseList(output):
    data = {}
    currentItem = {}
    currentIndex = -1
    patternNewItem = re.compile('(\* )?index: (\d+)')
    patternNewItem = re.compile(r'(\* )?index: (\d+)')
    patternKeyValue = re.compile('\t*([^:]+):(?: (.+))?')
    patternEnumerating = re.compile('^\tproperties:')
    patternProps = re.compile('(\S+) = "(.+)"')
    patternProps = re.compile(r'(\S+) = "(.+)"')
    enumeratingProps = None
    lastKey = None
    for line in lines:
    @@ -58,7 +59,7 @@ def parseList(output):
    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)
    currentItem[lastKey] += re.sub(r'^\s+', '\n', line)
    # Last item will need to be pushed manually
    data[currentIndex] = currentItem
    return data
    @@ -77,7 +78,7 @@ def pulse_mute_spotify(mutestate):
    # 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'
    if 'properties' in sink and 'application.process.binary' in sink['properties'] and sink['properties']['application.process.binary'] == 'spotify'
    ]
    for spotify_index in spotify_indices:
    pacmd_set_sink_input_mute(spotify_index, mutestate)
  4. hoehermann revised this gist Sep 7, 2022. 1 changed file with 7 additions and 4 deletions.
    11 changes: 7 additions & 4 deletions spotisilencer.py
    Original file line number Diff line number Diff line change
    @@ -84,10 +84,13 @@ def pulse_mute_spotify(mutestate):
    print('Set mutestate to %s for %s'%(mutestate, str(spotify_indices)))

    def is_advert(metadata):
    """ Whether this is an advertisement is being recorded. """
    m = metadata['Metadata']
    artist = m['xesam:artist'][0]
    return artist == ""
    """ 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
  5. hoehermann revised this gist Oct 26, 2021. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions spotisilencer.py
    Original file line number Diff line number Diff line change
    @@ -1,13 +1,13 @@
    #!/usr/bin/env python
    #!/usr/bin/env python3
    # encoding: utf-8

    import sys
    import dbus
    import dbus.mainloop.glib
    import gobject
    import subprocess
    import time
    import re
    from gi.repository import GLib

    # copied from https://github.com/avindra/pacmd-python
    def parseList(output):
    @@ -64,7 +64,7 @@ def parseList(output):
    return data

    def pacmd_list_sink_inputs():
    p = subprocess.Popen(['pacmd','list-sink-inputs'], stdout=subprocess.PIPE)
    p = subprocess.Popen(['pacmd','list-sink-inputs'], stdout=subprocess.PIPE, encoding='utf-8')
    out, err = p.communicate()
    return parseList(out)

    @@ -77,7 +77,7 @@ def pulse_mute_spotify(mutestate):
    # spotify may allocate more than one output
    spotify_indices = [
    index for index, sink in inputs.items()
    if sink['properties']['application.process.binary'] == 'spotify'
    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)
    @@ -114,7 +114,7 @@ def main():
    if (dbe.get_dbus_name() == "org.freedesktop.DBus.Error.ServiceUnknown"):
    print("Please start Spotify first. (%s)"%(dbe.get_dbus_message()))
    sys.exit(1)
    loop = gobject.MainLoop()
    loop = GLib.MainLoop()
    loop.run()

    if __name__ == '__main__':
  6. hoehermann revised this gist Jan 5, 2020. 1 changed file with 58 additions and 17 deletions.
    75 changes: 58 additions & 17 deletions spotisilencer.py
    Original file line number Diff line number Diff line change
    @@ -7,25 +7,66 @@
    import gobject
    import subprocess
    import time
    import re

    # 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)
    out, err = p.communicate()
    inputs = out.split('\n')
    inputs = "".join([i+'\n' for i in inputs if ('sink input(s) available.' not in i and 'volume:' not in i and ' ' not in i)]) # remove some less easy to parse information
    inputs = inputs.replace('\n ','}}, {"')
    inputs = inputs.replace(' index:','"index:')
    inputs = inputs.replace(':\n\t\t', '":{"')
    inputs = inputs.replace(': ','": "')
    inputs = inputs.replace('\n\t\t',', "')
    inputs = inputs.replace(' = "','": "')
    inputs = inputs.replace('\n\t','", "')
    inputs = inputs.replace('\n',' ')
    inputs = '[{'+inputs+'}}]' # inputs is now the string representation of a list of dicts
    try:
    return eval(inputs)
    except SyntaxError:
    return []
    return parseList(out)

    def pacmd_set_sink_input_mute(index, mutestate):
    p = subprocess.Popen(['pacmd','set-sink-input-mute', index, mutestate])
    @@ -35,8 +76,8 @@ def pulse_mute_spotify(mutestate):
    inputs = pacmd_list_sink_inputs()
    # spotify may allocate more than one output
    spotify_indices = [
    i['index'] for i in inputs
    if i['properties']['application.process.binary'] == 'spotify'
    index for index, sink in inputs.items()
    if sink['properties']['application.process.binary'] == 'spotify'
    ]
    for spotify_index in spotify_indices:
    pacmd_set_sink_input_mute(spotify_index, mutestate)
  7. hoehermann created this gist Dec 6, 2019.
    80 changes: 80 additions & 0 deletions spotisilencer.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,80 @@
    #!/usr/bin/env python
    # encoding: utf-8

    import sys
    import dbus
    import dbus.mainloop.glib
    import gobject
    import subprocess
    import time

    def pacmd_list_sink_inputs():
    p = subprocess.Popen(['pacmd','list-sink-inputs'], stdout=subprocess.PIPE)
    out, err = p.communicate()
    inputs = out.split('\n')
    inputs = "".join([i+'\n' for i in inputs if ('sink input(s) available.' not in i and 'volume:' not in i and ' ' not in i)]) # remove some less easy to parse information
    inputs = inputs.replace('\n ','}}, {"')
    inputs = inputs.replace(' index:','"index:')
    inputs = inputs.replace(':\n\t\t', '":{"')
    inputs = inputs.replace(': ','": "')
    inputs = inputs.replace('\n\t\t',', "')
    inputs = inputs.replace(' = "','": "')
    inputs = inputs.replace('\n\t','", "')
    inputs = inputs.replace('\n',' ')
    inputs = '[{'+inputs+'}}]' # inputs is now the string representation of a list of dicts
    try:
    return eval(inputs)
    except SyntaxError:
    return []

    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 = [
    i['index'] for i in inputs
    if i['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 this is an advertisement is being recorded. """
    m = metadata['Metadata']
    artist = m['xesam:artist'][0]
    return artist == ""

    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 = gobject.MainLoop()
    loop.run()

    if __name__ == '__main__':
    main()