AudioBook Plugin: create audio book stream

This commit is contained in:
Jakub Melka
2021-08-28 16:11:44 +02:00
parent 3318a2a6d7
commit ae4296776f
11 changed files with 320 additions and 2 deletions

View File

@ -33,10 +33,12 @@ DESTDIR = $$OUT_PWD/../../pdfplugins
CONFIG += c++11
SOURCES += \
audiobookcreator.cpp \
audiobookplugin.cpp \
audiotextstreameditordockwidget.cpp
HEADERS += \
audiobookcreator.h \
audiobookplugin.h \
audiotextstreameditordockwidget.h

View File

@ -0,0 +1,176 @@
// Copyright (C) 2021 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#include "audiobookcreator.h"
#ifdef Q_OS_WIN
#include <sapi.h>
#pragma comment(lib, "ole32")
#endif
namespace pdfplugin
{
AudioBookCreator::AudioBookCreator() :
m_initialized(false)
{
#ifdef Q_OS_WIN
auto comResult = ::CoInitialize(nullptr);
m_initialized = SUCCEEDED(comResult);
#endif
}
AudioBookCreator::~AudioBookCreator()
{
#ifdef Q_OS_WIN
if (m_initialized)
{
::CoUninitialize();
}
#endif
}
pdf::PDFOperationResult AudioBookCreator::createAudioBook(const Settings& settings, pdf::PDFDocumentTextFlow& flow)
{
#ifdef Q_OS_WIN
QString audioString;
QTextStream textStream(&audioString);
for (const pdf::PDFDocumentTextFlow::Item& item : flow.getItems())
{
QString trimmedText = item.text.trimmed();
if (!trimmedText.isEmpty())
{
textStream << trimmedText << endl;
}
}
auto getVoiceToken = [](const Settings& settings)
{
ISpObjectToken* token = nullptr;
QStringList voiceSelector;
if (!settings.voiceName.isEmpty())
{
voiceSelector << QString("Name=%1").arg(settings.voiceName);
}
if (!settings.voiceGender.isEmpty())
{
voiceSelector << QString("Gender=%1").arg(settings.voiceGender);
}
if (!settings.voiceAge.isEmpty())
{
voiceSelector << QString("Age=%1").arg(settings.voiceAge);
}
if (!settings.voiceLangCode.isEmpty())
{
voiceSelector << QString("Language=%1").arg(settings.voiceLangCode);
}
QString voiceSelectorString = voiceSelector.join(";");
LPCWSTR requiredAttributes = !voiceSelectorString.isEmpty() ? (LPCWSTR)voiceSelectorString.utf16() : nullptr;
ISpObjectTokenCategory* category = nullptr;
if (!SUCCEEDED(::CoCreateInstance(CLSID_SpObjectTokenCategory, NULL, CLSCTX_ALL, __uuidof(ISpObjectTokenCategory), (LPVOID*)&category)))
{
return token;
}
if (!SUCCEEDED(category->SetId(SPCAT_VOICES, FALSE)))
{
category->Release();
return token;
}
IEnumSpObjectTokens* enumTokensObject = nullptr;
if (SUCCEEDED(category->EnumTokens(requiredAttributes, NULL, &enumTokensObject)))
{
enumTokensObject->Next(1, &token, NULL);
}
if (enumTokensObject)
{
enumTokensObject->Release();
}
if (category)
{
category->Release();
}
return token;
};
ISpObjectToken* voiceToken = getVoiceToken(settings);
// Do we have any voice?
if (!voiceToken)
{
return tr("No suitable voice found.");
}
QString outputFile = settings.audioFileName;
BSTR outputFileName = (BSTR)outputFile.utf16();
ISpeechFileStream* stream = nullptr;
if (!SUCCEEDED(::CoCreateInstance(CLSID_SpFileStream, NULL, CLSCTX_ALL, __uuidof(ISpeechFileStream), (LPVOID*)&stream)))
{
voiceToken->Release();
return tr("Cannot create output stream '%1'.").arg(outputFile);
}
ISpVoice* voice = nullptr;
if (!SUCCEEDED(::CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, __uuidof(ISpVoice), (LPVOID*)&voice)))
{
voiceToken->Release();
stream->Release();
return tr("Cannot create voice.");
}
if (!SUCCEEDED(stream->Open(outputFileName, SSFMCreateForWrite)))
{
voiceToken->Release();
voice->Release();
stream->Release();
return tr("Cannot create output stream '%1'.").arg(outputFile);
}
if (!SUCCEEDED(voice->SetVoice(voiceToken)))
{
voiceToken->Release();
voice->Release();
stream->Release();
return tr("Failed to set requested voice.");
}
LPCWSTR stringToSpeak = (LPCWSTR)audioString.utf16();
voice->SetOutput(stream, FALSE);
voice->SetRate(settings.rate * 10.0);
voice->SetVolume(settings.volume * 100.0);
voice->Speak(stringToSpeak, SPF_PURGEBEFORESPEAK | SPF_PARSE_SAPI, NULL);
voice->Release();
stream->Release();
voiceToken->Release();
return true;
#else
return tr("Audio book plugin is unsupported on your system.");
#endif
}
} // namespace pdfplugin

View File

@ -0,0 +1,57 @@
// Copyright (C) 2021 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#ifndef AUDIOBOOKCREATOR_H
#define AUDIOBOOKCREATOR_H
#include "pdfdocumenttextflow.h"
#include <QtGlobal>
namespace pdfplugin
{
class AudioBookCreator
{
Q_DECLARE_TR_FUNCTIONS(pdfplugin::AudioBookCreator)
public:
AudioBookCreator();
~AudioBookCreator();
struct Settings
{
QString audioFileName;
QString voiceName;
QString voiceGender;
QString voiceAge;
QString voiceLangCode;
double rate = 0.0;
double volume = 1.0;
};
bool isInitialized() const { return m_initialized; }
pdf::PDFOperationResult createAudioBook(const Settings& settings, pdf::PDFDocumentTextFlow& flow);
private:
bool m_initialized;
};
} // namespace pdfplugin
#endif // AUDIOBOOKCREATOR_H

View File

@ -20,6 +20,7 @@
#include "pdfwidgettool.h"
#include "pdfutils.h"
#include "pdfwidgetutils.h"
#include "audiobookcreator.h"
#include <QAction>
#include <QPainter>
@ -27,6 +28,7 @@
#include <QMessageBox>
#include <QMouseEvent>
#include <QTableView>
#include <QFileDialog>
#include <QRegularExpression>
namespace pdfplugin
@ -117,7 +119,7 @@ void AudioBookPlugin::setWidget(pdf::PDFWidget* widget)
m_actionCreateAudioBook = new QAction(QIcon(":/pdfplugins/audiobook/create-audio-book.svg"), tr("Create Audio Book"), this);
m_actionCreateAudioBook->setObjectName("actionAudioBook_CreateAudioBook");
connect(m_actionRestoreOriginalText, &QAction::triggered, this, &AudioBookPlugin::onCreateAudioBook);
connect(m_actionCreateAudioBook, &QAction::triggered, this, &AudioBookPlugin::onCreateAudioBook);
m_actionClear = new QAction(QIcon(":/pdfplugins/audiobook/clear.svg"), tr("Clear Text Stream"), this);
m_actionClear->setObjectName("actionAudioBook_Clear");
@ -425,7 +427,35 @@ void AudioBookPlugin::onMoveSelectionDown()
void AudioBookPlugin::onCreateAudioBook()
{
pdf::IPluginDataExchange::VoiceSettings voiceSettings = m_dataExchangeInterface->getVoiceSettings();
QString fileName = QFileDialog::getSaveFileName(m_widget, tr("Select Audio File"), voiceSettings.directory, tr("Audio stream (*.mp3)"));
if (fileName.isEmpty())
{
return;
}
pdf::PDFOperationResult result = true;
AudioBookCreator audioBookCreator;
if (audioBookCreator.isInitialized())
{
AudioBookCreator::Settings settings;
settings.audioFileName = fileName;
settings.voiceName = voiceSettings.voiceName;
settings.rate = voiceSettings.rate;
settings.volume = voiceSettings.volume;
pdf::PDFDocumentTextFlow textFlow = m_textFlowEditor.createEditedTextFlow();
result = audioBookCreator.createAudioBook(settings, textFlow);
}
else
{
result = tr("Audio book creator cannot be initialized.");
}
if (!result)
{
QMessageBox::critical(m_widget, tr("Error"), result.getErrorMessage());
}
}
void AudioBookPlugin::onRectanglePicked(pdf::PDFInteger pageIndex, QRectF rectangle)