- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF109:Python
Материал из Linuxformat.
- Hardcore Linux Проверь себя на крутом проекте для продвинутых пользователей
Содержание |
Python: Создаем GUI для Festival
- Python и Qt идеальны для быстрой разработки приложений. Грэм Моррисон докажет это, используя новейшие технологии KDE и примерно 60 строк кода.
В прошлом месяце мы связали Python и синтезатор речи Festival для создания читалки RSS, озвучивавшей слова из RSS-ленты. Если вы прошли тот урок полностью, то уже знаете, что Festival – синтезатор достойный, но образцом дружелюбия к пользователю не является. Даже простейшие задачи, вроде печати слов и их последующего чтения, требуют множества аргументов и загадочного формата командной стро- ки. Нет, это не выход. Для Festival явно нужны простейший графический интерфейс с полем для печати слов и большая кнопка «ЧИТАТЬ». Именно его мы и создадим на данном уроке.
Python и Qt
Чтобы облегчить себе жизнь, воспользуемся тем же средством быстрой разработки, что и в прошлом месяце – Python. Несмотря на свою скриптовую природу, Python отлично подходит и для создания приложений с графическим интерфейсом. В нем не нужно беспокоиться о всякой ерунде типа объектов, типов и инициализации, тормозящей разработку графических интерфейсов в других языках. Программы на Python эффективны и легко модифицируются, и запускать их можно немедленно. Полностью работоспособные приложения могут уложиться в несколько строк кода – эквивалентам на C или C++ потребуется во много раз больше. Однако по умолчанию Python не предусматривает средств для создания графических интерфейсов. Как и в случае RSS-читалки из прошлого месяца, нужная функциональность обеспечивается добавочными модулями; по части GUI их имеется несколько. Совместно с Python обычно используются два кросс-платформенных инструментария – wxWidgets и GTK, оба могут быть внедрены в ваш собственный Python-скрипт импортированием модулей WXPython и PyGTK в его начале. Но мы-то возьмем PyQt, другой популярный GUI- модуль; как следует из названия, он позволяет применять Qt внутри скриптов Python. Тот же инструментарий использован для рабочего стола KDE, и его API битком набит возможностями, от вывода 3D-графики до проигрывания музыки. Месяц назад мы делали обзор последнего релиза Qt (4.4), а команда разработчиков PyQt уже успела интегрировать эти новшества в последний релиз PyQt. Итак, программисты на Python получили преимущества нового Qt, включая виджет браузера WebKit.
Это очень важно для нашего проекта, потому что мы воспользуемся одной из новых возможностей, чтобы заставить Festival заговорить. Имя ее – Phonon, это мультимедиа-инструментарий KDE4, здорово облегчающий работу с мультимедиа-потоками. С самим KDE мы иметь дела не будем. Phonon портирован обратно в Qt, и его функциональность можно применять, обойдясь без разработки полновесного KDE-приложения. До Phonon не существовало стандарта кросс-платформенного (или даже кросс-интерфейсного) метода работы с аудио- и видео-потоками. Это раздражало и делало работу программиста гораздо сложнее, чем надо бы. С Phonon вам, например, больше не требуется использовать DirectMedia в Windows, QuickTime в OS X и GStreamer в Linux: он выступает как посредник для этих технологий. Вы пишете код для проигрывания, а Qt выполняет за вас остальную работу, конвертируя ваш запрос в мультимедиа-инструментарий выбранной вами платформы. Та же философия принята и внутри Python, и поэтому мы выбрали PyQt для нашего проекта.
Приложим руки к ушам
Phonon так важен для нашего графического интерфейса синтезатора речи, потому что нам нужно контролировать вывод звука самим. Festival включает собственную возможность проигрывания, но она намертво завязана с древней Open Sound System (OSS). А значит, нельзя поделить аудиокарту с другими приложениями, что приводит к сбоям работы во многих современных дистрибутивах Linux. Чтобы обойти это ограничение, будем записывать вывод от Festival в звуковой файл, благо что это не так трудно сделать. Наше приложение будет затем воспроизводить его, для чего мы задействуем Phonon.
У этого решения есть два плюса. Во-первых, оно не имеет ограничений, присущих OSS, и с радостью делит аудиокарту с другими приложениями. Во-вторых, пользователь получает возможность перерыва, останова и перемотки синтезируемой речи. Этого трудно достичь без использования аудиофайла, так как синтез лучше не прерывать на пол-пути. Вывод звука займет лишь доли секунд, затем аудиофайл может быть загружен и проигран. Но все это будет наша Часть 2.
Часть 1 Создаем графический интерфейс
Для данной работы важно предусмотреть следующее. Очевидно, понадобятся установленные и работающие Python и Festival – см. урок прошлого месяца, если у вас возникнут сложности с ними. Еще нам необходим SIP (модуль, создающий привязки C и C++ с Qt) для Python, также как и GStreamer, если они не установлены, а дополнительно – Qt 4.4 и PyQt 4.4. Вы, наверное, уже сможете добавить двоичные пакеты для обоих через менеджер пакетов вашего дистрибутива, но во время написания статьи нам пришлось компилировать и устанавливать и Qt 4.4, и PyQt 4.4 вручную из исходных кодов, что заняло несколько часов. Также вам необходимо убедиться в наличии небольшой утилиты командной строки pyuic4; она либо скачивается с исходными кодами PyQt, либо имеется в отдельном пакете (pyqt-dev-tools для пользователей Debian и Ubuntu).
Итак, приступим к программированию. PyQt имеет доступ ко всему API Qt, поэтому мы можем использовать Qt Designer для разработки пользовательского интерфейса нашего приложения, и создадим в нем основное окно нашей программы. Возможно, куда проще сделать это вручную, но если вы захотите расширить программу, добавив новые возможности и элементы интерфейса, Designer сделает этот процесс интуитивнее.
Если вы установили Qt из исходных кодов, убедитесь, что переменная окружения QTDIR указывает на новое место размещения Qt 4. Запустите Designer из командной строки или меню «Разработка» на рабочем столе. Проследуйте через все три этапа (далее) для добавления нужных приложению графических элементов, затем сохраните результат в файл в директории проекта. Соглашение требует начинать имя файла с ui_, так что определить, где хранится интерфейс пользователя, нетрудно. Файлы Designer используют XML для описания схемы только что спроектированного приложения, и теперь нам нужно сконвертировать его в рабочий код Python для построения интерфейса. Инструмент для этого преобразования называется pyuic4. Наберите pyuic4 ui_fooey.ui > ui_fooey.py для создания кода Python (заменив fooey на имя вашего скрипта)..
Обычно pyuic4 генерирует самостоятельный код, не требующий доработки. Но из-за использования Phonon, слишком нового для Qt Designer, нужно отредактировать несколько строк в файле ui_filename.py вручную. Откройте его в вашем любимом текстовом редакторе и просто наберите from PyQt4.phonon import Phonon в отдельной строке, сразу под первой из строк from в начале файла. Так обеспечивается импорт Phonon из PyQt, и можно использовать его функции совместно с графическим интерфейсом. Поискав ниже, замените три строки, начинающиеся на self.progressBar, на следующие две, и поправьте строку self.horizontalLayout, чтобы использовать self.seekSlider:
self.seekSlider = Phonon.SeekSlider(self.frame) self.seekSlider.setObjectName(“progressBar”) self.horizontalLayout.addWidget(self.seekSlider)
Здесь мы заменили виджет индикатора выполнения на виджет Phonon, что позволит пользователю передвигать и видеть позицию текущего воспроизведения при прослушивании музыкального файла. Если бы Designer поддерживал этот виджет напрямую, данный шаг был бы не нужен: мы бы просто перетащили виджет с панели инструментов. Это также означает, что если вы измените компоновку интерфейса (в Designer) и снова запустите pyuic4, вам придется осуществить все изменения вновь, так как pyuic4 автоматически перезапишет файл при генерации кода Python.
Вот и весь код, необходимый графическому интерфейсу. Следующий шаг – написание скрипта для использования объектов созданного GUI и добавления «разговорной» функциональности Festival.
Шаг за шагом: Создаем графический интерфейс
1 Основное окно
Выберите шаблон Main Window в стартовом мастере Designer. Перетащите на него с панели инструментов контейнер-рамку и перенесите в нее два виджета PushButton: TextEdit и ProgressBar.
2 Размещение
Два раза кликните на кнопки для изменения надписей. Мы используем их для воспроизведения/паузы и остановки. Выберите индикатор выполнения и два виджета кнопок, а затем нажмите Ctrl+1 и выровняйте их горизонтально.
3 Компоновка
Можете поиграть с компоновкой виджетов, разместив их так, как вам нравится. Удовлетворившись результатом, нажмите Ctrl+5 для общего выравнивания по внутренней сетке и сохраните файл в директории вашего проекта.
Часть 2 Логика кода
Не вводите весь код за одну сессию. Попробуйте набирать по куску кода, затем запускать для проверки скрипт, чтобы убедиться в его работоспособности. Это упростит отладку.
Создайте для остатка нашего скрипта новый текстовый файл. Первым делом опишем модули, планируемые к применению. В начале файла добавьте следующие четыре строки:
import sys, os, time, tempfile from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon from ui_fooey import Ui_MainWindow
Первая добавляет основную функциональность Python. Модуль sys мы использовали на прошлом уроке, модуль os обеспечивает работу с файлами и путями, time понадобится нам позже для добавления небольшой задержки, чтобы мы могли дождаться создания аудио-файла, а при помощи tempfile Python автоматически генерирует правильные пути временных файлов в системе. Ниже этой строки мы импортируем основные функции Qt из PyQt4, а еще ниже – импортируем Phonon. Последняя строка импортирует класс GUI, автоматически созданный pyuic4 из нашего файла Designer. Здесь ui_fooey – имя нашего файла, а Ui_MainWindow – имя класса внутри файла.
Если вы пока не имели дела с именами классов, то их можно представить как обобщение функций, используемое в объектно-ориентированном программировании. Это комбинация данных и функций, их обрабатывающих, сведенная в единую самодостаточную сущность, или «класс». Импорт класса в скрипт Python обеспечит нам доступ к компоновке интерфейса и уже созданным нами виджетам, а также к данным, хранящимся в этих элементах. Мы получим все это, создав новый класс в нашем коде. Под строками импорта добавьте следующее:
class StartQT4(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self)
Сперва мы создаем новый класс с именем StartQt4 и он описывается как тип QMainWindow – его же мы создали и в Designer. Это родительский тип окна для всех приложений Qt, и это означает нашу возможность управлять окном нашего приложения так же, как и в других программах. Как альтернативу, Qt включает виджеты диалогов и окон, но они не часто используются в самостоятельных приложениях. Команда создания следует за описанием функции с именем __init__ без указания класса.
Как и другие функции Python с похожим названием, она запускается при инициализации объекта и содержит все подпрограммы настройки, которые понадобятся нам для добавления функциональности в ранее созданные виджеты. Далее мы говорим этому новому окну, что интерфейс пользователя управляется из класса основного окна (self.ui = Ui_MainWindow), а затем просим Qt выполнить компоновку графического интерфейса. Теперь передйем к виджетам в основном окне. Под кодом, приведенным выше, и на таком же уровне отступов добавьте следующие строки:
self.audioOutput = Phonon.AudioOutput(Phonon. MusicCategory, self) self.metaInformationResolver = Phonon.MediaObject(self) self.mediaObject = Phonon.MediaObject(self) self.ui.seekSlider.setMediaObject(self.mediaObject) Phonon.createPath(self.mediaObject, self.audioOutput)
Данный участок кода содержит конфигурацию Phonon и настройки нашего приложения. Phonon использует несколько соединений для подключения источников, которые мы хотим проиграть, с устройством, на котором мы хотим их проигрывать. Этого достигают первые несколько строк вышеприведенного кода, создавая путь к аудиовыходу через audioOutput и метаданные, необходимые Phonon для распознавания вида проигрываемого содержимого. Мы связали все это с индикатором проигрывания в графическом интерфейсе и соединили объекты друг с другом. На данном этапе при запуске программы Phonon уже находится в стадии готовности к воспроизведению и ждет сигнала к началу работы. Чтобы сделать что-то интересное, добавьте следующие строки:
self.connect(self.ui.pushButton2, QtCore.SIGNAL(‘pressed()’), self.mediaObject, QtCore.SLOT(‘stop()’)) self.connect(self.ui.pushButton1, QtCore.SIGNAL(‘pressed()’), self.playPause)
Тех, кто не привык к Qt (как, впрочем, и искушенных программистов), эти строки озадачат: здесь применен механизм слотов и сигналов Qt. Сигналы и слоты действуют как указания для управления событиями внутри приложения, прокладывая причинно-следственную связь между различными функциями и объектами программы. Эти связи возникают, когда пользователь нажимает на иконку Сохранить Файл или кликает правой кнопкой мыши на виджет TextEdit, и многие события уже имеют соединения по умолчанию. Так, ползунок Playback автоматически обновляет свое положение в зависимости от места проигрывания аудио-файла, а виджет основного окна Resize автоматически информирует механизм компоновки о необходимости пере рисовки дисплея. В верхней строке мы велим приложению вызвать функцию Phonon stop() (слот), когда кнопка pushButton2 будет нажата [pressed()] (сигнал). Следующая строка вызывает похожую функцию, только в этом случае мы соединяем сигнал кнопки pushButton1 со слотом – функцией playPause. Ее-то мы сейчас и напишем.
def playPause(self): if self.mediaObject.state() == Phonon.PlayingState: self.mediaObject.pause() elif self.mediaObject.state() == Phonon.PausedState: self.mediaObject.play() else: os.popen(‘echo “’ + str(self.ui.textEdit.toPlainText()) + ‘” | festival_client --ttw > ‘ + tmpfile, “r”) time.sleep(0.2) self.mediaObject.setCurrentSource(Phonon.MediaSource(str(tmpfile))) self.mediaObject.play()
Отступы в def нужны такие же, как в __init__, где мы начали класс StartQt4. Это наша реализация слота playPause, и данный код запустится, когда пользователь нажмет на кнопку Play/Pause, поэтому мы сперва проверяем состояние проигрывания внутри кода. Если воспроизведение уже идет, мы ставим его на паузу командой self.mediaObject.pause; если же оно приостановлено, мы возобновляем его. При обнаружении состояния, отличного от этих, мы подразумеваем, что проигрывание было остановлено, а слова в текстовом редакторе – изменены. Тогда мы запускаем в командной строке festival_client, передаем содержимое текстового поля (self.ui.textEdit.toPlainText) и перенаправляем вывод звукового потока во временный файл. Для этого мы используем команду Python os.popen. Она берет строку внутри кавычек как аргумент и запускает команду от имени приложения. На время выполнения команды программа останавливается, но нужно подождать несколько секунд для обновления файловой системы, что обеспечивается строкой time.sleep(0.2). Затем мы используем этот WAV-файл как источник для объекта проигрывания Phonon и немедленно начинаем воспроизведение.
Осталось сделать только одну вещь: прибрать за собой (э, откуда тут этот временный файл?) и написать функцию main приложения – ту, что запускается первой при его старте.
if __name__ == “__main__”: app = QtGui.QApplication(sys.argv) app.setApplicationName(“Speech Synth”) tmpfile = tempfile.mktemp(suffix=’.wav’) os.popen(‘touch ‘ + tmpfile) myapp = StartQT4() myapp.show() try: sys.exit(app.exec_()) except SystemExit: pass os.remove(tmpfile)
Первая строка данного кода не должна иметь отступа. Это главная подпрограмма, которая выполнится при запуске нашего скрипта, эквивалент ‘int main()’ в C и C++. В ней мы генерируем интерфейс из ранее созданного объекта и даем ему имя приложения. Далее мы переходим к созданию временного файла для сохранения «сырых» аудио- данных вывода Festival. В Linux это обычно директория /tmp в вашем корневом разделе, и мы используем ранее импортированный модуль tempfile, чтобы автоматически назначить корректный путь к временной директории для файла, который нам необходимо создать. Мы также снабжаем файл расширением .wav, чтобы Phonon знал, какой вид данных он проигрывает. Затем мы запускаем в командной строке touch для создания пустого файла, который Phonon будет использовать как источник для воспроизведения. Наконец, запускаем приложение и показываем графический интерфейс. Приложение теперь зациклено; оно ожидает, когда пользователь закроет его с помощью кнопки в заголовке окна. Когда это произойдет, мы отловим событие в операторе try и дадим скрипту Python удалить временный файл перед выходом из программы.
Сохраните этот файл и запустите Festival в режиме сервера: ‘festival --server’. Теперь выполните скрипт, набрав python fooey.py. Вам будет предоставлен графический интерфейс на Qt с полем для ввода текста. Нажатие на Play пошлет текст в Festival, а файл результата – назад в Phonon, который затем его проиграет. Вы можете ставить на паузу и перематывать файл вперед и назад, используя ползунок. Если у вас кризис идей – что бы такое еще добавить? – гляньте на наш список задач; а кто внесет в программу какие-нибудь грандиозные дополнения, сообщите нам. LXF
Список задач
- Кэшировать вывод речи, чтобы одинаковый текст не требовал повторного преобразования, когда пользователь нажимает stop/play.
- Существует проблема безопасности в использовании tempfile (он может быть подделан). Решение – использовать tempfile.mktemp
- Перехватывать вывод с Festival напрямую и использовать его как исходные данные для проигрывания.
- Добавить в интерфейс чтение и запись файлов.
- Обеспечить больший контроль над Festival, включая возможность изменять голос диктора.
- Использовать Phonon для добавления эффектов в аудиовывод, например, реверберации, задержки и сдвига тона.