Skip to content

Instantly share code, notes, and snippets.

@ramast
Last active September 3, 2023 11:12
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ramast/c47bd5e57586e9c2deb74975e27089f0 to your computer and use it in GitHub Desktop.
Save ramast/c47bd5e57586e9c2deb74975e27089f0 to your computer and use it in GitHub Desktop.
Record a program's output with PulseAudio
#!/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()
@ati-ince
Copy link

Hey @ramast, do you think this script can adapt to Win(10)+?

@ramast
Copy link
Author

ramast commented Jun 30, 2021

Hi @ati-ince, Windows doesn't use PulseAudio and this solution is based on PulseAudio so no it's impossible :(

@ati-ince
Copy link

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