refactor: remove dead code from isr.py

- Drop unused struct import, AudioDevice.extra/description fields
- Remove unused get_preferred_backend() and the backend priority
  machinery (only one backend exists)
- Deduplicate SoundcardRecorder.close_current_file via super()
- Remove duplicate config-exists check in main()
- Simplify --list-devices output: drop dead monitor grouping and the
  nonexistent pipewire backend example

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 11:41:37 +02:00
parent 4539ff78fa
commit 792f2b1fd5
+13 -66
View File
@@ -10,7 +10,6 @@ import os
import sys import sys
import time import time
import wave import wave
import struct
import signal import signal
import logging import logging
import threading import threading
@@ -18,7 +17,7 @@ import configparser
import subprocess import subprocess
import shutil import shutil
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass, field from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Optional, Dict, Any, List, Callable from typing import Optional, Dict, Any, List, Callable
@@ -57,8 +56,6 @@ class AudioDevice:
backend: str # Backend name ('alsa') backend: str # Backend name ('alsa')
is_default: bool = False # Is system default is_default: bool = False # Is system default
is_monitor: bool = False # Is a monitor/loopback source is_monitor: bool = False # Is a monitor/loopback source
description: str = "" # Extended description
extra: Dict[str, Any] = field(default_factory=dict)
def __str__(self): def __str__(self):
flags = [] flags = []
@@ -74,7 +71,6 @@ class AudioBackend(ABC):
"""Abstract base for audio capture backends.""" """Abstract base for audio capture backends."""
name: str = "base" name: str = "base"
priority: int = 0 # Higher = preferred
@classmethod @classmethod
@abstractmethod @abstractmethod
@@ -99,7 +95,6 @@ class ALSABackend(AudioBackend):
"""ALSA backend using arecord (raw PCM output, no sound server required).""" """ALSA backend using arecord (raw PCM output, no sound server required)."""
name = "alsa" name = "alsa"
priority = 5 # Lowest priority — direct hardware access, use when no sound server runs
@classmethod @classmethod
def is_available(cls) -> bool: def is_available(cls) -> bool:
@@ -251,19 +246,12 @@ class AudioSystem:
def get_backend(self, name: str) -> Optional[AudioBackend]: def get_backend(self, name: str) -> Optional[AudioBackend]:
return self._backends.get(name) 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]: def list_all_devices(self) -> List[AudioDevice]:
"""List devices from all available backends.""" """List devices from all available backends."""
all_devices = [] all_devices = []
seen_names = set() seen_names = set()
# Get devices from backends in priority order for cls in self._backend_classes:
for cls in sorted(self._backend_classes, key=lambda c: -c.priority):
if cls.name in self._backends: if cls.name in self._backends:
for dev in cls.list_devices(): for dev in cls.list_devices():
# Deduplicate by name (same device may appear in multiple backends) # Deduplicate by name (same device may appear in multiple backends)
@@ -733,16 +721,9 @@ class SoundcardRecorder(BaseRecorder):
self.audio_buffer.clear() self.audio_buffer.clear()
def close_current_file(self): def close_current_file(self):
"""Close current recording file.""" """Flush any buffered audio, then close the current recording file."""
self._flush_buffer_to_file() self._flush_buffer_to_file()
if self.current_file: super().close_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
def record(self): def record(self):
"""Main recording loop.""" """Main recording loop."""
@@ -990,22 +971,15 @@ def list_audio_devices():
print(" ISR Audio Device Discovery") print(" ISR Audio Device Discovery")
print("=" * 70) print("=" * 70)
# Check available backends if not ALSABackend.is_available():
available_backends = []
if ALSABackend.is_available():
available_backends.append(('alsa', 'ALSA (arecord)', 5))
if not available_backends:
print("\n No audio backends available!") print("\n No audio backends available!")
print("\n Install one of:") print("\n Install ALSA utilities:")
print(" sudo apt install alsa-utils (ALSA, always available on Linux)") print(" sudo apt install alsa-utils")
print() print()
return return
print("\n Available Backends:") print("\n Available Backends:")
for name, label, priority in sorted(available_backends, key=lambda x: -x[2]): print(" - ALSA (arecord)")
marker = " (preferred)" if priority == max(b[2] for b in available_backends) else ""
print(f" - {label}{marker}")
# Initialize audio system and list devices # Initialize audio system and list devices
audio_system = AudioSystem(logger) audio_system = AudioSystem(logger)
@@ -1016,30 +990,10 @@ def list_audio_devices():
print() print()
return 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("\n Input Devices:")
print(" " + "-" * 68) print(" " + "-" * 68)
for dev in inputs: for dev in devices:
flags = [] flag_str = " [DEFAULT]" if dev.is_default else ""
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"\n {dev.name}{flag_str}")
print(f" ID: {dev.id} | Backend: {dev.backend}") print(f" ID: {dev.id} | Backend: {dev.backend}")
print(f" Channels: {dev.channels} | Sample Rate: {dev.sample_rate} Hz") print(f" Channels: {dev.channels} | Sample Rate: {dev.sample_rate} Hz")
@@ -1048,10 +1002,9 @@ def list_audio_devices():
print(" Configuration Examples:") print(" Configuration Examples:")
print("-" * 70) print("-" * 70)
print(" device = default # Use system default input") print(" device = default # Use system default input")
print(" device = monitor # Use first monitor/loopback source")
print(" device = <name> # Match by partial name") print(" device = <name> # Match by partial name")
print(" device = <id> # Use exact backend ID") print(" device = <id> # Use exact ALSA ID, e.g. hw:0,0")
print(" backend = pipewire # Force specific backend") print(" device = <pcm> # Any ALSA PCM name from asound.conf, e.g. shared_mic")
print("=" * 70 + "\n") print("=" * 70 + "\n")
@@ -1061,17 +1014,11 @@ def main():
list_audio_devices() list_audio_devices()
return return
# Get config file # Get config file (RecorderManager exits with a usage message if it's missing)
config_file = 'config.ini' config_file = 'config.ini'
if len(sys.argv) > 1: if len(sys.argv) > 1:
config_file = 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 # Docker sends SIGTERM before SIGKILL — treat it the same as Ctrl+C
def _sigterm(sig, frame): def _sigterm(sig, frame):
raise KeyboardInterrupt() raise KeyboardInterrupt()