diff --git a/LivescriptV2.01.py b/LivescriptV2.01.py index 72f86e1..9f8766a 100644 --- a/LivescriptV2.01.py +++ b/LivescriptV2.01.py @@ -976,12 +976,53 @@ def quit_app(self): # --- Background Threads --- +def is_loopback_microphone(microphone): + """Return True for PulseAudio/PipeWire monitor or other loopback sources.""" + mic_id = str(microphone.id).lower() + mic_name = microphone.name.lower() + return "monitor" in mic_id or "loopback" in mic_id or "monitor" in mic_name or "loopback" in mic_name + + +def get_loopback_microphone(speaker): + """Find the recording source that monitors the given speaker output.""" + speaker_id = str(speaker.id) + candidates = [ + f"{speaker_id}.monitor", # PulseAudio/PipeWire monitor source id on Linux. + speaker_id, + speaker.name, + f"Monitor of {speaker.name}", + ] + + for candidate in candidates: + try: + return sc.get_microphone(id=candidate, include_loopback=True) + except Exception: + pass + + loopbacks = [mic for mic in sc.all_microphones(include_loopback=True) if is_loopback_microphone(mic)] + matching_loopbacks = [mic for mic in loopbacks if speaker_id in str(mic.id) or speaker.name in mic.name] + + if matching_loopbacks: + return matching_loopbacks[0] + if len(loopbacks) == 1: + return loopbacks[0] + + available = "\n".join(f" {mic.name} | id={mic.id}" for mic in sc.all_microphones(include_loopback=True)) + raise RuntimeError( + "Could not find a loopback/monitor microphone for the default speaker.\n" + "Available microphones including loopback devices:\n" + f"{available}" + ) + + def audio_capture_thread(block_size, sample_rate): """Captures audio from the default speaker and puts it into a queue.""" try: default_speaker = sc.default_speaker() print(f"Using speaker: {default_speaker.name} for audio capture.") - with sc.get_microphone(id=str(default_speaker.id), include_loopback=True).recorder( + loopback_microphone = get_loopback_microphone(default_speaker) + print(f"Using loopback microphone: {loopback_microphone.name} for audio capture.") + with loopback_microphone.recorder( samplerate=sample_rate, channels=1, blocksize=int(block_size) ) as mic: print("Audio recorder started.") @@ -1107,4 +1148,4 @@ def restart_application(): if __name__ == "__main__": - main() \ No newline at end of file + main()