Skip to content

Instantly share code, notes, and snippets.

@looselyrigorous
Last active December 29, 2023 21:49
Show Gist options
  • Save looselyrigorous/0e0b409d5e429cb98bc2967084db5212 to your computer and use it in GitHub Desktop.
Save looselyrigorous/0e0b409d5e429cb98bc2967084db5212 to your computer and use it in GitHub Desktop.
(Pipewire/Pulseaudio) Set app volume programmatically
#!/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