aboutsummaryrefslogtreecommitdiffstats
path: root/solo-tool-project/src/solo_tool/recorder.py
diff options
context:
space:
mode:
authorEddy Pedroni <epedroni@pm.me>2026-01-01 17:57:27 +0100
committerEddy Pedroni <epedroni@pm.me>2026-01-01 17:57:27 +0100
commit8ea2b64ff798af913dcba64baace8d2536bf0b18 (patch)
treef85ea2f371055e67c629909df4897aec2f4bbad2 /solo-tool-project/src/solo_tool/recorder.py
parent88ce99d87889cdf953af611ef09d7a12b6d23747 (diff)
Add Android app wrapper around web interface
Diffstat (limited to 'solo-tool-project/src/solo_tool/recorder.py')
-rw-r--r--solo-tool-project/src/solo_tool/recorder.py75
1 files changed, 75 insertions, 0 deletions
diff --git a/solo-tool-project/src/solo_tool/recorder.py b/solo-tool-project/src/solo_tool/recorder.py
new file mode 100644
index 0000000..fd2df02
--- /dev/null
+++ b/solo-tool-project/src/solo_tool/recorder.py
@@ -0,0 +1,75 @@
+import pyaudio as pa
+from pathlib import Path
+
+class Recording:
+ def __init__(self, frames: [], channels: int, samplingRate: int, sampleFormat: int):
+ self._frames = frames
+ self._channels = channels
+ self._samplingRate = samplingRate
+ self._sampleFormat = sampleFormat
+
+ def writeWav(self, file: Path) -> None:
+ import wave
+ with wave.open(str(file), "wb") as wf:
+ wf.setnchannels(self._channels)
+ wf.setsampwidth(self._sampleFormat)
+ wf.setframerate(self._samplingRate)
+ wf.writeframes(b''.join(self._frames))
+
+ def writeMp3(self, file: Path) -> None:
+ from pydub import AudioSegment
+ segment = AudioSegment(
+ data=b''.join(self._frames),
+ sample_width=self._sampleFormat,
+ frame_rate=self._samplingRate,
+ channels=self._channels
+ )
+ segment.export(str(file), format="mp3", bitrate="320k")
+
+class Recorder:
+ def __init__(self, bufferSize: int, samplingRate: int):
+ self._bufferSize = bufferSize
+ self._samplingRate = samplingRate
+ self._sampleFormat = pa.paInt16
+ self._channels = 2
+ self._pa = pa.PyAudio()
+ self._stream = None
+ self._frames = []
+
+ def __del__(self):
+ if self.recording:
+ self._stream.stop_stream()
+ self._stream.close()
+
+ def _callback(self, inData, frameCount, timeInfo, statusFlags):
+ if statusFlags != pa.paNoError:
+ print(f"Recorder callback got status {hex(statusFlags)}, aborting")
+ return (None, pa.paAbort)
+
+ self._frames.append(inData)
+ return (None, pa.paContinue)
+
+ def startRecording(self) -> None:
+ if self.recording:
+ return
+
+ self._frames.clear()
+ self._stream = self._pa.open(format=self._sampleFormat,
+ channels=self._channels,
+ rate=self._samplingRate,
+ frames_per_buffer=self._bufferSize,
+ input=True,
+ stream_callback=self._callback)
+
+ def stopRecording(self) -> Recording:
+ if not self.recording:
+ return None
+ self._stream.stop_stream()
+ self._stream.close()
+ self._stream = None
+ return Recording(self._frames, self._channels, self._samplingRate, self._pa.get_sample_size(self._sampleFormat))
+
+ @property
+ def recording(self) -> bool:
+ return self._stream is not None
+