Last active
September 3, 2023 11:12
-
-
Save ramast/c47bd5e57586e9c2deb74975e27089f0 to your computer and use it in GitHub Desktop.
Record a program's output with PulseAudio
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 | |
# Based on code from these stackoverflow answers: | |
# https://askubuntu.com/questions/60837/record-a-programs-output-with-pulseaudio/910879#910879 | |
import re | |
import subprocess | |
import sys | |
import os | |
import signal | |
from time import sleep | |
INDEX_RE = re.compile(r'[0-9]+$') | |
APP_NAME_RE = re.compile(r'"([^"]+)"') | |
SINK_RE=re.compile("\s*sink: ([0-9]+) <.*>") | |
DEFAULT_OUTPUT_RE = re.compile(r'^\s*name: <([^ >]+)>') | |
record_module_id = None | |
def get_default_output(): | |
#pacmd list-sinks | grep -A1 "* index" | grep -oP "<\K[^ >]+" | |
output = subprocess.run(["pacmd", "list-sinks"], stdout=subprocess.PIPE, check=True).stdout | |
for line in output.decode('utf-8').split('\n'): | |
match = DEFAULT_OUTPUT_RE.match(line) | |
if match: | |
return match[1] | |
print("Can't seem to find proper input sink, are you using pulseaudio?") | |
sys.exit(3) | |
def load_record_module(): | |
default_output = get_default_output() | |
output = subprocess.run( | |
["pactl", "load-module", "module-combine-sink", "sink_name=record-n-play", f"slaves={default_output}", | |
"sink_properties=device.description=Record-and-Play"], | |
stdout=subprocess.PIPE, check=True).stdout | |
return int(output.strip()) | |
def load_apps(): | |
output = subprocess.run(["pacmd", "list-sink-inputs"], stdout=subprocess.PIPE, check=True).stdout | |
output = output.decode('utf-8').split('\n') | |
indexes = [] | |
app_names = [] | |
sinks = [] | |
for line in output: | |
if "index" in line: | |
index = INDEX_RE.findall(line)[0] | |
indexes.append(index) | |
elif "application.name" in line: | |
app_name = APP_NAME_RE.findall(line)[0] | |
app_names.append(app_name) | |
elif len(sinks) < len(indexes) and "sink: " in line: | |
sink = SINK_RE.match(line)[1] | |
sinks.append(sink) | |
if len(indexes) == 0: | |
print("Sorry, couldn't find any input audio channels") | |
sys.exit(1) | |
return indexes, app_names, sinks | |
def cleanup(*args, **kwargs): | |
if record_module_id is None: | |
sys.exit(0) | |
return | |
os.system(f"pactl move-sink-input {indexes[user_selection]} {sinks[user_selection]}") | |
os.system(f"pactl unload-module {record_module_id}") | |
print("Terminated") | |
sys.exit(0) | |
signal.signal(signal.SIGTERM, cleanup) | |
signal.signal(signal.SIGINT, cleanup) | |
if os.path.exists("temp.mp3"): | |
print("temp.mp3 already exist, aborting") | |
sys.exit(2) | |
_, app_names, _ = load_apps() | |
print("") | |
for idx, app_name in enumerate(app_names): | |
print(f"{idx + 1} - {app_name}") | |
print("") | |
while True: | |
try: | |
user_selection = int(input("Please enter a number: ")) | |
except ValueError: | |
print("Only numbers are allowed") | |
continue | |
if user_selection > len(app_names) or user_selection <= 0: | |
print("Number out of range") | |
continue | |
user_selection = int(user_selection) - 1 | |
break | |
app_name = app_names[user_selection] | |
print(f"Your selection was: {app_name}") | |
input("Please press enter when you are ready to start") | |
while True: | |
indexes, app_names, sinks = load_apps() | |
if app_name not in app_names: | |
print("Couldn't find selected audio channel, retrying") | |
sleep(0.2) | |
continue | |
user_selection = app_names.index(app_name) | |
record_module_id=load_record_module() | |
os.system(f"pactl move-sink-input {indexes[user_selection]} record-n-play") | |
os.system(f"parec --format=s16le -d record-n-play.monitor | lame -r -q 3 --lowpass 17 --abr 192 - 'temp.mp3'") | |
cleanup() |
Please ignore that, I don't think it was a mistake form the script.
Seems to be working fine now
I've updated my stackoverflow answer to give mention to your script.
Thanks for sharing!!
Hey @ramast, do you think this script can adapt to Win(10)+?
Hi @ati-ince, Windows doesn't use PulseAudio and this solution is based on PulseAudio so no it's impossible :(
Sorry to hear that @ramast :"-( I didn't find yet any operating system independent solution. So, thank you for solving the Linux side problem.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks but still doesn't seem to be working :(
running clients:
6966 - Firefox
Please enter a number: 6966
Press enter to record from Firefox...
INFO: Recording from client 6966 (Firefox)