1

I want to switch between sound sinks/outputs on Ubuntu using a shortcut. I want the solution to work with any number of devices and switching all sink-inputs in the process.

soriak
  • 303
  • 3
  • 11

1 Answers1

1

I want to provide my solution for anyone to use and improve as it got me pretty frustrated that none of the solutions (i.e. Audio output device, fast switch?) worked under all conditions.

Thus i created a python script with a little overkill (and bad and ugly regexs) to do the job:

#!/usr/bin/env python3
#this little script switches the sound sink on ubuntu
# https://askubuntu.com/questions/156895/how-to-switch-sound-output-with-key-shortcut/1203350#1203350 and https://askubuntu.com/questions/1011806/how-do-i-switch-the-audio-outputs-of-an-audio-device-from-cli?noredirect=1&lq=1 were helpful
import argparse
import logging
import subprocess
import re

#a simple representation of all relevant info of an audio sink for this script class Sink: def init(self, index, name, state): self.index = index self.name = name self.state = state if state in ["RUNNING", "IDLE"]: self.selected = True else: self.selected = False

def __str__(self):
    return 'sink\nindex: {self.index}\nname: {self.name}\nstate: {self.state}\nselected: {self.selected}\n'.format(self=self)

#a simple representation of all relevant info of an audio sink-input for this script class Sink_Input: def init(self, index, application_name, sink, state): self.index = index self.application_name = application_name self.sink = sink self.state = state

def __str__(self):
    return 'sink-input\nindex: {self.index}\napplication_name: {self.application_name}\nsink: {self.sink}\nstate: {self.state}\n'.format(self=self)


def get_sinks(): pacmd_output = str(subprocess.check_output(["pacmd", "list-sinks"])) sinks_raw = pacmd_output.split("index: ") sinks = [] for sink_raw in sinks_raw[1:]: index = int(re.findall("^\d+", sink_raw)[0]) name = re.findall("device.description = "[^\"]"", sink_raw)[0][22:-1] state = re.findall("state: [A-Z]", sink_raw)[0][7:] sink = Sink(index, name, state) sinks.append(sink) return sinks

def get_sink_inputs(): sink_inputs = [] pacmd_output = str(subprocess.check_output(["pacmd", "list-sink-inputs"])) inputs_raw = pacmd_output.split("index: ") for input_raw in inputs_raw[1:]: index = int(re.findall("^\d+", input_raw)[0]) sink = int(re.findall("sink: \d", input_raw)[0][5:]) application_name = re.findall("application.name = "[^\"]"", input_raw)[0][20:-1] state = re.findall("state: [A-Z]*", input_raw)[0][7:] sink_input = Sink_Input(index, application_name, sink, state) sink_inputs.append(sink_input) return sink_inputs

def switch_to_next_sink(sinks, notify): current_sink = None next_sink = sinks[0] for i in range(len(sinks)): if sinks[i].selected: current_sink = sinks[i] if i == len(sinks) -1: next_sink = sinks[0] else: next_sink = sinks[i+1] #switch default sink to next sink subprocess.call(["pacmd", "set-default-sink", str(next_sink.index)]) #move all apps to next sink for sink_input in get_sink_inputs(): subprocess.call(["pacmd", "move-sink-input", str(sink_input.index), str(next_sink.index)]) if notify: subprocess.call(["notify-send", "Changed audio sink", "new audio sink is " + next_sink.name])

def main(): parser = argparse.ArgumentParser(description='''Switches to the 'next' audio sink on Ubuntu and can provide additional info on sound sinks. If no arguments are passed only the next audio sink is selected. For ease of use add /usr/bin/python3 /home/sebi/misc/switch_sound_sink.py as keyboard shortcut i.e. Super+Shift+S (Super+O somehow not working) ''') parser.add_argument('-s', '--state', help='boiled down output of pacmd list-sinks and list-sink-inputs', action='store_true') parser.add_argument('-n', '--notify', help='send notification to the desktop', action='store_true')

logLevelsRange = [logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARN, logging.ERROR, logging.CRITICAL]
parser.add_argument('-l', '--logLevel', help='the log level', type=int, choices=logLevelsRange,
                    default=logging.INFO)

args = parser.parse_args()

if args.state:
    sinks = get_sinks()
    for sink in sinks:
        print(sink)
    sink_inputs = get_sink_inputs()
    for sink_input in sink_inputs:
        print(sink_input)
else:
    sinks = get_sinks()
    switch_to_next_sink(sinks, args.notify)

main()

I added optional desktops notifications but don't use them as they can't be shown only temporarily. (How can I send a custom desktop notification?)

soriak
  • 303
  • 3
  • 11