Files changed: audio_recorder_server.py, BUILD.bat, AudioRecorder.spec
lameenc (pure-Python LAME wrapper, no external DLL)
lameenc is not installed, the MP3 option is
shown as disabled with a pip install lameenc hint rather than crashinglameenc added to BUILD.bat install step and AudioRecorder.spec hiddenimports| Format | Quality | ~Size/min | Notes |
|---|---|---|---|
| WAV | Lossless | 10 MB | Best for editing |
| FLAC | Lossless compressed | 5 MB | Good archive format |
| OGG | Lossy | 1.5 MB | Open format |
| MP3 | Lossy 192kbps | 1.4 MB | Most compatible |
Files changed: audio_recorder_server.py, AudioRecorderController.ino
_audio_callback on every audio block
/api/level endpoint — lightweight JSON poll {level, peak, state}
localhost → getUserMedia + Web Audio API (real frequency spectrum, 60fps)/api/level every 80ms (works on all browsers/devices)location.hostname === 'localhost'getUserMedia is denied on localhost, falls back to server-side poll/api/level every 120ms with short 500ms HTTP timeout* REC HH:MM:SS simultaneouslygetUserMedia fails on remote devicesBrowsers enforce a security policy that blocks microphone access (getUserMedia)
on non-secure origins. HTTP connections only qualify as secure if the hostname is
localhost or 127.0.0.1. Any other host — including a LAN IP like
192.168.1.x — is treated as insecure, and mic access is denied regardless of
user permissions. The server-side /api/level approach works everywhere.
Files changed: audio_recorder_server.py
POST /api/rename endpoint — renames a recording file
.., no overwriting existing filesrec_YYYY-MM-DD_HH-MM-SS.ext
to YYYYMMDD_DAY_HHMM.ext (e.g. 20260304_TUE_0947.wav)
Files changed: audio_recorder_server.py
/api/devices)POST /api/setdevice — sets device + channels + samplerate + format atomically/api/devices — returns device list with resolved_rate and resolved_ch
so the UI can pre-select the best supported settingsdevice_name field added to /api/status responseresolve_device_settings()
which probes the device's actual capabilities before opening the stream
max_input_channels and default_samplerate from sounddevicesd.check_input_settings() to validate each candidate before committing_actual_rate, _actual_ch on the recorder
instance and used for encoding (prevents corrupt WAV headers)getUserMedia)Files changed: src/launcher.py, audio_recorder_server.py, AudioRecorder.spec
subprocess.Popen in a frozen PyInstaller app re-executes the
EXE entry point, spawning endless launcher copiesmultiprocessing.freeze_support() as absolute first line of launcherthreading.Thread — no subprocess spawning at alluse_reloader=False — prevents Flask's file-watcher
from spawning its own child process_tkinter ModuleNotFoundError
pystray system tray icon + PIL for the
icon image. No window, no tkinter dependency.audio_recorder_server.py not found (Missing File dialog)
sys._MEIPASS path was being set after the path variable was evaluatedBUNDLE_DIR = sys._MEIPASS (bundled read-only files in temp dir)BASE_DIR = Path(sys.executable).parent (writable dir next to EXE){display:flex})
as template variables and raised UndefinedErrorrender_template_string() entirely. HTML returned as:
return html, 200, {"Content-Type": "text/html; charset=utf-8"}build_ui_html() builds HTML via Python string concatenation (no triple-quotes
to avoid any risk of template engine involvement)ValueError: signal only works in main thread
signal.signal() calls were at module top level in audio_recorder_server.pysignal.signal() calls inside if __name__ == "__main__":recorder_launcher.log next to the EXE
ctypes.windll.user32.MessageBoxW (no tkinter)Files changed: AudioRecorder.spec, BUILD.bat
ERROR: script not found during PyInstaller build
AudioRecorder.spec used os.path.dirname(SPECPATH) which went one directory
up from the spec file instead of using the spec file's own directorySRC_DIR = os.path.join(SPECPATH, 'src') — SPECPATH is already the
correct directorycd /d "%~dp0" as first command to ensure it always runs
from its own directory regardless of where it is launched frompyinstaller "%~dp0AudioRecorder.spec" instead of bare AudioRecorder.specFiles: audio_recorder_server.py, src/launcher.py, AudioRecorderController.ino,
AudioRecorder.spec, BUILD.bat
audio_recorder_server.py)sounddevice with float32 PCM bufferingsoundfile — WAV (PCM_16), FLAC (PCM_16), OGG (Vorbis)RecorderState machine: IDLE → RECORDING → PAUSED → SAVINGthreading.Lock/recordings/<filename>--list-devices flag to enumerate audio inputssrc/launcher.py)wait_for_server() polls /api/status up to 12 secondsrecorder_config.json persisted next to EXEAudioRecorderController.ino)console=False, upx=False)BUILD.bat installs all dependencies and builds in one stepdebug_test.py diagnostic tool for troubleshooting without building EXE| Package | Version | Purpose |
|---|---|---|
| Python | 3.14.2 | Runtime |
| Flask | 3.x | HTTP server |
| sounddevice | 0.4.x | Audio capture |
| soundfile | 0.12.x | WAV/FLAC/OGG encoding |
| numpy | 1.x+ | Audio data processing |
| Pillow | 10.x | Tray icon rendering |
| pystray | 0.19.x | System tray |
| lameenc | 1.x | MP3 encoding |
| PyInstaller | 6.19.0 | EXE bundling |
| ArduinoJson | 7.x | ESP32 JSON parsing |