-
-
Save ramast/c47bd5e57586e9c2deb74975e27089f0 to your computer and use it in GitHub Desktop.
#!/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() |
cleaned up this code (with the black formatter), switched to subprocess for calls and made the encoder configurable in:
https://gitlab.com/anarcat/scripts/-/blob/master/pulse-recorder.py
let me know what the license of this is so i can give proper credit! :)
Thanks Anarcat, This work is all based on code written on stackoverflow
https://askubuntu.com/questions/60837/record-a-programs-output-with-pulseaudio/910879#910879
By users Waschtl and KrisWebDev
If you want to give credit it, I guess can link to that stackoverflow link ?
As far as I know there are no license, you can do whatever you want with it.
If you want to give credit it, I guess can link to that stackoverflow link ?
I had that already in the commitlogs, but made it explicit in the comments at the top of file.
As far as I know there are no license, you can do whatever you want with it.
Actually, contents on Stackoverflow is covered by the CC-BY-SA-4.0 license, so that kind of matters (for example, I have to give attribution, and so do you!) :)
Actually, contents on Stackoverflow is covered by the CC-BY-SA-4.0
Thanks, This is really good to know. I've already gave attributions first comment after the code.
Hopefully that'd be enough
yeah i guess that's alright :)
oh, and by the way, the script has evolved quite a bit. it now properly handles multiple outputs and has an "automatic" mode that doesn't prompt the user. i hope you like it!
Your scripts looks a lot more sophisticated and judging by the code I think it has also more features.
I've tried to run it but ran into a problem
First I ran the script like this python3 ~/pulse-recorder.py --raw
and it just hangs with no output.
I realized that it's because I didn't pass the -i
parameter but what is happening in this case?
I've tried again with -i
and I liked how I could identify the process by it's unique ID. Really helpful but after choosing the process I wanted to record it didn't record anything.
I've ran it again with --debug
option and I guess that was the issue
WARNING:root:Couldn't find selected audio channel, retrying
I think this output should be visible without the need for --debug
. I am not sure why it couldn't find "selected audio channel` though? I've tried same experiment with my old script and seemed to record fine.
Steps to reproduce:
- Open youtube video (i've used firefox if that make any difference)
- pause the video
- run the script and choose Firefox process id
- run the video
- go back to the script and press enter to record.
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)
Traceback (most recent call last):
File "/home/ramast/pulse-recorder.py", line 250, in <module>
main(args)
File "/home/ramast/pulse-recorder.py", line 145, in main
record_module_id = load_record_module(sinks[client_index])
KeyError: 6966
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.
Code review:
signal.signal(signal.SIGTERM, cleanup)
signal.signal(signal.SIGINT, cleanup)
Please review these lines as they are not indented.
Thank you for the correction though pulseaudio is now obsolete in favor of pipewire.
With pipewire its much simpler to record. Following line does the recording as well as converting the audio to mp3
pw-record -P '{ stream.capture.sink=true }' -q 1 --channels 2 --format=s16 -| lame -r -q 3 --lowpass 17 --abr 192 - "temp.mp3"
Explanation on how to use can be found here
https://askubuntu.com/questions/60837/record-a-programs-output-with-pulseaudio/910879#910879