Регулятор громкости на Python amixvol

On: 26 октября 2010 г.

 После перехода на OpenBox я пытался найти нормальный регулятор громкости звука в определённом канале для системного трея. Но искал я такую программу не долго. Вспомнив о команде amixer, я твердо решил создать что то свое. 


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

Инструменты:
  • пакет python-qt4 версии не ниже 4.7.3
  • пакет python версии не ниже 2.5 (на более низких версиях не тестировалось)

Требования:
  • Иконка в трее (две одна на без звук другая при включенном канале)
  • Появление окна регулирования при счилчке левой кнавиши мыши по иконке в трее.
  • Прятать окно при потере фокуса с объекта полосы прокрутки.
  • Слежение за уровнем звука на канале (индикатор включение выключения звука)

Описание работы программы amixvol

Иконки для программы я брал из темы Faenza (audio-volume-high.svg, audio-volume-muted.svg)
Для определения места нахождения иконок использовал полные пути (без заморочек с текущим каталогом)

pathsvg = '/home/cryptspirit/Sha-bang/audio/'

pic_high = pathsvg + 'audio-volume-high.svg'
pic_muted = pathsvg + 'audio-volume-muted.svg'

Для обработки события потери фокуса я подправил стандартный класс QtGui.QSlider создав свой slider_Event. В составе которого я изменил только процедуры потери фокуса, инициализации объекта и получения фокуса. Проблема заключается в том что в окне программы у нас находиться два объекта self.slider(сам регулятор громкости) и self.mCheckBox(переключатель режима без звука). Дело в том что пока у меня возникают заморочки со скрытием окна программы (писал программу стихийно, как только заработала более менее нормально то изменять код я перестал и отложил это до лучших времен). Когда один объект теряет фокус, а второй его еще не получил окно должно закрыться. Но объект mCheckBox у меня почему то упорно сбрасывает с себя фокус и у меня еще не было времени отработать все варианты с потерей и передачей фокуса. Для первого варианта я использовал флажок flag, если его значение находиться в положении False то окно отображаться не будет.
Далее идет описание канала с каким будем работать
channel = 'Front'
Я специально не стал заострять внимание на выборе канала(возможно это будет реализовано в графическом интерфейсе). Пока что канал можно определить как текстовую переменную channel. Для отображение текущего канала который использует amixvol использую подсказки
self.tray.setToolTip('[' + channel + ']')
Расмотрим подробнее работу функции получения информации о состоянии звукового канала

def getval(self, chl):
        s = subprocess.Popen(['amixer', '-c', '0', 'get', channel],
                            stdout=subprocess.PIPE).communicate()[0]
        try: re.search(r"\[off\]", s).group()
        except:
            self.mCheckBox.setChecked(False)
        else:
            self.tray.setIcon(QtGui.QIcon(pic_muted))
            self.mCheckBox.setChecked(True)
        s1 = re.search(r"" + chl + " Left: Playback.* \[",
                        s).group()[21:-1]

        val = re.search(r".*\d \[", s1).group()[:-2]
        s2 = re.search(r"Limits: Playback .*", s).group()[17:]
        minn = re.search(r".* -", s2).group()[:-2]
        return int(s2[len(minn) + 2:]), int(minn), int(val)

Как раньше и описывалось для манипуляции со звуком и получение информации о состоянии звукового канала будем использовать утилиту amixer. Вызываем amixer для получения данных о состоянии нашего указанного канала.
s = subprocess.Popen(['amixer', '-c', '0', 'get', channel],
                            stdout=subprocess.PIPE).communicate()[0]
далее используя регулярные выражения определяем режим (звук или без звука). Это делается просто. Выражение "amixer -c 0 get Front" возвращает приблезительно следующее:

Simple mixer control 'Front',0
  Capabilities: pvolume pswitch
  Playback channels: Front Left - Front Right
  Limits: Playback 0 - 64
  Mono:
  Front Left: Playback 39 [61%] [-25.00dB] [off]
  Front Right: Playback 39 [61%] [-25.00dB] [off]

Где "[off]" указывает на беззвучный режим канала. Используя исключения я определяю наличие текста "[off]" в полученной строке.
Если звук выключен есть смысл поставить иконку безвучного режима и установить значение объекта mCheckBox в True(поставить флажок). После чего снова используя регулярные выражения получаем уровень громкости звукового канала.

s1 = re.search(r"" + chl + " Left: Playback.* \[",
                        s).group()[21:-1]

Определяем текущее показание громкости

val = re.search(r".*\d \[", s1).group()[:-2]

Определяем границы громкости

minn = re.search(r".* -", s2).group()[:-2]
return int(s2[len(minn) + 2:]), int(minn), int(val)

И возвращаем их при выходе из функции

Далее осталось описать процедуры контроля громкости

    def Sound_Control(self):
            self.indk()
            subprocess.Popen(['amixer', '-c', '0', 'set', channel,
                            str(self.slider.value())],
                            stdout=subprocess.PIPE)

    def S_Control(self):
        if self.mCheckBox.isChecked() == True:
            subprocess.Popen(['amixer', '-c', '0', 'set', channel,
                            'mute'], stdout=subprocess.PIPE)
        else:
            subprocess.Popen(['amixer', '-c', '0', 'set', channel,
                            'unmute'], stdout=subprocess.PIPE)
        self.indk()

Главным индикатором здесь выступает состояние объекта mCheckBox. В процедуре Sound_Control, которая вызывается при извинении полосы прокрутки берется текущее положение полосы и через вызов утилиты amixer устанавливается определенный уровень громкости. Процедура S_Control вызывается по извинению значения флажка mCheckBox. А потом опираясь на его состояние устанавливается беззвучный режим или нет.
Это краткое описание работы программы amixvol. Оно как и сам код программы в этой статье еще будет меняться в  ходи улучшения самой программы.
Скачать файл программы можно здесь

Листиг всей программы:


import subprocess
from PyQt4 import QtCore, QtGui
import sys
import time
import re

flag = False
pathsvg = '/home/cryptspirit/Sha-bang/audio/'

pic_high = pathsvg + 'audio-volume-high.svg'
pic_muted = pathsvg + 'audio-volume-muted.svg'

channel = 'Front'

class slider_Event(QtGui.QSlider):
    def __init__(self, parent=None):
        QtGui.QSlider.__init__(self)
        self.flag = False
    def focusOutEvent(self, event):
        self.flag = False
    def setFocus(self):
        self.flag = True

class WidgetLayer(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, None, QtCore.Qt.Tool |
                                    QtCore.Qt.FramelessWindowHint |
                                    QtCore.Qt.WindowStaysOnTopHint)

        self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(pic_high))
        self.tray.activated.connect(self.iconActivated)
        self.tray.setToolTip('[' + channel + ']')
        self.tray.show()
        self.val = 0
        self.ck = False
        self.createActions()

        mainLayout = QtGui.QVBoxLayout()

        self.slider = slider_Event(QtCore.Qt.Vertical)
        self.slider.valueChanged.connect(self.Sound_Control)

        self.mCheckBox = QtGui.QCheckBox('')
        self.mCheckBox.toggled.connect(self.S_Control)

        mainLayout.addWidget(self.slider)
        mainLayout.addWidget(self.mCheckBox)
        self.setLayout(mainLayout)

        self.getval(channel)

        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.lostfocus)
        self.timer.start(3000)

    def lostfocus(self):
        if self.slider.flag == False:
            self.setVisible(False)

    def getval(self, chl):
        s = subprocess.Popen(['amixer', '-c', '0', 'get', channel],
                            stdout=subprocess.PIPE).communicate()[0]
        try: re.search(r"\[off\]", s).group()
        except:
            self.mCheckBox.setChecked(False)
        else:
            self.tray.setIcon(QtGui.QIcon(pic_muted))
            self.mCheckBox.setChecked(True)
        s1 = re.search(r"" + chl + " Left: Playback.* \[",
                        s).group()[21:-1]

        val = re.search(r".*\d \[", s1).group()[:-2]
        s2 = re.search(r"Limits: Playback .*", s).group()[17:]
        minn = re.search(r".* -", s2).group()[:-2]
        return int(s2[len(minn) + 2:]), int(minn), int(val)

    def sw(self):
        self.slider.setFocus()
        max, min, val = self.getval(channel)
        self.slider.setMaximum(max)
        self.slider.setMinimum(min)
        self.slider.setValue(val)
        self.setGeometry(QtGui.QCursor.pos().x(),
                        QtGui.QCursor.pos().y(), 10, 150)
        self.setVisible(True)

    def createActions(self):
        quitAction = QtGui.QAction("&Quit", QtGui.qApp,
                                    triggered=self.qui)
        self.trayIconMenu = QtGui.QMenu()
        self.trayIconMenu.addAction(quitAction)
        self.tray.setContextMenu(self.trayIconMenu)

    def iconActivated(self, reason):
        if reason == QtGui.QSystemTrayIcon.Trigger:
            self.sw()

    def qui(self):
        self.hide()
        sys.exit(1)

    def indk(self):
        if self.mCheckBox.isChecked() != True:
            self.tray.setIcon(QtGui.QIcon(pic_high))
        else:
            self.tray.setIcon(QtGui.QIcon(pic_muted))

    def Sound_Control(self):
            self.indk()
            subprocess.Popen(['amixer', '-c', '0', 'set', channel,
                            str(self.slider.value())],
                            stdout=subprocess.PIPE)

    def S_Control(self):
        if self.mCheckBox.isChecked() == True:
            subprocess.Popen(['amixer', '-c', '0', 'set', channel,
                            'mute'], stdout=subprocess.PIPE)
        else:
            subprocess.Popen(['amixer', '-c', '0', 'set', channel,
                            'unmute'], stdout=subprocess.PIPE)
        self.indk()

def main():
    app = QtGui.QApplication(sys.argv)
    QtGui.QApplication.setQuitOnLastWindowClosed(False)
    QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
    volcontr = WidgetLayer()
    sys.exit(app.exec_())
    return 0

if __name__ == '__main__':
    main()



Предуприждение:

Программа находиться в стадии разработки и если вы увидели не точности либо у вас есть предложения по изменению прицепа работы программы пишите в комментарии. Возможно автор тоже давно заметил свои недочеты но за недостатком свободного времени решил использовать хотя бы такой (рабочий) вариант. Это предупреждение исчезнет после выхода окончательной версии.

0 коммент. on "Регулятор громкости на Python amixvol"