7
from argparse import ArgumentParser
8
from subprocess import check_output, check_call, CalledProcessError
10
TYPES = ("source", "sink")
11
DIRECTIONS = {"source": "input", "sink": "output"}
13
default_pattern = "(?<=Default %s: ).*"
14
index_regex = re.compile("(?<=index: )[0-9]*")
15
muted_regex = re.compile("(?<=Mute: ).*")
16
volume_regex = re.compile("(?<=Volume: 0:)\s*[0-9]*")
17
name_regex = re.compile("(?<=Name:).*")
19
entry_pattern = "Name: %s.*?(?=Properties)"
22
def unlocalized_env(reset={"LANG": "C.UTF-8"}):
24
Create un-localized environment.
26
Produce an environment that is suitable for subprocess.Popen() and
27
associated subprocess functions. The returned environment is equal to a
28
copy the current environment, updated with the value of the reset argument.
30
env = dict(os.environ)
35
def set_profile_hdmi():
36
"""Sets desired device as active profile. This is typically
37
used as a fallback for setting HDMI as the output device.
40
profile = "output:hdmi-stereo-extra1+input:analog-stereo"
41
output_list = check_output(["pactl", "list"],
42
universal_newlines=True,
43
env=unlocalized_env())
45
if profile not in output_list:
46
profile = profile.replace("-extra1", "")
48
# Try and set device as default audio output
50
check_call(["pactl", "set-card-profile", "0", profile])
51
except CalledProcessError as error:
52
print("Failed setting audio output to:%s: %s" % (profile, error))
55
def get_current_profile_setting():
56
"""Captures and Writes current audio profile setting"""
57
output_list = check_output(["pactl", "list"],
58
universal_newlines=True,
59
env=unlocalized_env())
60
active_profile = re.findall("Active\sProfile.*", output_list)[0]
63
open("active_output", 'w').write(active_profile)
65
print("Failed to save active output information: %s" %
69
def restore_profile_setting():
71
setting_info = open("active_output").read()
72
profile = setting_info.split("Active Profile:")[-1]
73
except (IOError, IndexError):
74
print("Failed to retrieve previous profile information")
78
check_call(["pactl", "set-card-profile", "0", profile.strip()])
79
except CalledProcessError as error:
80
print("Failed setting audio output to:%s: %s" % (profile, error))
84
sink_inputs = check_output(["pacmd", "list-sink-inputs"],
85
universal_newlines=True,
86
env=unlocalized_env())
87
input_indexes = index_regex.findall(sink_inputs)
89
for input_index in input_indexes:
91
check_call(["pacmd", "move-sink-input", input_index, name])
92
except CalledProcessError:
93
print("Failed to move input %d to sink %d" % (input_index, name))
97
def store_audio_settings(file):
100
settings_file = open(file, 'w')
102
print("Failed to save settings: %s" % sys.exc_info()[1])
106
pactl_status = check_output(["pactl", "stat"],
107
universal_newlines=True,
108
env=unlocalized_env())
109
default_regex = re.compile(default_pattern % type.title())
110
default = default_regex.search(pactl_status).group()
112
print("default_%s: %s" % (type, default), file=settings_file)
114
pactl_list = check_output(["pactl", "list", type + 's'],
115
universal_newlines=True,
116
env=unlocalized_env())
118
entry_regex = re.compile(entry_pattern % default, re.DOTALL)
119
entry = entry_regex.search(pactl_list).group()
121
muted = muted_regex.search(entry)
122
print("%s_muted: %s" % (type, muted.group().strip()),
125
volume = int(volume_regex.search(entry).group().strip())
127
print("%s_volume: %s%%" % (type, str(volume)),
131
def set_audio_settings(device, mute, volume):
132
print(device, mute, volume)
135
pactl_entries = check_output(["pactl", "list", type + 's'],
136
universal_newlines=True,
137
env=unlocalized_env())
139
# Find the name of the sink/source we want to set
140
names = name_regex.findall(pactl_entries)
144
if device in name and DIRECTIONS[type] in name:
146
check_call(["pacmd", "set-default-%s" % type, name])
147
except CalledProcessError:
148
print("Failed to set default %s" % type)
156
"set-%s-mute" % type, name, str(mute)])
158
print("Failed to set mute for %s" % name)
162
check_call(["pactl", "set-%s-volume" % type,
163
name, str(volume) + '%'])
165
print("Failed to set volume for %s" % name)
169
def restore_audio_settings(file):
171
settings_file = open(file).read().split()
173
print("Unable to open existing settings file: %s" %
178
#First try to get the three elements we need. If we fail to get any of them, it means
179
#the file's format is incorrect, so we just abort.
181
name = settings_file[settings_file.index("default_%s:" % type) + 1]
182
muted = settings_file[settings_file.index("%s_muted:" % type) + 1]
183
volume = settings_file[settings_file.index("%s_volume:" % type) + 1]
185
print("Unable to restore settings because settings file is invalid")
191
check_call(["pacmd", "set-default-%s" % type, name])
192
except CalledProcessError:
193
print("Failed to set default %s" % type)
202
check_call(["pactl", "set-%s-mute" % type, name, muted])
204
print("Failed to set mute for %s" % device)
210
check_call(["pactl", "set-%s-volume" % type, name, volume])
212
print("Failed to set volume for %s" % device)
217
parser = ArgumentParser("Manipulate PulseAudio settings")
218
parser.add_argument("action",
219
choices=['store', 'set', 'restore'],
220
help="Action to perform with the audio settings")
221
parser.add_argument("-d", "--device",
222
help="The device to apply the new settings to.")
223
parser.add_argument("-m", "--mute",
225
help="""The new value for the mute setting
226
of the specified device.""")
227
parser.add_argument("-v", "--volume",
229
help="""The new value for the volume setting
230
of the specified device.""")
231
parser.add_argument("-f", "--file",
232
help="""The file to store settings in or restore
234
args = parser.parse_args()
236
if args.action == "store":
238
print("No file specified to store audio settings!")
241
store_audio_settings(args.file)
242
get_current_profile_setting()
243
elif args.action == "set":
245
print("No device specified to change settings of!")
248
print("No volume level specified!")
251
if args.device == "hdmi":
253
set_audio_settings(args.device, args.mute, args.volume)
254
elif args.action == "restore":
255
restore_audio_settings(args.file)
256
restore_profile_setting()
258
print(args.action + "is not a valid action")
263
if __name__ == "__main__":