# _opusdll.py - Opus DLL constants, structures, bindings and auto-download loader # Reference: ap_ds/_sdl2.py structure import os import sys import ssl import hashlib import tempfile import shutil import urllib.request import ctypes import ctypes.wintypes as wt from ctypes import * # ============================================================ # Opus DLL Library Loader # ============================================================ # Global library handles opusfile = None # libopusfile-0.dll winmm = None # winmm.dll (system) kernel32 = None # kernel32.dll (system) _opus_dll_error = None # Records DLL load failure reason # DLL file list (from the download API) OPUS_DLL_FILES = [ { "filename": "libopusurl-0.dll", "url": "https://dvsyun.top/ap_ds/download/libopusurl-0.dll", "size": 76772, }, { "filename": "libopus-0.dll", "url": "https://dvsyun.top/ap_ds/download/libopus-0.dll", "size": 500112, }, { "filename": "libogg-0.dll", "url": "https://dvsyun.top/ap_ds/download/libogg-0.dll", "size": 40580, }, { "filename": "libopusfile-0.dll", "url": "https://dvsyun.top/ap_ds/download/libopusfile-0.dll", "size": 55884, }, ] # DLL SHA256 hashes for verification (after download) OPUS_DLL_HASHES = { "libopusfile-0.dll": "fc8ff75c5e0180e73b0528dc78c51ed0fb493741375cdc227f50c2a33cabf727", "libopus-0.dll": "90aa25a0a6525d7da48a7ae8dd3306e45b0c28ce09a73d2a02b56cd95418d5be", "libogg-0.dll": "3038ce8d161324a6349bf7c83b78493857ff6a3501e3adb3d541c6a07bd94a57", "libopusurl-0.dll": "a6cde968a23f2d0067332a13718c52e265653a2c35d65862e8dff4cf2a0346d9", } def _get_package_dir(): """Get the directory containing this module.""" return os.path.dirname(os.path.abspath(__file__)) def _check_opus_libraries_exist(directory): """Check if all Opus DLLs exist in the given directory.""" required = ["libopusfile-0.dll", "libopus-0.dll", "libogg-0.dll"] for dll in required: if not os.path.exists(os.path.join(directory, dll)): return False return True def _load_from_directory(directory): """Load Opus libraries from the specified directory. Supports both Windows (.dll) and Linux (.so) library names. Returns: bool: True if the Opus library loaded successfully """ global opusfile platform = sys.platform # Determine library filename based on platform if platform == "win32": libopusfile_path = os.path.join(directory, "libopusfile-0.dll") elif platform.startswith("linux"): # Try multiple Linux .so naming conventions names_to_try = [ "libopusfile.so", "libopusfile.so.0", "libopusfile-0.so", ] libopusfile_path = None for name in names_to_try: candidate = os.path.join(directory, name) if os.path.exists(candidate): libopusfile_path = candidate break if libopusfile_path is None: return False else: # macOS (not added yet) or other libopusfile_path = os.path.join(directory, "libopusfile-0.dll") if not os.path.exists(libopusfile_path): return False if not os.path.exists(libopusfile_path): return False try: # Add directory to library search path if platform == "win32": if hasattr(os, 'add_dll_directory'): os.add_dll_directory(directory) os.environ['PATH'] = directory + os.pathsep + os.environ.get('PATH', '') elif platform.startswith("linux"): if 'LD_LIBRARY_PATH' not in os.environ: os.environ['LD_LIBRARY_PATH'] = directory else: os.environ['LD_LIBRARY_PATH'] = directory + ':' + os.environ['LD_LIBRARY_PATH'] opusfile = ctypes.CDLL(libopusfile_path) return True except Exception as e: _opus_dll_error = f"Opus library load error: {e}" return False def _load_from_system(): """Try loading libopusfile from system paths.""" global opusfile try: import ctypes.util found = ctypes.util.find_library("opusfile") if found: opusfile = ctypes.CDLL(found) return True except Exception: pass return False def _load_user_config(): """Load user-saved Opus library paths from config file.""" global opusfile try: config_file = os.path.expanduser('~/.config/ap_ds/opus_paths.conf') if os.path.exists(config_file): with open(config_file, 'r') as f: opusfile_path = None for line in f: if line.startswith('OPUSFILE_PATH='): opusfile_path = line.strip().split('=', 1)[1] if opusfile_path and os.path.exists(opusfile_path): opusfile = ctypes.CDLL(opusfile_path) return True except Exception: pass return False def _run_sudo_command(cmd, packages): """Run a sudo command with interactive password input. First tries without password (if already root or passwordless sudo). If that fails, prompts for the sudo password interactively. Args: cmd: Base command list (e.g. ['apt-get', 'install', '-y']) packages: Package names to install Returns: bool: True if command succeeded """ import subprocess import getpass full_cmd = cmd + packages # Try without password first (if already root / passwordless sudo) try: result = subprocess.run( ['sudo', '-n'] + full_cmd, capture_output=True, text=True, timeout=120 ) if result.returncode == 0: return True except Exception: pass # Prompt for sudo password interactively print("šŸ”‘ sudo password required for package installation") try: password = getpass.getpass("Enter sudo password: ") except (EOFError, KeyboardInterrupt): print("\nāŒ Password input cancelled") return False # Use sudo -S to read password from stdin try: result = subprocess.run( ['sudo', '-S'] + full_cmd, input=password + '\n', capture_output=True, text=True, timeout=180 ) if result.returncode == 0: print("āœ… Packages installed successfully") return True else: print(f"āŒ Installation failed: {result.stderr.strip()}") return False except Exception as e: print(f"āŒ Installation error: {e}") return False def _linux_auto_install(): """Try automatic package manager installation on Linux. Uses interactive sudo password input to avoid hanging. """ try: import subprocess import shutil if shutil.which('apt-get'): print("šŸ“¦ Detected apt-based system (Ubuntu/Debian)") if _run_sudo_command(['apt-get', 'install', '-y'], ['libopusfile-dev', 'libopus-dev', 'libogg-dev']): if _load_from_system(): print("āœ… Opus libraries installed and loaded") return True elif shutil.which('dnf'): print("šŸ“¦ Detected dnf-based system (Fedora)") if _run_sudo_command(['dnf', 'install', '-y'], ['opusfile-devel', 'opus-devel', 'libogg-devel']): if _load_from_system(): print("āœ… Opus libraries installed and loaded") return True elif shutil.which('pacman'): print("šŸ“¦ Detected pacman-based system (Arch)") if _run_sudo_command(['pacman', '-S', '--noconfirm'], ['opusfile', 'opus', 'libogg']): if _load_from_system(): print("āœ… Opus libraries installed and loaded") return True except Exception as e: print(f"āš ļø Automatic installation failed: {e}") return False def _linux_interactive_setup(): """Linux interactive setup for Opus libraries.""" global opusfile print("\n" + "=" * 70) print("Linux Opus Library Loading") print("=" * 70) print("Options:") print("1. Use system-installed libraries (re-check)") print("2. Specify path to your compiled .so files") print("3. Show installation instructions") print("=" * 70) while True: choice = input("\nChoose option (1/2/3): ").strip() if choice == "1": if _load_from_system(): print("āœ… Opus libraries loaded from system") return True print("āŒ System libraries not found") continue elif choice == "2": opusfile_path = input("Enter full path to libopusfile.so: ").strip() if os.path.exists(opusfile_path): try: opusfile = ctypes.CDLL(opusfile_path) # Save for future try: config_dir = os.path.expanduser('~/.config/ap_ds') os.makedirs(config_dir, exist_ok=True) with open(os.path.join(config_dir, 'opus_paths.conf'), 'w') as f: f.write(f"OPUSFILE_PATH={opusfile_path}\n") except Exception: pass print("āœ… Libraries loaded from user-specified paths") return True except Exception as e: print(f"āŒ Failed to load: {e}") else: print("āŒ File not found") continue elif choice == "3": print("\nšŸ“š Installation instructions:") print("=" * 50) print("For Ubuntu/Debian:") print(" sudo apt-get install libopusfile-dev libopus-dev libogg-dev") print("\nFor Fedora:") print(" sudo dnf install opusfile-devel opus-devel libogg-devel") print("\nFor Arch:") print(" sudo pacman -S opusfile opus libogg") print("=" * 50) continue else: print("āŒ Invalid choice. Please enter 1, 2, or 3.") def _macos_apology(): """Print a humble apology about missing Opus precompiled packages on macOS.""" print("\n" + "=" * 70) print("āš ļø macOS Opus Support Notice") print("=" * 70) print("We sincerely apologize.") print("On macOS, SDL2 libraries can be downloaded automatically, but we") print("could NOT find any precompiled Opus framework packages for macOS.") print("This is a limitation of the Opus ecosystem, not of ap_ds.") print("") print("We recommend using a package manager to install the Opus libraries.") print("ap_ds will first try to auto-install them for you.") print("If that fails, we will guide you through manual installation.") print("=" * 70 + "\n") def _macos_detect_system(): """Detect if Opus libraries are already present on macOS system paths.""" global opusfile try: import ctypes.util found = ctypes.util.find_library("opusfile") if found: opusfile = ctypes.CDLL(found) return True except Exception: pass # Check common macOS library paths for path in [ "/opt/homebrew/lib/libopusfile.dylib", # Apple Silicon Homebrew "/usr/local/lib/libopusfile.dylib", # Intel Homebrew "/opt/local/lib/libopusfile.dylib", # MacPorts ]: if os.path.exists(path): try: opusfile = ctypes.CDLL(path) return True except Exception: pass return False def _macos_install_macports(): """Try installing Opus libraries via MacPorts.""" import subprocess import shutil if shutil.which('port'): print("šŸ“¦ Detected MacPorts") try: result = subprocess.run( ['sudo', '-n', 'port', 'install', 'opus', 'opusfile', 'libogg'], capture_output=True, text=True, timeout=180 ) if result.returncode == 0: if _macos_detect_system(): print("āœ… Opus libraries installed via MacPorts") return True except Exception: pass # If sudo -n failed (needs password), prompt for password import getpass print("šŸ”‘ sudo password required for MacPorts installation") try: password = getpass.getpass("Enter sudo password: ") result = subprocess.run( ['sudo', '-S', 'port', 'install', 'opus', 'opusfile', 'libogg'], input=password + '\n', capture_output=True, text=True, timeout=300 ) if result.returncode == 0 and _macos_detect_system(): print("āœ… Opus libraries installed via MacPorts") return True except Exception as e: print(f"āŒ MacPorts installation failed: {e}") return False def _macos_install_homebrew(): """Try installing Opus libraries via Homebrew.""" import subprocess import shutil if shutil.which('brew'): print("šŸ“¦ Detected Homebrew") try: result = subprocess.run( ['brew', 'install', 'opus', 'opusfile', 'libogg'], capture_output=True, text=True, timeout=300 ) if result.returncode == 0: if _macos_detect_system(): print("āœ… Opus libraries installed via Homebrew") return True except Exception as e: print(f"āŒ Homebrew installation failed: {e}") return False def _macos_guide_manual_install(): """Guide the user through manual installation of Opus libraries.""" print("\n" + "=" * 70) print("šŸ“š Manual Installation Guide (macOS)") print("=" * 70) print("We could not auto-install the Opus libraries. Please install them") print("using one of the following methods:") print("") print("Method 1: Install Homebrew (if not installed)") print(" /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"") print(" Then: brew install opus opusfile libogg") print("") print("Method 2: Install MacPorts") print(" https://www.macports.org/install.php") print(" Then: sudo port install opus opusfile libogg") print("") print("Method 3: Compile from source") print(" Download from https://opus-codec.org/downloads/") print(" opus-1.6.1.tar.gz, opusfile-0.12.tar.gz, libogg-1.3.6.tar.gz") print(" Compile each with: ./configure && make && sudo make install") print("=" * 70 + "\n") def _macos_auto_install(): """Try to auto-install Opus libraries on macOS. Order: detect system -> MacPorts -> Homebrew -> manual guide. """ _macos_apology() # Step 1: Detect if already present if _macos_detect_system(): print("āœ… Opus libraries already present on system") return True # Step 2: Try MacPorts print("\nšŸ“¦ Attempting MacPorts installation...") if _macos_install_macports(): return True # Step 3: Try Homebrew print("\nšŸ“¦ Attempting Homebrew installation...") if _macos_install_homebrew(): return True # Step 4: Guide manual installation _macos_guide_manual_install() return False def _check_opus_libraries_exist_linux(directory): """Check if Opus .so libraries exist in directory (Linux).""" return os.path.exists(os.path.join(directory, "libopusfile.so")) def verify_file_hash(file_path, expected_hash): """Verify the SHA256 hash of a file. Args: file_path: Path to the file to verify expected_hash: Expected SHA256 hash (hex string) Returns: bool: True if hash matches (or no hash configured) """ if not expected_hash: print(f" āš ļø No hash configured for {os.path.basename(file_path)}, skipping verification") return True try: with open(file_path, 'rb') as f: content = f.read() file_hash = hashlib.sha256(content).hexdigest() print(f" Existing file SHA256: {file_hash}") if file_hash.lower() == expected_hash.lower(): print(f" āœ… Hash verification passed") return True else: print(f" āŒ Hash verification failed! Expected: {expected_hash}, Got: {file_hash}") return False except Exception as e: print(f" āŒ Error verifying hash: {e}") return False def download_opus_libraries(): """Download all Opus DLLs to the package directory with auto-download and hash verification. Returns: bool: True if all DLLs downloaded successfully """ current_dir = _get_package_dir() print(f"Package directory: {current_dir}") def download_file(url, filename, expected_hash=None): """Download a single file with SSL fallback and hash verification.""" temp_file = tempfile.NamedTemporaryFile(delete=False) temp_file.close() try: print(f" Downloading {filename} from {url}...") try: # Try with SSL verification first req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) with urllib.request.urlopen(req, timeout=30) as response: content = response.read() print(f" āœ… Download successful with SSL verification") except (urllib.error.URLError, ssl.SSLError) as e: print(f" SSL verification failed: {e}") print(f" Retrying without SSL verification...") context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) with urllib.request.urlopen(req, timeout=30, context=context) as response: content = response.read() print(f" āœ… Download successful without SSL verification") with open(temp_file.name, 'wb') as f: f.write(content) print(f" Downloaded {len(content)} bytes") # Verify hash before moving if expected_hash: file_hash = hashlib.sha256(content).hexdigest() print(f" Downloaded file SHA256: {file_hash}") if file_hash.lower() != expected_hash.lower(): print(f" āŒ Hash verification failed! Expected: {expected_hash}, Got: {file_hash}") try: os.unlink(temp_file.name) except Exception: pass return False print(f" āœ… Hash verification passed") # Move to package directory dest_path = os.path.join(current_dir, filename) shutil.move(temp_file.name, dest_path) print(f" āœ… Saved to {dest_path}") return True except Exception as e: print(f" āŒ Download failed for {filename}: {e}") try: os.unlink(temp_file.name) except Exception: pass return False # Download each DLL with hash verification success_count = 0 for dll_info in OPUS_DLL_FILES: filename = dll_info["filename"] expected_hash = OPUS_DLL_HASHES.get(filename) file_path = os.path.join(current_dir, filename) if os.path.exists(file_path): print(f"\nšŸ“ {filename} already exists, verifying hash...") if verify_file_hash(file_path, expected_hash): print(f"āœ… {filename} is valid, skipping download") success_count += 1 continue else: print(f"āš ļø {filename} hash mismatch, re-downloading...") try: os.remove(file_path) except Exception: pass print(f"\nšŸ“„ Downloading {filename}...") if download_file(dll_info["url"], filename, expected_hash): success_count += 1 print(f"\nāœ… Downloaded {success_count}/{len(OPUS_DLL_FILES)} DLLs") return success_count == len(OPUS_DLL_FILES) def import_opus(): """Main function: Import Opus libraries with cross-platform support. Returns: bool: True if Opus libraries loaded successfully """ global opusfile # Already loaded? if opusfile is not None: return True current_dir = _get_package_dir() platform = sys.platform # Windows: use DLL + auto-download if platform == "win32": # Layer 1: Load from current directory if _load_from_directory(current_dir): return True # Layer 2: Load from system if _load_from_system(): return True # Layer 3: Auto-download DLLs print("Opus DLLs not found, downloading...") if download_opus_libraries(): if _load_from_directory(current_dir): print("āœ… Opus DLLs loaded after download") return True # Linux: use system .so libraries (reference _sdl2.py) # NOTE: The package directory contains Windows .dll files only. # Linux Opus libraries (.so) are installed via system package manager. elif platform.startswith("linux"): # Layer 1: User config (custom .so paths) if _load_user_config(): print("āœ… Opus loaded from user config") return True # Layer 2: System libraries if _load_from_system(): print("āœ… Opus loaded from system") return True # Layer 3: Auto install via package manager if _linux_auto_install(): return True # Layer 4: Interactive setup if _linux_interactive_setup(): return True # macOS: no precompiled Opus framework, use package manager # Order: detect system -> MacPorts -> Homebrew -> manual guide elif platform == "darwin": if _macos_auto_install(): return True global _opus_dll_error if not _opus_dll_error: _opus_dll_error = "Failed to load Opus libraries" return False def check_opus_dll(): """Check whether the Opus DLL can be loaded normally. Returns: (bool, str): (whether normal, error message) """ if not import_opus(): return (False, _opus_dll_error or "Opus DLL load failed") try: if not hasattr(opusfile, 'op_open_file'): return (False, "libopusfile-0.dll not loaded correctly (missing op_open_file)") return (True, "Opus DLL OK") except Exception as e: return (False, f"Opus DLL check failed: {e}") # ============================================================ # Opus structures # ============================================================ class OpusHead(Structure): _fields_ = [ ("version", c_int), ("channel_count", c_int), ("pre_skip", c_uint), ("input_sample_rate", c_uint), ("output_gain", c_int), ("mapping_family", c_int), ("stream_count", c_int), ("coupled_count", c_int), ("mapping", c_ubyte * 255), ] class OpusTags(Structure): _fields_ = [ ("user_comments", POINTER(c_char_p)), ("comment_lengths", POINTER(c_int)), ("comments", c_int), ("vendor", c_char_p), ] # ============================================================ # Windows Wave API structures and constants # ============================================================ WAVE_FORMAT_PCM = 1 WAVE_MAPPER = 0xFFFFFFFF CALLBACK_EVENT = 0x00050000 WHDR_DONE = 0x1 MMSYSERR_NOERROR = 0 WAIT_OBJECT_0 = 0 class WAVEFORMATEX(Structure): _fields_ = [ ("wFormatTag", wt.WORD), ("nChannels", wt.WORD), ("nSamplesPerSec", wt.DWORD), ("nAvgBytesPerSec", wt.DWORD), ("nBlockAlign", wt.WORD), ("wBitsPerSample", wt.WORD), ("cbSize", wt.WORD), ] class WAVEHDR(Structure): pass WAVEHDR._fields_ = [ ("lpData", wt.LPSTR), ("dwBufferLength", wt.DWORD), ("dwBytesRecorded", wt.DWORD), ("dwUser", c_void_p), # DWORD_PTR (8 bytes) ("dwFlags", wt.DWORD), ("dwLoops", wt.DWORD), ("lpNext", POINTER(WAVEHDR)), ("reserved", c_void_p), # DWORD_PTR (8 bytes) ] # ============================================================ # Opus file function bindings (wrapper functions) # ============================================================ def op_open_file(path, error): """Open an Opus file. Returns handle or None.""" return opusfile.op_open_file(path, error) def op_free(of): """Free an Opus file handle.""" opusfile.op_free(of) def op_head(of, li): """Get OpusHead for a link.""" return opusfile.op_head(of, li) def op_tags(of, li): """Get OpusTags for a link.""" return opusfile.op_tags(of, li) def op_channel_count(of, li): """Get channel count.""" return opusfile.op_channel_count(of, li) def op_pcm_total(of, li): """Get total PCM samples.""" return opusfile.op_pcm_total(of, li) def op_bitrate(of, li): """Get average bitrate.""" return opusfile.op_bitrate(of, li) def op_seekable(of): """Check if stream is seekable.""" return opusfile.op_seekable(of) def op_link_count(of): """Get number of links.""" return opusfile.op_link_count(of) def op_read_stereo(of, pcm, buf_size): """Read decoded stereo PCM.""" return opusfile.op_read_stereo(of, pcm, buf_size) def op_pcm_seek(of, pos): """Seek to PCM sample position.""" return opusfile.op_pcm_seek(of, pos) def op_pcm_tell(of): """Get current PCM position.""" return opusfile.op_pcm_tell(of) # ============================================================ # Windows Wave API function bindings # ============================================================ def waveOutOpen(phwo, device_id, fmt, callback, instance, flags): return winmm.waveOutOpen(phwo, device_id, fmt, callback, instance, flags) def waveOutPrepareHeader(hwo, pwh, cbwh): return winmm.waveOutPrepareHeader(hwo, pwh, cbwh) def waveOutWrite(hwo, pwh, cbwh): return winmm.waveOutWrite(hwo, pwh, cbwh) def waveOutUnprepareHeader(hwo, pwh, cbwh): return winmm.waveOutUnprepareHeader(hwo, pwh, cbwh) def waveOutClose(hwo): return winmm.waveOutClose(hwo) def waveOutSetVolume(hwo, volume): return winmm.waveOutSetVolume(hwo, volume) def waveOutGetVolume(hwo, volume): return winmm.waveOutGetVolume(hwo, volume) def waveOutPause(hwo): return winmm.waveOutPause(hwo) def waveOutRestart(hwo): return winmm.waveOutRestart(hwo) def waveOutReset(hwo): return winmm.waveOutReset(hwo) def waveOutGetErrorTextW(code, buf, size): return winmm.waveOutGetErrorTextW(code, buf, size) # ============================================================ # Kernel32 function bindings # ============================================================ def CreateEventW(lp_attrs, b_manual, b_initial, name): return kernel32.CreateEventW(lp_attrs, b_manual, b_initial, name) def WaitForSingleObject(handle, ms): return kernel32.WaitForSingleObject(handle, ms) def ResetEvent(handle): return kernel32.ResetEvent(handle) def CloseHandle(handle): return kernel32.CloseHandle(handle) # ============================================================ # Function prototypes (set after libraries are loaded) # ============================================================ def _setup_prototypes(): """Set up function prototypes for opusfile, winmm, and kernel32.""" # --- opusfile prototypes --- opusfile.op_open_file.restype = c_void_p opusfile.op_open_file.argtypes = [c_char_p, POINTER(c_int)] opusfile.op_free.restype = None opusfile.op_free.argtypes = [c_void_p] opusfile.op_head.restype = POINTER(OpusHead) opusfile.op_head.argtypes = [c_void_p, c_int] opusfile.op_tags.restype = POINTER(OpusTags) opusfile.op_tags.argtypes = [c_void_p, c_int] opusfile.op_channel_count.restype = c_int opusfile.op_channel_count.argtypes = [c_void_p, c_int] opusfile.op_pcm_total.restype = c_longlong opusfile.op_pcm_total.argtypes = [c_void_p, c_int] opusfile.op_bitrate.restype = c_int opusfile.op_bitrate.argtypes = [c_void_p, c_int] opusfile.op_seekable.restype = c_int opusfile.op_seekable.argtypes = [c_void_p] opusfile.op_link_count.restype = c_int opusfile.op_link_count.argtypes = [c_void_p] opusfile.op_read_stereo.restype = c_int opusfile.op_read_stereo.argtypes = [c_void_p, POINTER(c_int16), c_int] opusfile.op_pcm_seek.restype = c_int opusfile.op_pcm_seek.argtypes = [c_void_p, c_longlong] opusfile.op_pcm_tell.restype = c_longlong opusfile.op_pcm_tell.argtypes = [c_void_p] # --- winmm prototypes (Windows only) --- if winmm is None: return winmm.waveOutOpen.restype = wt.DWORD winmm.waveOutOpen.argtypes = [ POINTER(wt.HANDLE), wt.UINT, POINTER(WAVEFORMATEX), wt.DWORD, wt.DWORD, wt.DWORD] winmm.waveOutPrepareHeader.restype = wt.DWORD winmm.waveOutPrepareHeader.argtypes = [wt.HANDLE, POINTER(WAVEHDR), wt.UINT] winmm.waveOutWrite.restype = wt.DWORD winmm.waveOutWrite.argtypes = [wt.HANDLE, POINTER(WAVEHDR), wt.UINT] winmm.waveOutUnprepareHeader.restype = wt.DWORD winmm.waveOutUnprepareHeader.argtypes = [wt.HANDLE, POINTER(WAVEHDR), wt.UINT] winmm.waveOutClose.restype = wt.DWORD winmm.waveOutClose.argtypes = [wt.HANDLE] winmm.waveOutSetVolume.restype = wt.DWORD winmm.waveOutSetVolume.argtypes = [wt.HANDLE, wt.DWORD] winmm.waveOutGetVolume.restype = wt.DWORD winmm.waveOutGetVolume.argtypes = [wt.HANDLE, POINTER(wt.DWORD)] winmm.waveOutPause.restype = wt.DWORD winmm.waveOutPause.argtypes = [wt.HANDLE] winmm.waveOutRestart.restype = wt.DWORD winmm.waveOutRestart.argtypes = [wt.HANDLE] winmm.waveOutReset.restype = wt.DWORD winmm.waveOutReset.argtypes = [wt.HANDLE] winmm.waveOutGetErrorTextW.restype = wt.DWORD winmm.waveOutGetErrorTextW.argtypes = [wt.DWORD, wt.LPWSTR, wt.UINT] # --- kernel32 prototypes --- kernel32.CreateEventW.restype = wt.HANDLE kernel32.CreateEventW.argtypes = [c_void_p, wt.BOOL, wt.BOOL, wt.LPCWSTR] kernel32.WaitForSingleObject.restype = wt.DWORD kernel32.WaitForSingleObject.argtypes = [wt.HANDLE, wt.DWORD] kernel32.ResetEvent.restype = wt.BOOL kernel32.ResetEvent.argtypes = [wt.HANDLE] kernel32.CloseHandle.restype = wt.BOOL kernel32.CloseHandle.argtypes = [wt.HANDLE] # ============================================================ # Initialize: Load system DLLs and Opus DLLs # ============================================================ # Windows-specific system DLLs (winmm/kernel32) are only available on Windows. # On Linux/macOS, these are set to None; Opus decoding still works via opusfile, # but waveOut playback is Windows-only. if sys.platform == "win32": winmm = ctypes.WinDLL("winmm") kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) else: winmm = None kernel32 = None # Load Opus libraries (with auto-download on Windows, system .so on Linux) if import_opus(): _setup_prototypes()