Программа управления звуком с помощью PyQT

Программа управления звуком с помощью PyQT

Приветствие, давно не писал ничего. Сегодня займемся созданием лайт-виджета для регулировки уровня звука в системе. Таких программ достаточно много, но мне нужна была маленькая, быстрая и под стиль моей панели tint2.

Начнем с формулировки того, что мы хотим получить у нас будет виджет, без панели управления и скрытый из таскбара; координаты расположения крайний левый угол экрана; на весь размер окна мы растягиваем иконку текущего положения звука. Всего будет 4 разных типа отображения: high, medium, low, mute; регулировка звука по прокручиванию колесика мыши над виджетом; дополнительная возможность: по нажатию правой кнопки мы запоминаем текущее значение уровня звука и выставляем его на 0 для системы, на последующее нажатие правой кнопки мы берем сохраненное значение и возобновляем его.

Давайте сразу подумаем о среде, которую мы возьмем для программирования. Языком я выбрал Python, так как он достаточно прост и подходит для такой работы, а графическая библиотека — Qt.

Перед написанием всего проекта я всегда сразу проверял работу её “ядра”. Зачем писать все остальное, если главная цель программы ещё не продумана и не факт, что будет работать. Поэтому мы сразу беремся за получение и выставление уровня звука.

Получаем уровень звука в системе

Через пару минут мучения консоли я нашел способ получения уровня звука. Все довольно просто, для получения текущих характеристик звука нам нужно ввести в консоли

$ amixer

На что мы получим подробный вывод о всех звуковых каналах. Теперь нам нужно получить Master-канал, который и отвечает за общий звук в системе:

$ amixer get Master

В выводе я получил примерно такое: ockonal@wincode $ amixer get Master

Simple mixer control 'Master',0

Capabilities: pvolume pvolume-joined pswitch pswitch-joined

Playback channels: Mono

Limits: Playback 0 - 64

Mono: Playback 44 [69%] [-20.00dB] [on]

Легко заметить, что уровень звука хранится в разделе Mono, поэтому мы отсеем немного вывод команды:

$ amixer get Master | grep Mono grep — системная утилита для фильтрования вывода. Работает на регулярных выражениях, как я помню. Благодаря предыдущему действию мы получим строку:

Mono: Playback 44 [69%] [-20.00dB] [on]

На этом этапе мы закончим с получением звука. Конечно, можно было бы сразу ещё отсеять всю “левую” информацию, но мы сделаем это в программе, используя регулярные выражения.

Выставляем уровень звука в системе

Теперь мне на помощь, как всегда, пришел гугл. Выставить уровень звука можно через тот же alsa-mixer. Вот сразу полная команда: amixer cset iface=MIXER, name="Master Playback Volume" %VOLUME_VALUE%

Вместо %VOLUME_VALUE% вам нужно указать число от 0 до 64. Здесь нужно заметить, что не у всех звуковых плат такой уровень, в некоторых я замечал от 0 до 81 и т. д. Но в нашей статье я ограничился более-менее стандартным числом. Если захотите написать программу под большее количество компьютеров, то вам нужно будет отпарсить вывод amixer get Master, там есть пункт: Limits: Playback 0 - 64.

Что же, основной предполагаемый функционал нашей программы работает успешно, перейдем к программированию!

Создание основного интерфейса программы

Сразу напишу: если вам что-то непонятно на счет расположения кода, в конце статьи я выложил полный текст исходников. Я предполагаю, что все средства разработки у вас уже есть, так же установлен пакет PyQt (>=4).

Пишем каркас приложения: import sys from PyQt4 import QtCore, QtGui class CVolumeWindow( QtGui. QMainWindow ): pass if __name__ == '__main__': app = QtGui. QApplication( sys. argv ) volumeWindow = CVolumeWindow() volumeWindow. show() sys. exit( app. exec_() )

Надеюсь, здесь вам всё понятно. Мы создаем свой класс CVolumeWindow и наследуем его от QMainWindow. Это и будет наш основной виджет. В функции main мы создаем экземпляр нашего окна и запускаем его. Более подробно можно почитать здесь.

Пишем инициализацию окна

Теперь будем постепенно наполнять наш виджет. Для начала нужно дописать конструктор для класса: def __init__( self, parent = None ):

QtGui. QWidget.__init__( self, parent )

QtGui. QMainWindow.__init__( self, None, QtCore. Qt. Tool | QtCore. Qt. FramelessWindowHint ) self. setFocusPolicy( QtCore. Qt. StrongFocus ) self. setAttribute( QtCore. Qt. WA_QuitOnClose, True )

В __init__ мы вызываем стандартный обработчик конструктора виджета, сразу передаем в него флаги окна. Благодаря Qt. Tool мы получим окно, которое не отображается на таскбаре, а Qt. FramelessWindowHint убирает системную панель из окна. Последние 2 строчки нужны для корректного закрытия приложения, при флагах, которые я указал выше.

Сразу же перейдем к стилизации нашего виджета. Я сделал всё достаточно просто: тёмный фон. Вы можете поиграться с параметрами рамки, округления рамки и т. д. Все последующие строки кода дописывайте к конструктору.

Self. setStyleSheet( 'background-color: black;' ) после этого зададим размер нашего окна, это будет 32х32: self. resize( 32, 32 ) и перемещаем виджет в левый нижний угол: self. move( 2, QtGui. QDesktopWidget().availableGeometry().height() )

На этом инициализация нашего окна заканчивается. Переходим к функциональной части.

Пишем основной функционал

Давайте запишем глобальные для класса переменные, которые будем использовать в ходе работы: m_getVolumeRequest = 'amixer get Master | grep Mono:' m_setVolumeRequest = 'amixer cset iface=MIXER, name="Master Playback Volume"' m_curVolume = None

Первая и вторая просто хранят текст команд, которые мы будем направлять на исполнение, m_curVolume хранит текущее значение уровня звука.

Теперь можно взяться за основные функции — получения и выставления параметров миксера.

Def getVolumeValue( self ): p = Popen( self. m_getVolumeRequest, shell = True, stdout = PIPE ) outputData = p. stdout. read() curVolume = int( re. search( ur"(?<=\[)[0-9]{1,3}", outputData ).group() ) self. m_curVolume = curVolume self. repaint()

Здесь, как всегда, всё достаточно просто. В первой строке кода функции мы отсылаем запрос на получение текущего уровня звука, получаем вывод и парсим его регулярными выражениями. Вот тут и остановимся. Как работать с регулярными выражениями можно посмотреть в официальной документации, но мы разберем само регулярное выражение. Давайте посмотрим на строку, из которой нам нужно “вытащить” значение:

Mono: Playback 44 [69%] [-20.00dB] [on]

Можно заметить, что нужное нам число находится между угловыми скобками, заканчивается знаком процента и может иметь 1, 2, 3 цифры. В итоге имеем простенькое, недоработанное регулярное выражение:

"(?<=\[)[0-9]{1,3}"

(?<=\[)

Эта часть нам указывает на то, что нужно брать текст, перед которым стоит угловая скобка. Нам нужно поставить перед ней обратный слеш, чтобы парсер не принял за специальный знак.

[0-9]{1,3}

Если вы укажите символы в угловых скобках, то парсер будет искать совпадения по ним. В нашем случае: 0-9 указывает на все цифры. {1,3} указывает на количество символов для совпадения. Как я уже говорил, у нас может быть от 1 до 3 цифр. Вот и все, просто и, главное, работает.

Следующей строкой мы просто сохраняем текущий уровень звука в глобальную переменную и перерисовываем окно (у нас там будет выборка нужной иконки по состоянию миксеров).

Переходим к выставлению значения уровня звука: def setVolumeValue( self, value ):

# Convert Percent Value => Alsa Value if value < 0: value = 0 elif value > 100: value = 100 mixerVol = (64 * value) / 100 os. system( self. m_setVolumeRequest + str(mixerVol) ) self. getVolumeValue()

Надеюсь, вы ещё не забыли, что в alsa-mixer максимальное значение 64, а не 100. Поэтому у нас сразу небольшая проверка значения, а потом перевод. Для того, чтобы понять формулу достаточно пройти учебный курс за 5-й класс. Через os. system мы отсылаем запрос на изменение звука +добавляем к запросу число, характеризирующее новый уровень.

С этим все, перейдем к обработке сообщений окна. Для начала, переопределим закрытие виджета: def closeEvent( self, evt ):

QtGui. QApplication. exit()

Опять же, мы нуждаемся в этом для нормального закрытия приложения.

Теперь перейдем к ещё одной важной функции — прорисовка виджета. Здесь мы выбираем фоновую картинку для окна, которая зависит от уровня звука. Все функции, входящие здесь в состав будут описаны ниже по пунктам.

Def paintEvent( self, evt ): imagePath = self. convertValueToName(self. m_curVolume) pixmapHandle = self. getBgImage( imagePath ) paintHandle = QtGui. QPainter( self ) paintHandle. setRenderHint(QtGui. QPainter. Antialiasing, True) paintHandle. drawPixmap( pixmapHandle. rect(), pixmapHandle)

Функция convertValueToName переводит числовое значение уровня в нужную картинку. Здесь всё достаточно просто, у нас есть 4 разных картинки состояния звука. Я условно разделил их так:

0 – mute

0-33 — low

33-66 — medium

66-100 — high

Иконки имеют одноименные названия (mute. png, low. png, …). Вот реализация функции: def convertValueToName( self, curVolume ): volumeStateIcon = None if curVolume <= 0: volumeStateIcon = 'mute. png' elif curVolume > 0 and curVolume < 33: volumeStateIcon = 'low. png' elif curVolume >= 33 and curVolume <=66: volumeStateIcon = 'medium. png' elif curVolume > 66 and curVolume <= 100: volumeStateIcon = 'high. png' else: print 'Really bad day ; )' return volumeStateIcon

Надеюсь, здесь всё понятно для вас, учитывая число возвращаем нужное имя иконки. Переходим далее к разбору перерисовки окна.

Функция getBgImage подгружает иконку imagePath, подгоняет её размеры под высоту и ширину окна, возвращает хэндл.

Def getBgImage( self, imagePath ): iconHandle = QtGui. QPixmap( self. getAbosluteIconPath(imagePath) ) widthScale = float(self. width()) / float(iconHandle. width()) heightScale = float(self. height()) / float(iconHandle. height()) widthScale = round( widthScale, 1 ) heightScale = round( heightScale, 1 ) iconHandle = iconHandle. scaled( iconHandle. width()*widthScale, iconHandle. height()*heightScale,

QtCore. Qt. IgnoreAspectRatio, QtCore. Qt. SmoothTransformation ) return iconHandle

В первой строке мы опять ссылаемся на новую функцию, её задание довольно примитивно — получить абсолютный путь к папке, где находится наш скрипт (учитываем то, что все наши иконки лежат в папке icons возле скрипта: def getAbosluteIconPath( self, fileName ): return str(os. path. dirname(os. path. abspath(__file__))) + '/icons/' + fileName

Следующие 2 строки мы получаем коэффициент для новых размеров иконки. Делим размеры окна на размеры графического файла и получаем число, во сколько раз нам нужно увеличить иконку.

Далее эти коэфициенты округляем до 1 цифры после запятой и вконце, с помощью функции QPixmap. scaled ресайзим. 4-й параметр функции указывает на то, что нам нужен качественный результат. В результате чего функция будет обрабатывать иконку больше времени, но качественней.

И, на конец-то, разбираем 3 последних строчки обработчика прорисовки окна: paintHandle = QtGui. QPainter( self ) paintHandle. setRenderHint(QtGui. QPainter. Antialiasing, True) paintHandle. drawPixmap( pixmapHandle. rect(), pixmapHandle)

Создаем экземпляр QPainter, с помощью которого будем рисовать. Выставляем прорисовку с антиальясингом (сглаженные края). Последняя строчка отвечает за прорисовку иконки на виджете.

Ура, теперь вы можете запустить приложения. Что же мы увидим? Слева внизу будет окно с черным фоном и прорисованной иконкой текущего уровня звука. Всё хорошо, но мы ещё хотели изменять уровень звука.

Как мы сразу договорились, изменение будет происходить по прокручиванию колесика мыши над виджетом. Для этого нам нужно переопределить событие wheelEvent def wheelEvent( self, evt ): numDegrees = evt. delta() / 8; numSteps = numDegrees / 5; self. setVolumeValue( self. m_curVolume + numSteps )

В первой строке получаем количество градусов, на которое прокрутилось колесико, далее высчитываем количество шагов. В результате мы получим где-то 3 шага за 1 раз. И в последней строке выставляем новый уровень звука, учитывая количество шагов ролика мыши.

Теперь у нас полноценное приложение! Не хватает только иконок и последнего пункта из нашего списка заданий, а именно выключение звука по правой кнопке и восстановление по последующему нажатию. За иконки поговорим в самом конце.

Для начала нам нужно завести переменные, в которых будем хранить промежуточные данные. Допишите в самое начало, перед конструктором: m_saveRegister = None m_muttedValue = False

Первая переменная отвечает за временное хранение текущего звука, а вторая показывает какое действие нам нужно выполнить.

Переопределяем обработку нажатия мыши: def mousePressEvent( self, evt ): if evt. button() == QtCore. Qt. RightButton: if self. m_muttedValue: self. setVolumeValue( self. m_saveRegister ) self. m_muttedValue = False else: self. getVolumeValue() self. m_saveRegister = self. m_curVolume self. setVolumeValue( 0 ) self. m_muttedValue = True evt. accept()

Проверяем, нажата ли именно правая кнопка мыши, если m_muttedValue = True, значит нам нужно поставить уровень звук из временной переменной-регистра. Если же у нас False, то сохраняем текущее положение звука в регистр и устанавливаем системный уровень в 0.

На этом этапе мы закончили с программированием.


Карта сайта


Информационный сайт Webavtocat.ru