Initial commit: ap_ds 音频播放库 (Audio Player By DVS AFS)

This commit is contained in:
dvs
2026-08-27 19:11:29 +08:00
commit bb521483b9
16 changed files with 11045 additions and 0 deletions
+946
View File
@@ -0,0 +1,946 @@
# _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()