diff --git a/isr.py b/isr.py index 2779131..cc1d903 100644 --- a/isr.py +++ b/isr.py @@ -10,7 +10,6 @@ import os import sys import time import wave -import struct import signal import logging import threading @@ -18,7 +17,7 @@ import configparser import subprocess import shutil from abc import ABC, abstractmethod -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import datetime, timedelta from pathlib import Path from typing import Optional, Dict, Any, List, Callable @@ -57,8 +56,6 @@ class AudioDevice: backend: str # Backend name ('alsa') is_default: bool = False # Is system default is_monitor: bool = False # Is a monitor/loopback source - description: str = "" # Extended description - extra: Dict[str, Any] = field(default_factory=dict) def __str__(self): flags = [] @@ -74,7 +71,6 @@ class AudioBackend(ABC): """Abstract base for audio capture backends.""" name: str = "base" - priority: int = 0 # Higher = preferred @classmethod @abstractmethod @@ -99,7 +95,6 @@ class ALSABackend(AudioBackend): """ALSA backend using arecord (raw PCM output, no sound server required).""" name = "alsa" - priority = 5 # Lowest priority — direct hardware access, use when no sound server runs @classmethod def is_available(cls) -> bool: @@ -251,19 +246,12 @@ class AudioSystem: def get_backend(self, name: str) -> Optional[AudioBackend]: return self._backends.get(name) - def get_preferred_backend(self) -> Optional[AudioBackend]: - """Get the highest priority available backend.""" - if not self._backends: - return None - return max(self._backends.values(), key=lambda b: b.__class__.priority) - def list_all_devices(self) -> List[AudioDevice]: """List devices from all available backends.""" all_devices = [] seen_names = set() - # Get devices from backends in priority order - for cls in sorted(self._backend_classes, key=lambda c: -c.priority): + for cls in self._backend_classes: if cls.name in self._backends: for dev in cls.list_devices(): # Deduplicate by name (same device may appear in multiple backends) @@ -733,16 +721,9 @@ class SoundcardRecorder(BaseRecorder): self.audio_buffer.clear() def close_current_file(self): - """Close current recording file.""" + """Flush any buffered audio, then close the current recording file.""" self._flush_buffer_to_file() - if self.current_file: - try: - self.current_file.close() - self.logger.info(f"[{self.name}] Closed: {self.current_filename}") - except Exception as e: - self.logger.error(f"[{self.name}] Error closing file: {e}") - self.current_file = None - self.current_filename = None + super().close_current_file() def record(self): """Main recording loop.""" @@ -990,22 +971,15 @@ def list_audio_devices(): print(" ISR Audio Device Discovery") print("=" * 70) - # Check available backends - available_backends = [] - if ALSABackend.is_available(): - available_backends.append(('alsa', 'ALSA (arecord)', 5)) - - if not available_backends: + if not ALSABackend.is_available(): print("\n No audio backends available!") - print("\n Install one of:") - print(" sudo apt install alsa-utils (ALSA, always available on Linux)") + print("\n Install ALSA utilities:") + print(" sudo apt install alsa-utils") print() return print("\n Available Backends:") - for name, label, priority in sorted(available_backends, key=lambda x: -x[2]): - marker = " (preferred)" if priority == max(b[2] for b in available_backends) else "" - print(f" - {label}{marker}") + print(" - ALSA (arecord)") # Initialize audio system and list devices audio_system = AudioSystem(logger) @@ -1016,42 +990,21 @@ def list_audio_devices(): print() return - # Group by type - monitors = [d for d in devices if d.is_monitor] - inputs = [d for d in devices if not d.is_monitor] - - if inputs: - print("\n Input Devices:") - print(" " + "-" * 68) - for dev in inputs: - flags = [] - if dev.is_default: - flags.append("DEFAULT") - flag_str = f" [{', '.join(flags)}]" if flags else "" - print(f"\n {dev.name}{flag_str}") - print(f" ID: {dev.id} | Backend: {dev.backend}") - print(f" Channels: {dev.channels} | Sample Rate: {dev.sample_rate} Hz") - - if monitors: - print("\n Monitor/Loopback Sources:") - print(" " + "-" * 68) - for dev in monitors: - flags = ["MONITOR"] - if dev.is_default: - flags.append("DEFAULT") - flag_str = f" [{', '.join(flags)}]" - print(f"\n {dev.name}{flag_str}") - print(f" ID: {dev.id} | Backend: {dev.backend}") - print(f" Channels: {dev.channels} | Sample Rate: {dev.sample_rate} Hz") + print("\n Input Devices:") + print(" " + "-" * 68) + for dev in devices: + flag_str = " [DEFAULT]" if dev.is_default else "" + print(f"\n {dev.name}{flag_str}") + print(f" ID: {dev.id} | Backend: {dev.backend}") + print(f" Channels: {dev.channels} | Sample Rate: {dev.sample_rate} Hz") print("\n" + "=" * 70) print(" Configuration Examples:") print("-" * 70) print(" device = default # Use system default input") - print(" device = monitor # Use first monitor/loopback source") print(" device = # Match by partial name") - print(" device = # Use exact backend ID") - print(" backend = pipewire # Force specific backend") + print(" device = # Use exact ALSA ID, e.g. hw:0,0") + print(" device = # Any ALSA PCM name from asound.conf, e.g. shared_mic") print("=" * 70 + "\n") @@ -1061,17 +1014,11 @@ def main(): list_audio_devices() return - # Get config file + # Get config file (RecorderManager exits with a usage message if it's missing) config_file = 'config.ini' if len(sys.argv) > 1: config_file = sys.argv[1] - if not os.path.exists(config_file): - print(f"Error: Configuration file '{config_file}' not found!") - print("Usage: python ISR.py [config.ini]") - print(" python ISR.py --list-devices") - sys.exit(1) - # Docker sends SIGTERM before SIGKILL — treat it the same as Ctrl+C def _sigterm(sig, frame): raise KeyboardInterrupt()