File size: 10,738 Bytes
27e74f3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
import logging
import platform
import subprocess
import re
import shutil
class VolumeController:
"""跨平台音量控制器"""
def __init__(self):
self.logger = logging.getLogger("VolumeController")
self.system = platform.system()
self.is_arm = platform.machine().startswith(('arm', 'aarch'))
# 初始化特定平台的控制器
if self.system == "Windows":
self._init_windows()
elif self.system == "Darwin": # macOS
self._init_macos()
elif self.system == "Linux":
self._init_linux()
else:
self.logger.warning(f"不支持的操作系统: {self.system}")
raise NotImplementedError(f"不支持的操作系统: {self.system}")
def _init_windows(self):
"""初始化Windows音量控制"""
try:
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
self.devices = AudioUtilities.GetSpeakers()
interface = self.devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
self.volume_control = cast(interface, POINTER(IAudioEndpointVolume))
self.logger.debug("Windows音量控制初始化成功")
except Exception as e:
self.logger.error(f"Windows音量控制初始化失败: {e}")
raise
def _init_macos(self):
"""初始化macOS音量控制"""
try:
import applescript
# 测试是否可以访问音量控制
result = applescript.run('get volume settings')
if not result or result.code != 0:
raise Exception("无法访问macOS音量控制")
self.logger.debug("macOS音量控制初始化成功")
except Exception as e:
self.logger.error(f"macOS音量控制初始化失败: {e}")
raise
def _init_linux(self):
"""初始化Linux音量控制"""
# 检测可用的音量控制工具
self.linux_tool = None
def cmd_exists(cmd):
return shutil.which(cmd) is not None
# 按优先级检查工具
if cmd_exists("pactl"):
self.linux_tool = "pactl"
elif cmd_exists("wpctl"):
self.linux_tool = "wpctl"
elif cmd_exists("amixer"):
self.linux_tool = "amixer"
elif cmd_exists("alsamixer") and cmd_exists("expect"):
self.linux_tool = "alsamixer"
if not self.linux_tool:
self.logger.error("未找到可用的Linux音量控制工具")
raise Exception("未找到可用的Linux音量控制工具")
self.logger.debug(f"Linux音量控制初始化成功,使用: {self.linux_tool}")
def get_volume(self):
"""获取当前音量 (0-100)"""
if self.system == "Windows":
return self._get_windows_volume()
elif self.system == "Darwin":
return self._get_macos_volume()
elif self.system == "Linux":
return self._get_linux_volume()
return 70 # 默认音量
def set_volume(self, volume):
"""设置音量 (0-100)"""
# 确保音量在有效范围内
volume = max(0, min(100, volume))
if self.system == "Windows":
self._set_windows_volume(volume)
elif self.system == "Darwin":
self._set_macos_volume(volume)
elif self.system == "Linux":
self._set_linux_volume(volume)
def _get_windows_volume(self):
"""获取Windows音量"""
try:
# 获取音量百分比
volume_scalar = self.volume_control.GetMasterVolumeLevelScalar()
return int(volume_scalar * 100)
except Exception as e:
self.logger.warning(f"获取Windows音量失败: {e}")
return 70
def _set_windows_volume(self, volume):
"""设置Windows音量"""
try:
# 直接设置音量百分比
self.volume_control.SetMasterVolumeLevelScalar(volume / 100.0, None)
except Exception as e:
self.logger.warning(f"设置Windows音量失败: {e}")
def _get_macos_volume(self):
"""获取macOS音量"""
try:
import applescript
result = applescript.run('output volume of (get volume settings)')
if result and result.out:
return int(result.out.strip())
return 70
except Exception as e:
self.logger.warning(f"获取macOS音量失败: {e}")
return 70
def _set_macos_volume(self, volume):
"""设置macOS音量"""
try:
import applescript
applescript.run(f'set volume output volume {volume}')
except Exception as e:
self.logger.warning(f"设置macOS音量失败: {e}")
def _get_linux_volume(self):
"""获取Linux音量"""
if self.linux_tool == "pactl":
return self._get_pactl_volume()
elif self.linux_tool == "wpctl":
return self._get_wpctl_volume()
elif self.linux_tool == "amixer":
return self._get_amixer_volume()
return 70
def _set_linux_volume(self, volume):
"""设置Linux音量"""
if self.linux_tool == "pactl":
self._set_pactl_volume(volume)
elif self.linux_tool == "wpctl":
self._set_wpctl_volume(volume)
elif self.linux_tool == "amixer":
self._set_amixer_volume(volume)
elif self.linux_tool == "alsamixer":
self._set_alsamixer_volume(volume)
def _get_pactl_volume(self):
"""使用pactl获取音量"""
try:
result = subprocess.run(
["pactl", "list", "sinks"],
capture_output=True,
text=True
)
if result.returncode == 0:
for line in result.stdout.split('\n'):
if 'Volume:' in line and 'front-left:' in line:
match = re.search(r'(\d+)%', line)
if match:
return int(match.group(1))
except Exception as e:
self.logger.debug(f"通过pactl获取音量失败: {e}")
return 70
def _set_pactl_volume(self, volume):
"""使用pactl设置音量"""
try:
subprocess.run(
["pactl", "set-sink-volume", "@DEFAULT_SINK@", f"{volume}%"],
capture_output=True,
text=True
)
except Exception as e:
self.logger.warning(f"通过pactl设置音量失败: {e}")
def _get_wpctl_volume(self):
"""使用wpctl获取音量"""
try:
result = subprocess.run(
["wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@"],
capture_output=True,
text=True,
check=True
)
return int(float(result.stdout.split(' ')[1]) * 100)
except Exception as e:
self.logger.debug(f"通过wpctl获取音量失败: {e}")
return 70
def _set_wpctl_volume(self, volume):
"""使用wpctl设置音量"""
try:
subprocess.run(
["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", f"{volume}%"],
capture_output=True,
text=True,
check=True
)
except Exception as e:
self.logger.warning(f"通过wpctl设置音量失败: {e}")
def _get_amixer_volume(self):
"""使用amixer获取音量"""
try:
result = subprocess.run(
["amixer", "get", "Master"],
capture_output=True,
text=True
)
if result.returncode == 0:
match = re.search(r'(\d+)%', result.stdout)
if match:
return int(match.group(1))
except Exception as e:
self.logger.debug(f"通过amixer获取音量失败: {e}")
return 70
def _set_amixer_volume(self, volume):
"""使用amixer设置音量"""
try:
subprocess.run(
["amixer", "sset", "Master", f"{volume}%"],
capture_output=True,
text=True
)
except Exception as e:
self.logger.warning(f"通过amixer设置音量失败: {e}")
def _set_alsamixer_volume(self, volume):
"""使用alsamixer设置音量"""
try:
script = f"""
spawn alsamixer
send "m"
send "{volume}"
send "%"
send "q"
expect eof
"""
subprocess.run(
["expect", "-c", script],
capture_output=True,
text=True
)
except Exception as e:
self.logger.warning(f"通过alsamixer设置音量失败: {e}")
@staticmethod
def check_dependencies():
"""检查并报告缺少的依赖"""
import platform
system = platform.system()
missing = []
if system == "Windows":
try:
import pycaw
except ImportError:
missing.append("pycaw")
try:
import comtypes
except ImportError:
missing.append("comtypes")
elif system == "Darwin": # macOS
try:
import applescript
except ImportError:
missing.append("applescript")
elif system == "Linux":
import shutil
tools = ["pactl", "wpctl", "amixer", "alsamixer"]
found = False
for tool in tools:
if shutil.which(tool):
found = True
break
if not found:
missing.append("pulseaudio-utils、wireplumber 或 alsa-utils")
if missing:
print(f"警告: 音量控制需要以下依赖,但未找到: {', '.join(missing)}")
print("请使用以下命令安装缺少的依赖:")
if system == "Windows":
print("pip install " + " ".join(missing))
elif system == "Darwin":
print("pip install " + " ".join(missing))
elif system == "Linux":
print("sudo apt-get install " + " ".join(missing))
return False
return True |