Add new python recorder for clients
This commit is contained in:
150
Client/pdab/recorder.py
Normal file
150
Client/pdab/recorder.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import pyaudio
|
||||
import wave, logging, threading, time, queue, signal, argparse
|
||||
from os import path, makedirs
|
||||
|
||||
logging.basicConfig(format="%(asctime)s: %(message)s", level=logging.INFO,datefmt="%H:%M:%S")
|
||||
|
||||
class DiscordRecorder:
|
||||
def __init__(self, DEVICE_ID, CHUNK = 1024, FORMAT = pyaudio.paInt16, CHANNELS = 2, RATE = 48000, FILENAME = "./recs/radio.wav"):
|
||||
self.pa_instance = pyaudio.PyAudio()
|
||||
|
||||
self.DEVICE_ID = DEVICE_ID
|
||||
self.CHUNK = CHUNK
|
||||
self.FORMAT = FORMAT
|
||||
self.CHANNELS = CHANNELS
|
||||
self.RATE = RATE
|
||||
|
||||
self.FILENAME = FILENAME
|
||||
self._check_file_path_exists()
|
||||
|
||||
self.queued_frames = queue.Queue()
|
||||
|
||||
self.stop_threads = threading.Event()
|
||||
|
||||
self.recording_thread = None
|
||||
self.saving_thread = None
|
||||
|
||||
self.running_stream = None
|
||||
|
||||
# Wrapper to check if the given filepath (not file itself) exists
|
||||
def _check_file_path_exists(self):
|
||||
if not path.exists(path.dirname(self.FILENAME)):
|
||||
makedirs(path.dirname(self.FILENAME), exist_ok=True)
|
||||
|
||||
# Wrapper for the recorder thread; Adds new data to the queue
|
||||
def _recorder(self):
|
||||
logging.info("* Recording Thread Starting")
|
||||
while True:
|
||||
data = self.running_stream.read(self.CHUNK)
|
||||
self.queued_frames.put(data)
|
||||
|
||||
# check for stop
|
||||
if self.stop_threads.is_set():
|
||||
break
|
||||
|
||||
# Wrapper for saver thread; Saves the queue to the file
|
||||
def _saver(self):
|
||||
logging.info("* Saving Thread Starting")
|
||||
while True:
|
||||
if not self.queued_frames.empty():
|
||||
dequeued_frames = []
|
||||
for i in range(self.queued_frames.qsize()):
|
||||
dequeued_frames.append(self.queued_frames.get())
|
||||
|
||||
if not path.isfile(self.FILENAME):
|
||||
wf = wave.open(self.FILENAME, 'wb')
|
||||
wf.setnchannels(self.CHANNELS)
|
||||
wf.setsampwidth(self.pa_instance.get_sample_size(self.FORMAT))
|
||||
wf.setframerate(self.RATE)
|
||||
wf.writeframes(b''.join(dequeued_frames))
|
||||
wf.close()
|
||||
else:
|
||||
read_file = wave.open(self.FILENAME, 'rb')
|
||||
read_file_data = read_file.readframes(read_file.getnframes())
|
||||
read_file.close()
|
||||
|
||||
wf = wave.open(self.FILENAME, 'wb')
|
||||
wf.setnchannels(self.CHANNELS)
|
||||
wf.setsampwidth(self.pa_instance.get_sample_size(self.FORMAT))
|
||||
wf.setframerate(self.RATE)
|
||||
|
||||
wf.writeframes(read_file_data)
|
||||
wf.writeframes(b''.join(dequeued_frames))
|
||||
wf.close()
|
||||
|
||||
# check for stop
|
||||
if self.stop_threads.is_set():
|
||||
break
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
# Start the recording function
|
||||
def start_recording(self):
|
||||
logging.info("* Recording")
|
||||
|
||||
self.running_stream = self.pa_instance.open(
|
||||
input_device_index=self.DEVICE_ID,
|
||||
format=self.FORMAT,
|
||||
channels=self.CHANNELS,
|
||||
rate=self.RATE,
|
||||
input=True,
|
||||
frames_per_buffer=self.CHUNK
|
||||
)
|
||||
|
||||
self.recording_thread = threading.Thread(target=self._recorder)
|
||||
self.recording_thread.start()
|
||||
|
||||
self.saving_thread = threading.Thread(target=self._saver)
|
||||
self.saving_thread.start()
|
||||
|
||||
# Stop the recording function
|
||||
def stop_recording(self):
|
||||
self.stop_threads.set()
|
||||
self.recording_thread.join()
|
||||
self.saving_thread.join()
|
||||
self.running_stream.stop_stream()
|
||||
self.running_stream.close()
|
||||
self.pa_instance.terminate()
|
||||
|
||||
logging.info("* Done recording")
|
||||
|
||||
|
||||
class GracefulExitCatcher:
|
||||
def __init__(self, stop_callback):
|
||||
self.stop = False
|
||||
|
||||
# The function to run when the exit signal is caught
|
||||
self.stop_callback = stop_callback
|
||||
|
||||
# Update what happens when these signals are caught
|
||||
signal.signal(signal.SIGINT, self.exit_gracefully)
|
||||
signal.signal(signal.SIGTERM, self.exit_gracefully)
|
||||
|
||||
def exit_gracefully(self, *args):
|
||||
logging.info("* Stop signal caught...")
|
||||
|
||||
# Stop the main loop
|
||||
self.stop = True
|
||||
|
||||
# Run the given callback function
|
||||
self.stop_callback()
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("deviceId", type=int, help="The ID of the audio device to use")
|
||||
parser.add_argument("filename", type=str, help="The filepath/filename of the output file")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.debug("Arguments:", args)
|
||||
|
||||
recorder = DiscordRecorder(args.deviceId, FILENAME=args.filename)
|
||||
|
||||
exit_catcher = GracefulExitCatcher(recorder.stop_recording)
|
||||
|
||||
recorder.start_recording()
|
||||
|
||||
while not exit_catcher.stop:
|
||||
time.sleep(1)
|
||||
Reference in New Issue
Block a user