やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

PySide2+QML+PyOpenJTalk

 GUI音声合成する。

成果物

0

概要

  1. テキストボックスに任意テキストを入力する
  2. Enterキーを押す
  3. 任意テキストが音声合成される

QML

import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.0

ApplicationWindow {
    id: mainWindow
    width: 500
    height: 50
    title: qsTr("Qt + PySide2 + PyOpenJTalk")
    visible: true
    locale: locale

    Rectangle {
        color: "#FFCCDD"
        anchors.fill: parent

        TextInput {
            id: _talkText
            text: "発話したいテキストを入力してからEnterキーを押してください。"
            focus: true
            font.pixelSize: Math.max(16, parent.width / 40)
            anchors.fill: parent
            onAccepted: Connect.talk(_talkText.text)
        }
    }
}

 ポイントはイベント処理。QMLのイベントをPythonで実装する。このとき、どのようにしてQMLとPythonを連携するか。

 QMLの以下がイベント処理する箇所である。Connectという名前はPython側でユーザが任意に定義している。詳細は後述。

            onAccepted: Connect.talk(_talkText.text)

ソースコード

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, sys, numpy, pyopenjtalk
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication
from PySide2.QtQuick import QQuickView
from PySide2.QtCore import QUrl, QObject, Slot
import simpleaudio as sa

class Connect(QObject):
    def __init__(self, parent=None): super().__init__(parent)
    @Slot(str)
    def talk(self, text):
        x, sr = pyopenjtalk.tts(text)
        ply = sa.play_buffer(x.astype(numpy.int16), 1, 2, sr)
        ply.wait_done()

def Main():
    app = QApplication(sys.argv)
    connect = Connect()
    engine = QQmlApplicationEngine()
    ctx = engine.rootContext()
    ctx.setContextProperty("Connect", connect)
    HERE = os.path.dirname(os.path.abspath(__file__))
    UI = os.path.join(HERE, 'talker.qml')
    engine.load(UI)
    if not engine.rootObjects(): sys.exit(-1)
    sys.exit(app.exec_())

if __name__ == '__main__':
    Main()

 ポイントとなるイベント処理はQQmlApplicationEngineで設定する。以下でConnectという名前をQML内で使えるようにしている。

    ctx.setContextProperty("Connect", connect)

 Connectという名前に紐づけたオブジェクトconnectは、クラスConnectインスタンスである。

class Connect(QObject):
    def __init__(self, parent=None): super().__init__(parent)
    @Slot(str)
    def talk(self, text):
        ...

 PySide2.QtCore.Slotでデコレートしたtalkメソッドがある。これがQMLで呼び出されている。

 もう一度QMLをみてみる。テキストボックスの入力値をtalkメソッドの引数に渡している。つまり、テキストボックスに入力したテキストを発話する。

            onAccepted: Connect.talk(_talkText.text)

所感

 環境構築からここまで、非常に大変だった。

 おそらくOSが変わったらまた環境構築コードを書き直さねばならない。

対象環境

$ uname -a
Linux raspberrypi 5.4.83-v7l+ #1379 SMP Mon Dec 14 13:11:54 GMT 2020 armv7l GNU/Linux