import os import queue import threading import tempfile import subprocess import sounddevice as sd import soundfile as sf import keyboard # PARAMETERS – adjust as needed CHANNELS = 2 SAMPLE_RATE = 44100 BLOCKSIZE = 1024 # frames per chunk # Look for "Stereo Mix" which is a common device for capturing system audio devices = sd.query_devices() DEVICE_ID = 14 # Will be set dynamically WASAPI_METHOD = False # First try with Stereo Mix, then try WASAPI if available print("Available audio devices:") for i, device in enumerate(devices): print(f" {i} {device['name']}, {device['hostapi']} ({device['max_input_channels']} in, {device['max_output_channels']} out)") # First priority: Stereo Mix if "Stereo Mix" in device['name'] and device['max_input_channels'] > 0: DEVICE_ID = i print(f"Selected device {i}: {device['name']} for system audio recording") break # If no Stereo Mix, try to find WASAPI devices if DEVICE_ID is None: hostapis = sd.query_hostapis() wasapi_index = None for i, api in enumerate(hostapis): if 'WASAPI' in api['name']: wasapi_index = i print(f"Found WASAPI at index {i}") break if wasapi_index is not None: # Try to find a WASAPI output device for i, device in enumerate(devices): if device['hostapi'] == wasapi_index and device['max_output_channels'] > 0 and 'Speakers' in device['name']: DEVICE_ID = i print(f"Selected device {i}: {device['name']} (will try WASAPI method)") WASAPI_METHOD = True break # Fallback to any WASAPI output device if DEVICE_ID is None: for i, device in enumerate(devices): if device['hostapi'] == wasapi_index and device['max_output_channels'] > 0: DEVICE_ID = i print(f"Selected device {i}: {device['name']} (will try WASAPI method)") WASAPI_METHOD = True break # If still no device found, prompt user if DEVICE_ID is None: print("No suitable recording device found. Please select a device manually:") for i, device in enumerate(devices): print(f" {i} {device['name']}, {device['hostapi']} ({device['max_input_channels']} in, {device['max_output_channels']} out)") device_id_input = input("Enter device ID to use: ") try: DEVICE_ID = int(device_id_input) except ValueError: print("Invalid device ID") exit(1) def record_loop(cut_event: threading.Event): """ Records audio into successive WAV files. When cut_event is set, closes current file, converts it, and starts a new one. """ q = queue.Queue() recording_index = 1 current_wav = None wav_file = None def callback(indata, frames, time, status): if status: print(f"⚠️ {status}") q.put(indata.copy()) # Set up recording mode device_info = sd.query_devices(DEVICE_ID) print(f"Using device: {device_info['name']}") # Open the input stream - different approach based on whether it's a capture device or WASAPI try: # Use either regular input stream or customized parameters if WASAPI_METHOD: print("Using WASAPI device - if this doesn't work, try enabling 'Stereo Mix' in Windows sound settings") # Use standard input stream with potential extra settings for newer versions stream_kwargs = { 'device': DEVICE_ID, 'channels': CHANNELS, 'samplerate': SAMPLE_RATE, 'blocksize': BLOCKSIZE, 'callback': callback, 'dtype': 'float32' } # Try adding WASAPI loopback option if available in this version try: stream = sd.InputStream(**stream_kwargs) except Exception as e: print(f"Standard InputStream failed: {e}") print("Trying with loopback option...") try: # Try with extra settings dictionary instead stream_kwargs['extra_settings'] = {'wasapi_loopback': True} stream = sd.InputStream(**stream_kwargs) except Exception as e2: print(f"WASAPI loopback failed: {e2}") raise Exception("Could not initialize audio stream with WASAPI device") from e2 else: # Regular input device like Stereo Mix stream = sd.InputStream( device=DEVICE_ID, channels=CHANNELS, samplerate=SAMPLE_RATE, blocksize=BLOCKSIZE, callback=callback, dtype='float32' ) # Start the stream stream.start() print("▶️ Recording... Press 'c' to cut.") while True: # Start a new WAV file current_wav = tempfile.NamedTemporaryFile( prefix=f"segment_{recording_index:02d}_", suffix=".wav", delete=False ) wav_file = sf.SoundFile( current_wav.name, mode='w', samplerate=SAMPLE_RATE, channels=CHANNELS, subtype='FLOAT' ) # Write until cut_event while not cut_event.is_set(): try: data = q.get(timeout=0.1) wav_file.write(data) except queue.Empty: continue # Cut triggered wav_file.close() print(f"✂️ Segment {recording_index} captured: {current_wav.name}") # Convert to MP3 mp3_path = current_wav.name.replace('.wav', '.mp3') cmd = [ 'ffmpeg', '-y', '-i', current_wav.name, '-codec:a', 'libmp3lame', '-qscale:a', '2', mp3_path ] subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) print(f"🔊 Converted to MP3: {mp3_path}") # Prompt for rename/move new_path = input("Enter new filename (with .mp3) or full path (leave blank to keep): ").strip() if new_path: try: os.replace(mp3_path, new_path) print(f"✅ Moved to {new_path}") except Exception as e: print(f"❌ Could not move file: {e}") else: print(f"➔ Keeping as {mp3_path}") # Clean up WAV os.remove(current_wav.name) recording_index += 1 # Reset for next segment cut_event.clear() print("▶️ Recording next segment... Press 'c' to cut.") except Exception as e: print(f"❌ Error occurred setting up audio stream: {e}") print("\nThere are several ways to fix this:") print("1. Try enabling 'Stereo Mix' in Windows Sound settings") print(" - Right-click the speaker icon in system tray") print(" - Select 'Sound settings' -> 'Sound Control Panel'") print(" - Go to 'Recording' tab") print(" - Right-click empty space and check 'Show Disabled Devices'") print(" - Enable 'Stereo Mix' if available") print("2. Try a different device ID") print("\nAvailable devices:") for i, device in enumerate(sd.query_devices()): hostapi_name = sd.query_hostapis(device['hostapi'])['name'] print(f" {i} {device['name']}, {hostapi_name} ({device['max_input_channels']} in, {device['max_output_channels']} out)") print("\nRun the script again with a specific device ID:") print(" .venv/Scripts/python record.py") exit(1) def listen_for_cut(cut_event: threading.Event): """Waits for the user to press 'c' and sets the event.""" while True: keyboard.wait('c') cut_event.set() if __name__ == '__main__': cut_flag = threading.Event() # Thread that listens for 'c' listener = threading.Thread(target=listen_for_cut, args=(cut_flag,), daemon=True) listener.start() # Main recording loop try: record_loop(cut_flag) except KeyboardInterrupt: print("\n🛑 Recording stopped by user.") except Exception as e: print(f"❌ Error occurred while recording: {e}")