Last active
December 29, 2023 21:49
-
-
Save looselyrigorous/0e0b409d5e429cb98bc2967084db5212 to your computer and use it in GitHub Desktop.
(Pipewire/Pulseaudio) Set app volume programmatically
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
import subprocess | |
import json | |
import re | |
def VolumeSpecificationValidator(value: str): | |
# Only validates absolute value volume specifications, | |
# as parsing relative values has additional complexity. | |
if value.isdigit(): | |
if not (0 <= int(value) <= 65535): | |
raise argparse.ArgumentTypeError( | |
"Integer volume specification must be between 0 (0%) and 65535 (100%)" | |
) | |
if value.replace(".", "", 1).isdigit(): | |
if not (0 <= float(value) <= 1.0): | |
raise argparse.ArgumentTypeError( | |
"Linear factor volume specification must be between 0 and 1.0" | |
) | |
pct_pattern = re.compile(r"(\d+(?:\.\d+)?)%") | |
match_pct = pct_pattern.fullmatch(value) | |
if match_pct: | |
pct_number = float(match_pct.group(1)) | |
if not (0 <= pct_number <= 100): | |
raise argparse.ArgumentTypeError( | |
"Percentage volume specification must be between 0% and 100%" | |
) | |
if not any([value.isdigit(), value.replace(".", "", 1).isdigit(), match_pct]): | |
raise argparse.ArgumentTypeError( | |
"Malformed input. Must be either a decimal percentage (55.3%), a linear factor (0.553) or an integer." | |
) | |
return value | |
parser = argparse.ArgumentParser( | |
prog="set_app_vol", | |
description="Set app volume programmatically", | |
) | |
parser.add_argument( | |
"app_name", | |
help='Application name. Use "pactl list sink-inputs" to find the desired value (application.name property)', | |
) | |
parser.add_argument("volume_verb", type=VolumeSpecificationValidator) | |
args = parser.parse_args() | |
app_name = args.app_name | |
sink_inputs = json.loads( | |
subprocess.check_output(["pactl", "-f", "json", "list", "sink-inputs"]).decode() | |
) | |
# Match first instance of desired application | |
# (Scrape output of pactl list sink-inputs to find app_name) | |
try: | |
matched_input = next( | |
( | |
input | |
for input in sink_inputs | |
if input.get("properties", {}).get("application.name") == app_name | |
), | |
None, | |
) | |
if matched_input: | |
sink_id = str(matched_input["index"]) | |
subprocess.run(["pactl", "set-sink-input-volume", sink_id, args.volume_verb]) | |
except KeyError: | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment