|
|
|
import ctypes |
|
import os |
|
import sys |
|
import platform |
|
import shutil |
|
from pathlib import Path |
|
|
|
|
|
from src.utils.logging_config import get_logger |
|
logger = get_logger(__name__) |
|
|
|
|
|
|
|
WINDOWS = 'windows' |
|
MACOS = 'darwin' |
|
LINUX = 'linux' |
|
|
|
|
|
LIB_INFO = { |
|
WINDOWS: {'name': 'opus.dll', 'system_name': 'opus'}, |
|
MACOS: {'name': 'libopus.dylib', 'system_name': 'libopus.dylib'}, |
|
LINUX: { |
|
'name': 'libopus.so', |
|
'system_name': ['libopus.so.0', 'libopus.so'] |
|
}, |
|
} |
|
|
|
|
|
DIR_STRUCTURE = { |
|
WINDOWS: {'arch': 'x86_64', 'path': 'libs/libopus/win/x86_64'}, |
|
MACOS: { |
|
'arch': {'arm': 'arm64', 'intel': 'x64'}, |
|
'path': 'libs/libopus/mac/{arch}' |
|
}, |
|
LINUX: { |
|
'arch': {'arm': 'arm64', 'intel': 'x64'}, |
|
'path': 'libs/libopus/linux/{arch}' |
|
}, |
|
} |
|
|
|
|
|
def get_system_info(): |
|
"""获取当前系统信息""" |
|
system = platform.system().lower() |
|
architecture = platform.machine().lower() |
|
|
|
|
|
if system == 'windows' or system.startswith('win'): |
|
system = WINDOWS |
|
elif system == 'darwin': |
|
system = MACOS |
|
elif system.startswith('linux'): |
|
system = LINUX |
|
|
|
|
|
is_arm = 'arm' in architecture or 'aarch64' in architecture |
|
|
|
if system == MACOS: |
|
arch_name = DIR_STRUCTURE[MACOS]['arch']['arm' if is_arm else 'intel'] |
|
elif system == WINDOWS: |
|
arch_name = DIR_STRUCTURE[WINDOWS]['arch'] |
|
else: |
|
arch_name = DIR_STRUCTURE[LINUX]['arch']['arm' if is_arm else 'intel'] |
|
|
|
return system, arch_name |
|
|
|
|
|
def get_search_paths(system, arch_name): |
|
"""获取库文件搜索路径列表""" |
|
|
|
possible_base_dirs = [ |
|
Path(__file__).parent.parent.parent, |
|
Path.cwd(), |
|
] |
|
|
|
|
|
if getattr(sys, 'frozen', False): |
|
|
|
exe_dir = Path(sys.executable).parent |
|
possible_base_dirs.append(exe_dir) |
|
|
|
|
|
if hasattr(sys, '_MEIPASS'): |
|
meipass_dir = Path(sys._MEIPASS) |
|
possible_base_dirs.append(meipass_dir) |
|
|
|
if meipass_dir.name == '_internal': |
|
|
|
possible_base_dirs.append(meipass_dir.parent) |
|
|
|
|
|
parent_dir = exe_dir.parent |
|
possible_base_dirs.append(parent_dir) |
|
|
|
|
|
internal_dir = exe_dir / '_internal' |
|
if internal_dir.exists(): |
|
possible_base_dirs.append(internal_dir) |
|
|
|
logger.debug(f"可执行文件目录: {exe_dir}") |
|
logger.debug(f"可执行文件父目录: {parent_dir}") |
|
if hasattr(sys, '_MEIPASS'): |
|
logger.debug(f"PyInstaller资源目录: {meipass_dir}") |
|
|
|
|
|
lib_name = LIB_INFO[system]['name'] |
|
search_paths = [] |
|
|
|
for base_dir in filter(None, possible_base_dirs): |
|
|
|
if system == MACOS: |
|
lib_path = DIR_STRUCTURE[MACOS]['path'].format(arch=arch_name) |
|
search_paths.append((base_dir / lib_path, lib_name)) |
|
elif system == WINDOWS: |
|
lib_path = DIR_STRUCTURE[WINDOWS]['path'] |
|
search_paths.append((base_dir / lib_path, lib_name)) |
|
elif system == LINUX: |
|
lib_path = DIR_STRUCTURE[LINUX]['path'] |
|
search_paths.append((base_dir / lib_path, lib_name)) |
|
|
|
|
|
search_paths.append((base_dir, lib_name)) |
|
|
|
|
|
is_exe_dir = ( |
|
getattr(sys, 'frozen', False) and |
|
base_dir == Path(sys.executable).parent |
|
) |
|
if is_exe_dir: |
|
|
|
libs_dir = base_dir / 'libs' |
|
if libs_dir.exists(): |
|
if system == MACOS: |
|
macos_lib_path = f"libopus/mac/{arch_name}" |
|
search_paths.append((libs_dir / macos_lib_path, lib_name)) |
|
elif system == WINDOWS: |
|
win_lib_path = "libopus/win/x86_64" |
|
search_paths.append((libs_dir / win_lib_path, lib_name)) |
|
elif system == LINUX: |
|
linux_lib_path = f"libopus/linux/{arch_name}" |
|
search_paths.append((libs_dir / linux_lib_path, lib_name)) |
|
|
|
|
|
internal_libs_dir = base_dir / '_internal' / 'libs' |
|
if internal_libs_dir.exists(): |
|
if system == MACOS: |
|
macos_path = f"libopus/mac/{arch_name}" |
|
search_paths.append( |
|
(internal_libs_dir / macos_path, lib_name) |
|
) |
|
elif system == WINDOWS: |
|
win_path = "libopus/win/x86_64" |
|
search_paths.append( |
|
(internal_libs_dir / win_path, lib_name) |
|
) |
|
elif system == LINUX: |
|
linux_path = f"libopus/linux/{arch_name}" |
|
search_paths.append( |
|
(internal_libs_dir / linux_path, lib_name) |
|
) |
|
|
|
|
|
for dir_path, filename in search_paths: |
|
logger.debug(f"搜索路径: {dir_path / filename}") |
|
|
|
return search_paths |
|
|
|
|
|
def find_system_opus(): |
|
"""从系统路径查找opus库""" |
|
system, _ = get_system_info() |
|
lib_path = None |
|
|
|
try: |
|
|
|
lib_names = LIB_INFO[system]['system_name'] |
|
if not isinstance(lib_names, list): |
|
lib_names = [lib_names] |
|
|
|
|
|
for lib_name in lib_names: |
|
try: |
|
|
|
import ctypes.util |
|
system_lib_path = ctypes.util.find_library(lib_name) |
|
|
|
if system_lib_path: |
|
lib_path = system_lib_path |
|
logger.info(f"在系统路径中找到opus库: {lib_path}") |
|
break |
|
else: |
|
|
|
ctypes.cdll.LoadLibrary(lib_name) |
|
lib_path = lib_name |
|
logger.info(f"直接加载系统opus库: {lib_name}") |
|
break |
|
except Exception as e: |
|
logger.debug(f"加载系统库 {lib_name} 失败: {e}") |
|
continue |
|
|
|
except Exception as e: |
|
logger.error(f"查找系统opus库失败: {e}") |
|
|
|
return lib_path |
|
|
|
|
|
def copy_opus_to_project(system_lib_path): |
|
"""将系统库复制到项目目录""" |
|
system, arch_name = get_system_info() |
|
|
|
if not system_lib_path: |
|
logger.error("无法复制opus库:系统库路径为空") |
|
return None |
|
|
|
try: |
|
|
|
if getattr(sys, 'frozen', False): |
|
|
|
project_root = Path(sys.executable).parent |
|
else: |
|
|
|
project_root = Path(__file__).parent.parent.parent |
|
|
|
|
|
if system == MACOS: |
|
target_path = DIR_STRUCTURE[MACOS]['path'].format(arch=arch_name) |
|
elif system == WINDOWS: |
|
target_path = DIR_STRUCTURE[WINDOWS]['path'] |
|
else: |
|
target_path = DIR_STRUCTURE[LINUX]['path'] |
|
|
|
target_dir = project_root / target_path |
|
|
|
|
|
target_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
lib_name = LIB_INFO[system]['name'] |
|
target_file = target_dir / lib_name |
|
|
|
|
|
shutil.copy2(system_lib_path, target_file) |
|
logger.info(f"已将opus库从 {system_lib_path} 复制到 {target_file}") |
|
|
|
return str(target_file) |
|
|
|
except Exception as e: |
|
logger.error(f"复制opus库到项目目录失败: {e}") |
|
return None |
|
|
|
|
|
def setup_opus(): |
|
"""设置opus动态库""" |
|
|
|
if hasattr(sys, '_opus_loaded'): |
|
logger.info("opus库已由运行时钩子加载") |
|
return True |
|
|
|
|
|
system, arch_name = get_system_info() |
|
logger.info(f"当前系统: {system}, 架构: {arch_name}") |
|
|
|
|
|
search_paths = get_search_paths(system, arch_name) |
|
|
|
|
|
lib_path = None |
|
lib_dir = None |
|
|
|
for dir_path, file_name in search_paths: |
|
full_path = dir_path / file_name |
|
if full_path.exists(): |
|
lib_path = str(full_path) |
|
lib_dir = str(dir_path) |
|
logger.info(f"找到opus库文件: {lib_path}") |
|
break |
|
|
|
|
|
if lib_path is None: |
|
logger.warning("本地未找到opus库文件,尝试从系统路径加载") |
|
system_lib_path = find_system_opus() |
|
|
|
if system_lib_path: |
|
|
|
try: |
|
_ = ctypes.cdll.LoadLibrary(system_lib_path) |
|
logger.info(f"已从系统路径加载opus库: {system_lib_path}") |
|
sys._opus_loaded = True |
|
return True |
|
except Exception as e: |
|
logger.warning(f"加载系统opus库失败: {e},尝试复制到项目目录") |
|
|
|
|
|
lib_path = copy_opus_to_project(system_lib_path) |
|
if lib_path: |
|
lib_dir = str(Path(lib_path).parent) |
|
else: |
|
logger.error("无法找到或复制opus库文件") |
|
return False |
|
else: |
|
logger.error("在系统中也未找到opus库文件") |
|
return False |
|
|
|
|
|
if system == WINDOWS and lib_dir: |
|
|
|
if hasattr(os, 'add_dll_directory'): |
|
try: |
|
os.add_dll_directory(lib_dir) |
|
logger.debug(f"已添加DLL搜索路径: {lib_dir}") |
|
except Exception as e: |
|
logger.warning(f"添加DLL搜索路径失败: {e}") |
|
|
|
|
|
os.environ['PATH'] = lib_dir + os.pathsep + os.environ.get('PATH', '') |
|
|
|
|
|
_patch_find_library('opus', lib_path) |
|
|
|
|
|
try: |
|
|
|
_ = ctypes.CDLL(lib_path) |
|
logger.info(f"成功加载opus库: {lib_path}") |
|
sys._opus_loaded = True |
|
return True |
|
except Exception as e: |
|
logger.error(f"加载opus库失败: {e}") |
|
return False |
|
|
|
|
|
def _patch_find_library(lib_name, lib_path): |
|
"""修补ctypes.util.find_library函数""" |
|
import ctypes.util |
|
original_find_library = ctypes.util.find_library |
|
|
|
def patched_find_library(name): |
|
if name == lib_name: |
|
return lib_path |
|
return original_find_library(name) |
|
|
|
ctypes.util.find_library = patched_find_library |