pythonaudiopyside2qtmultimediagtts

play gtts buffer with QtMultimedia.QAudioOutput directly


I want to play sounds with QtMultimedia.

In the case of QMediaPlayer, I could play mp3 file , it is made from gTTS.

(I think it is okay but I don't like the file remains unless I excute codes for deleting it.)

I make a mp3 file with gTTS module and I want to play sounds with the buffer directly.

It seems that I can make a valid object but QAudioOutput doesn't do anything.

I seriarize the mp3 file into a database and fetch it when I like.

What is short of my code?

Here is the excerpt of my original Code.

In my original code, the buffer data is in Qt.UserRole + x and I can take them whenever.

The playlist is constucted with QTableWidget and QTableWidgetItem.

from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtMultimedia
import os
import PySide2
import sys

dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path

import gtts

def main():
    if QtWidgets.QApplication.instance() is not None:
        app = QtWidgets.QApplication.instance()
    else:
        app = QtWidgets.QApplication([])
    widget = QtWidgets.QTableWidget()
    widget.setColumnCount(2)
    text = "hello"

    lang = "en"
    onsei = gtts.gTTS(text=text, lang=lang)
    buf = QtCore.QBuffer()


    buf.setOpenMode( QtCore.QIODevice.WriteOnly)      
    onsei.write_to_fp(buf)
    buf.close()


    if not widget.rowCount():
        widget.setRowCount(1)
    else:
        widget.insertRow(1)  
    nitem = QtWidgets.QTableWidgetItem()
    nitem.setText(str(widget.rowCount()))
    item = QtWidgets.QTableWidgetItem()

    item.setText("{0}_tts_lang_{1}.mp3".format(text, lang))
    item.setData(QtCore.Qt.UserRole+1, buf.data())

    variant = item.data(QtCore.Qt.UserRole+1)
    format = QtMultimedia.QAudioFormat()
    format.setSampleRate(8000)
    format.setChannelCount(1)
    format.setSampleSize(16)
    format.setCodec("audio/pcm")
    format.setByteOrder(QtMultimedia.QAudioFormat.LittleEndian)
    format.setSampleType(QtMultimedia.QAudioFormat.UnSignedInt)

    buf = QtCore.QBuffer()
    buf.setData(variant)        

    buf.open(QtCore.QIODevice.ReadOnly)
    buf.seek(0)

    audio = QtMultimedia.QAudioOutput(format, app)
    audio.setVolume(0.5)
    audio.setBufferSize(buf.size())

    audio.start(buf)
    buf.close()


    print(67)
#    sys.exit(QtWidgets.QApplication.exec_())
    sys.exit()
if __name__ == "__main__":
    main()

Solution

  • Instead of using QAudioOutput you could use QMediaPlayer:

    import sys
    import threading
    import uuid
    
    from PySide2 import QtCore, QtGui, QtWidgets, QtMultimedia
    
    import gtts
    
    IdentifierRole = QtCore.Qt.UserRole
    DataRole = QtCore.Qt.UserRole + 1
    DownLoadRole = QtCore.Qt.UserRole + 2
    ActiveRole = QtCore.Qt.UserRole + 3
    
    
    class BackgroundColorDelegate(QtWidgets.QStyledItemDelegate):
        def initStyleOption(self, option, index):
            super().initStyleOption(option, index)
            color = None
            if index.data(DownLoadRole):
                color = QtGui.QColor("green")
            if index.data(ActiveRole):
                color = QtGui.QColor("red")
            if color:
                option.backgroundBrush = color
    
    
    class DownLoader(QtCore.QObject):
        downloaded = QtCore.Signal(str, QtCore.QByteArray)
    
        def start(self, identifier, text, lang):
            threading.Thread(
                target=self._execute, args=(identifier, text, lang), daemon=True
            ).start()
    
        def _execute(self, identifier, text, lang):
            tts = gtts.gTTS(text=text, lang=lang)
            buf = QtCore.QBuffer()
            buf.open(QtCore.QBuffer.ReadWrite)
            tts.write_to_fp(buf)
            self.downloaded.emit(identifier, buf.data())
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.player = QtMultimedia.QMediaPlayer()
            self.current_buff = QtCore.QBuffer()
    
            self.tablewidget = QtWidgets.QTableWidget(
                0,
                2,
                selectionBehavior=QtWidgets.QAbstractItemView.SelectRows,
                editTriggers=QtWidgets.QAbstractItemView.NoEditTriggers,
            )
            delegate = BackgroundColorDelegate(self.tablewidget)
            self.tablewidget.setItemDelegateForColumn(0, delegate)
            self.tablewidget.itemClicked.connect(self.on_item_clicked)
            self.setCentralWidget(self.tablewidget)
    
            self.add_row("hello", "en")
            self.add_row("world", "en")
    
        def add_row(self, text, lang):
            it = QtWidgets.QTableWidgetItem("{0}_tts_lang_{1}.mp3".format(text, lang))
            identifier = str(uuid.uuid4())
            it.setData(IdentifierRole, identifier)
            downloader = DownLoader(self)
            downloader.start(identifier, text, lang)
            downloader.downloaded.connect(self.on_downloaded)
            downloader.downloaded.connect(downloader.deleteLater)
    
            row = self.tablewidget.rowCount()
            self.tablewidget.insertRow(row)
            self.tablewidget.setItem(row, 0, it)
    
        @QtCore.Slot(str, QtCore.QByteArray)
        def on_downloaded(self, identifier, data):
            model = self.tablewidget.model()
            indexes = model.match(
                model.index(0, 0), IdentifierRole, identifier, flags=QtCore.Qt.MatchExactly
            )
            if indexes:
                item = self.tablewidget.itemFromIndex(indexes[0])
                item.setData(DataRole, data)
                item.setData(DownLoadRole, True)
    
        @QtCore.Slot("QTableWidgetItem*")
        def on_item_clicked(self, item):
            self.player.stop()
            self.current_buff.close()
            data = item.data(DataRole)
            if not data:
                return
            self.current_buff.setData(data)
            self.current_buff.open(QtCore.QIODevice.ReadOnly)
            self.player.setMedia(QtMultimedia.QMediaContent(), self.current_buff)
            self.player.play()
    
            for row in range(self.tablewidget.rowCount()):
                it = self.tablewidget.item(row, 0)
                it.setData(ActiveRole, it is item)
    
    
    def main():
        app = QtWidgets.QApplication.instance()
        if app is None:
            app = QtWidgets.QApplication([])
    
        w = MainWindow()
        w.show()
    
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()