""" Volume control module. Regulates system volume on a scale from 1 to 10. """ # Модуль управления громкостью системы. # Работает через различные системные утилиты в зависимости от ОС. import subprocess import re import platform from ..core.roman import replace_roman_numerals try: import pymorphy3 _MORPH = pymorphy3.MorphAnalyzer() except Exception: _MORPH = None # Карта для перевода слов в цифры ("пять" -> 5) NUMBER_MAP = { "ноль": 0, "один": 1, "одна": 1, "раз": 1, "единица": 1, "единичка": 1, "два": 2, "две": 2, "двойка": 2, "двоечка": 2, "три": 3, "тройка": 3, "троечка": 3, "четыре": 4, "четверка": 4, "четверочка": 4, "пять": 5, "пятерка": 5, "пятерочка": 5, "шесть": 6, "шестерка": 6, "шестерочка": 6, "семь": 7, "семерка": 7, "семерочка": 7, "восемь": 8, "восьмерка": 8, "восьмерочка": 8, "девять": 9, "девятка": 9, "девяточка": 9, "десять": 10, "десятка": 10, "десяточка": 10, } _VOLUME_COMMAND_RE = re.compile(r"\b(громкост\w*|звук\w*|volume)\b") def _lemmatize(token: str) -> str: if _MORPH is None: return token return _MORPH.parse(token)[0].normal_form.replace("ё", "е") def _get_volume_command(level: int): """ Возвращает команду для изменения громкости в зависимости от ОС. Args: level: Уровень громкости (1-10) Returns: Список команд для выполнения или None, если команда не поддерживается """ percentage = level * 10 system = platform.system().lower() if system == "linux": # Проверяем доступность различных утилит if _command_exists("pactl"): # Используем PulseAudio (более современный подход) return ["pactl", "set-sink-volume", "@DEFAULT_SINK@", f"{percentage}%"] elif _command_exists("amixer"): # Используем ALSA return ["amixer", "-q", "sset", "Master", f"{percentage}%"] else: # Проверяем alsamixer if _command_exists("alsamixer"): return ["amixer", "-q", "sset", "Master", f"{percentage}%"] elif system == "darwin": # macOS return ["osascript", "-e", f"set volume output volume {percentage}"] elif system == "windows": # Для Windows используем PowerShell команду # Это требует дополнительных библиотек, поэтому пока просто покажем сообщение print("⚠️ Настройка громкости на Windows требует дополнительных библиотек") return None return None def _command_exists(command): """ Проверяет, существует ли команда в системе. Args: command: Название команды Returns: True, если команда существует """ try: result = subprocess.run( ["which", command], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False, ) return result.returncode == 0 except: try: # Альтернативная проверка для Windows result = subprocess.run( ["where", command], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False, ) return result.returncode == 0 except: return False def set_volume(level: int) -> bool: """ Устанавливает системную громкость (шкала 1-10). 1 -> 10% 10 -> 100% Args: level: Число от 1 до 10. Returns: True, если успешно. """ if not isinstance(level, int): print( f"❌ Ошибка: Уровень громкости должен быть целым числом, получено {type(level)}" ) return False # Ограничение диапазона if level < 1: level = 1 elif level > 10: level = 10 percentage = level * 10 # Получаем команду для текущей ОС cmd = _get_volume_command(level) if cmd is None: print(f"❌ Не найдена подходящая утилита для изменения громкости на вашей системе") print(f"💡 Установите PulseAudio (pactl) или ALSA (amixer) для управления громкостью") return False try: # Выполняем команду result = subprocess.run(cmd, check=True, capture_output=True, text=True) print(f"🔊 Громкость установлена на {level} ({percentage}%)") return True except subprocess.CalledProcessError as e: print(f"❌ Ошибка при установке громкости: {e}") print(f"💡 Убедитесь, что у вас установлены и настроены аудио утилиты (pactl, amixer)") return False except Exception as e: print(f"❌ Неизвестная ошибка громкости: {e}") return False def parse_volume_text(text: str) -> int | None: """ Пытается найти число громкости в тексте. Понимает и цифры ("5"), и слова ("пять"). """ text = replace_roman_numerals(text.lower().replace("ё", "е")) # 1. Ищем цифры в любом месте фразы. for match in re.finditer(r"\d+", text): value = int(match.group()) if 1 <= value <= 10: return value # 2. Ищем числительные и разговорные формы по леммам: # "семерку", "десяточку", "на двух" -> 7, 10, 2. for token in re.findall(r"[a-zA-Zа-яА-ЯёЁ]+", text): value = NUMBER_MAP.get(_lemmatize(token)) if value is not None and 1 <= value <= 10: return value return None def is_volume_command(text: str) -> bool: if not text: return False return bool(_VOLUME_COMMAND_RE.search(text.lower().replace("ё", "е")))