-
Notifications
You must be signed in to change notification settings - Fork 0
/
bloopvolume.py
executable file
·177 lines (142 loc) · 4.66 KB
/
bloopvolume.py
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
#!/usr/bin/env python
# BloopVolume - A command line volume control wth audible & visible
# notification.
from argparse import ArgumentParser
from subprocess import call
from pulsectl import Pulse
NOTIFICATION_ID = '6788'
DEFAULT_STEP = 5
DEFAULT_SOUND_FILE = (
'/usr/share/sounds/freedesktop/stereo/audio-volume-change.oga'
)
def call_subprocess(command):
"""
Run an external program
Args:
command (list): list of arguments
Returns:
boolean for success or failure
"""
try:
result = call(command) == 0
except FileNotFoundError:
return False
else:
return result
def playsound(sink_index, soundfile):
"""
Use external program 'pactl' to play a sound file
Args:
sink_index (int): sink to use for output
soundfile (str): full path of file to play
Returns:
boolean for success or failure
"""
# pactl first needs the sound file stored on the server
command = [
"pactl", "upload-sample", soundfile, "volume-change"
]
if call_subprocess(command):
# if the upload succeeds we can play the sound
command = [
"pactl", "play-sample", "volume-change", str(sink_index)
]
return call_subprocess(command)
else:
return False
def get_active_sink(pulse):
"""
Use pulsectl to select the active sound sink
A sink list is retrieved and iterated, the last found as 'running' is used
If no sink is 'running', we default to the first entry
Args:
pulse (Pulse): an instance of pulsectl.Pulse
Returns:
PulseSinkInfo: an instance of pulsectl.PuleSinkInfo
"""
active_sink = None
sink_list = pulse.sink_list()
for sink in sink_list:
if sink.state == 'running':
active_sink = sink
if active_sink is None:
active_sink = sink_list[0]
return active_sink
def send_notification(volume, mute):
"""
Use external program 'dunstify' to display a notification
Args:
volume (int): the volume level to display
mute (bool): mute status
Returns:
boolean for success or failure
"""
command = [
"dunstify",
"-r", NOTIFICATION_ID,
"-u", "normal"
]
if mute:
mute_symbol = '🔇' # U+1F507
command += [
mute_symbol+" Volume: Mute",
"-h", "int:value:0" # show zero bar to keep notification size
]
else:
if volume < 0.3:
volume_symbol = '🔈' # U+1F508
elif volume < 0.6:
volume_symbol = '🔉' # U+1F509
else:
volume_symbol = '🔊' # U+1F50A
command += [
volume_symbol+" Volume: ",
"-h", # pass hint and value to show progress bar
"int:value:{:.0f}".format(volume*100)
]
return call_subprocess(command)
def do_action(action, step=DEFAULT_STEP, sound_file=DEFAULT_SOUND_FILE):
"""
Change volume of the active sound sink
Args:
action (str): options 'up', 'down' or 'mute'
step (int): the amount to change the volume (up or down)
sound_file (str): sound to play when notification is shown
Returns:
boolean for success or failure
"""
with Pulse('volume-changer') as pulse:
active_sink = get_active_sink(pulse)
# round volume to nearest step value
volume = step * round((active_sink.volume.value_flat * 100) / step)
is_mute = (action == 'mute')
if action == 'up':
volume += step
if volume > 150:
volume = 150
elif action == 'down':
volume -= step
if volume < 0:
volume = 0
elif action == 'mute':
is_mute = not (active_sink.mute == 1)
else:
return False
pulse.mute(active_sink, is_mute)
pulse.volume_set_all_chans(active_sink, volume / 100)
playsound(active_sink.index, sound_file)
return send_notification(active_sink.volume.value_flat, is_mute)
def main():
"""
entry point to parse command line arguments
"""
parser = ArgumentParser(description='Pulseaudio volume controller')
parser.add_argument('action', choices=['up', 'down', 'mute'])
parser.add_argument('--step', dest='step', type=int, default=DEFAULT_STEP,
help='Percentage to change the volume')
parser.add_argument('--sound', dest='sound', default=DEFAULT_SOUND_FILE,
help='Specify a sound file to play')
args = parser.parse_args()
do_action(args.action, args.step, args.sound)
if __name__ == "__main__":
main()