From e9ae0595e324e2af5cc383f026fb78152f33c53e Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Mon, 19 Oct 2020 18:51:30 +0200 Subject: [PATCH] Voice info application --- PdfTool/pdftoolabstractapplication.cpp | 16 +++ PdfTool/pdftoolabstractapplication.h | 18 +++ PdfTool/pdftoolaudiobook.cpp | 182 +++++++++++++++++-------- PdfTool/pdftoolaudiobook.h | 22 ++- 4 files changed, 179 insertions(+), 59 deletions(-) diff --git a/PdfTool/pdftoolabstractapplication.cpp b/PdfTool/pdftoolabstractapplication.cpp index 7937831..b63fe1d 100644 --- a/PdfTool/pdftoolabstractapplication.cpp +++ b/PdfTool/pdftoolabstractapplication.cpp @@ -217,6 +217,14 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* parser->addOption(QCommandLineOption("text-show-struct-act-text", "Show actual text extracted from structure tree.")); parser->addOption(QCommandLineOption("text-show-phoneme", "Show phoneme extracted from structure tree.")); } + + if (optionFlags.testFlag(VoiceSelector)) + { + parser->addOption(QCommandLineOption("voice-name", "Choose voice name for text-to-speech engine.", "name")); + parser->addOption(QCommandLineOption("voice-gender", "Choose voice gender for text-to-speech engine.", "gender")); + parser->addOption(QCommandLineOption("voice-age", "Choose voice age for text-to-speech engine.", "age")); + parser->addOption(QCommandLineOption("voice-lang-code", "Choose voice language code for text-to-speech engine.", "code")); + } } PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser) const @@ -360,6 +368,14 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser options.textShowStructPhoneme = parser->isSet("text-show-phoneme"); } + if (optionFlags.testFlag(VoiceSelector)) + { + options.textVoiceName = parser->isSet("voice-name") ? parser->value("voice-name") : QString(); + options.textVoiceGender = parser->isSet("voice-gender") ? parser->value("voice-gender") : QString(); + options.textVoiceAge = parser->isSet("voice-age") ? parser->value("voice-age") : QString(); + options.textVoiceLangCode = parser->isSet("voice-lang-code") ? parser->value("voice-lang-code") : QString(); + } + return options; } diff --git a/PdfTool/pdftoolabstractapplication.h b/PdfTool/pdftoolabstractapplication.h index b98f478..c9cfe25 100644 --- a/PdfTool/pdftoolabstractapplication.h +++ b/PdfTool/pdftoolabstractapplication.h @@ -93,6 +93,22 @@ struct PDFToolOptions bool textShowStructActualText = false; bool textShowStructPhoneme = false; + // For option 'VoiceSelector' + QString textVoiceName; + QString textVoiceGender; + QString textVoiceAge; + QString textVoiceLangCode; + + // For option 'TextSpeech' + bool textSpeechMarkPageNumbers = false; + bool textSpeechSayPageNumbers = false; + bool textSpeechSayStructTitles = false; + bool textSpeechSayStructLanguage = false; + bool textSpeechSayStructAlternativeDescription = false; + bool textSpeechSayStructExpandedForm = false; + bool textSpeechSayStructActualText = false; + bool textSpeechSayStructPhoneme = false; + /// Returns page range. If page range is invalid, then \p errorMessage is empty. /// \param pageCount Page count /// \param[out] errorMessage Error message @@ -139,6 +155,8 @@ public: PageSelector = 0x0080, ///< Select page range (or all pages) TextAnalysis = 0x0100, ///< Text analysis options TextShow = 0x0200, ///< Text extract and show options + VoiceSelector = 0x0400, ///< Select voice from SAPI + TextSpeech = 0x0800, ///< Text speech options }; Q_DECLARE_FLAGS(Options, Option) diff --git a/PdfTool/pdftoolaudiobook.cpp b/PdfTool/pdftoolaudiobook.cpp index 7b5a965..2337011 100644 --- a/PdfTool/pdftoolaudiobook.cpp +++ b/PdfTool/pdftoolaudiobook.cpp @@ -28,6 +28,7 @@ namespace pdftool { static PDFToolAudioBook s_audioBookApplication; +static PDFToolAudioBookVoices s_audioBookVoicesApplication; PDFVoiceInfo::PDFVoiceInfo(std::map properties, ISpVoice* voice) : m_properties(qMove(properties)), @@ -51,11 +52,19 @@ QLocale PDFVoiceInfo::getLocale() const if (ok) { + // Language name int count = GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, NULL, 0); - std::vector localeString(count, wchar_t()); - GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, localeString.data(), int(localeString.size())); - QString isoCode = QString::fromWCharArray(localeString.data()); - return QLocale(isoCode); + std::vector buffer(count, wchar_t()); + GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, buffer.data(), int(buffer.size())); + QString languageCode = QString::fromWCharArray(buffer.data()); + + // Country name + count = GetLocaleInfoW(locale, LOCALE_SISO3166CTRYNAME, NULL, 0); + buffer.resize(count, wchar_t()); + GetLocaleInfoW(locale, LOCALE_SISO3166CTRYNAME, buffer.data(), int(buffer.size())); + QString countryCode = QString::fromWCharArray(buffer.data()); + + return QLocale(QString("%1_%2").arg(languageCode, countryCode)); } return QLocale(); @@ -72,50 +81,30 @@ QString PDFVoiceInfo::getStringValue(QString key) const return QString(); } -QString PDFToolAudioBook::getStandardString(StandardString standardString) const -{ - switch (standardString) - { - case Command: - return "audio-book"; - - case Name: - return PDFToolTranslationContext::tr("Audio book convertor"); - - case Description: - return PDFToolTranslationContext::tr("Convert your document to a simple audio book."); - - default: - Q_ASSERT(false); - break; - } - - return QString(); -} - -int PDFToolAudioBook::execute(const PDFToolOptions& options) -{ - if (!SUCCEEDED(::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY))) - { - return ErrorCOM; - } - - int returnCode = showVoiceList(options); - - ::CoUninitialize(); - - return returnCode; -} - -PDFToolAbstractApplication::Options PDFToolAudioBook::getOptionsFlags() const -{ - return ConsoleFormat | OpenDocument | PageSelector | TextAnalysis; -} - -int PDFToolAudioBook::fillVoices(const PDFToolOptions& options, PDFVoiceInfoList& list, bool fillVoicePointers) +int PDFToolAudioBookBase::fillVoices(const PDFToolOptions& options, PDFVoiceInfoList& list, bool fillVoicePointers) { int result = ExitSuccess; + QStringList voiceSelector; + if (!options.textVoiceName.isEmpty()) + { + voiceSelector << QString("Name=%1").arg(options.textVoiceName); + } + if (!options.textVoiceGender.isEmpty()) + { + voiceSelector << QString("Gender=%1").arg(options.textVoiceGender); + } + if (!options.textVoiceAge.isEmpty()) + { + voiceSelector << QString("Age=%1").arg(options.textVoiceAge); + } + if (!options.textVoiceLangCode.isEmpty()) + { + voiceSelector << QString("Language=%1").arg(options.textVoiceLangCode); + } + 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))) { @@ -130,7 +119,7 @@ int PDFToolAudioBook::fillVoices(const PDFToolOptions& options, PDFVoiceInfoList } IEnumSpObjectTokens* enumTokensObject = nullptr; - if (SUCCEEDED(category->EnumTokens(NULL, NULL, &enumTokensObject))) + if (SUCCEEDED(category->EnumTokens(requiredAttributes, NULL, &enumTokensObject))) { ISpObjectToken* token = nullptr; while (SUCCEEDED(enumTokensObject->Next(1, &token, NULL))) @@ -212,7 +201,7 @@ int PDFToolAudioBook::fillVoices(const PDFToolOptions& options, PDFVoiceInfoList return result; } -int PDFToolAudioBook::showVoiceList(const PDFToolOptions& options) +int PDFToolAudioBookBase::showVoiceList(const PDFToolOptions& options) { PDFVoiceInfoList voices; int result = fillVoices(options, voices, false); @@ -227,22 +216,27 @@ int PDFToolAudioBook::showVoiceList(const PDFToolOptions& options) formatter.writeTableHeaderColumn("name", PDFToolTranslationContext::tr("Name"), Qt::AlignLeft); formatter.writeTableHeaderColumn("gender", PDFToolTranslationContext::tr("Gender"), Qt::AlignLeft); formatter.writeTableHeaderColumn("age", PDFToolTranslationContext::tr("Age"), Qt::AlignLeft); - formatter.writeTableHeaderColumn("language", PDFToolTranslationContext::tr("Language"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("language-code", PDFToolTranslationContext::tr("Lang. Code"), Qt::AlignLeft); formatter.writeTableHeaderColumn("locale", PDFToolTranslationContext::tr("Locale"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("language", PDFToolTranslationContext::tr("Language"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("country", PDFToolTranslationContext::tr("Country"), Qt::AlignLeft); formatter.writeTableHeaderColumn("vendor", PDFToolTranslationContext::tr("Vendor"), Qt::AlignLeft); formatter.writeTableHeaderColumn("version", PDFToolTranslationContext::tr("Version"), Qt::AlignLeft); formatter.endTableHeaderRow(); for (const PDFVoiceInfo& voice : voices) { + QLocale locale = voice.getLocale(); formatter.beginTableRow("voice"); - formatter.writeTableHeaderColumn("name", voice.getName(), Qt::AlignLeft); - formatter.writeTableHeaderColumn("gender", voice.getGender(), Qt::AlignLeft); - formatter.writeTableHeaderColumn("age", voice.getAge(), Qt::AlignLeft); - formatter.writeTableHeaderColumn("language", voice.getLanguage(), Qt::AlignLeft); - formatter.writeTableHeaderColumn("locale", voice.getLocale().name(), Qt::AlignLeft); - formatter.writeTableHeaderColumn("vendor", voice.getVendor(), Qt::AlignLeft); - formatter.writeTableHeaderColumn("version", voice.getVersion(), Qt::AlignLeft); + formatter.writeTableColumn("name", voice.getName(), Qt::AlignLeft); + formatter.writeTableColumn("gender", voice.getGender(), Qt::AlignLeft); + formatter.writeTableColumn("age", voice.getAge(), Qt::AlignLeft); + formatter.writeTableColumn("language", voice.getLanguage(), Qt::AlignLeft); + formatter.writeTableColumn("locale", locale.name(), Qt::AlignLeft); + formatter.writeTableColumn("language", locale.nativeLanguageName(), Qt::AlignLeft); + formatter.writeTableColumn("country", locale.nativeCountryName(), Qt::AlignLeft); + formatter.writeTableColumn("vendor", voice.getVendor(), Qt::AlignLeft); + formatter.writeTableColumn("version", voice.getVersion(), Qt::AlignLeft); formatter.endTableRow(); } @@ -254,6 +248,84 @@ int PDFToolAudioBook::showVoiceList(const PDFToolOptions& options) return result; } +QString PDFToolAudioBookVoices::getStandardString(PDFToolAbstractApplication::StandardString standardString) const +{ + switch (standardString) + { + case Command: + return "audio-book-voices"; + + case Name: + return PDFToolTranslationContext::tr("Audio book voices"); + + case Description: + return PDFToolTranslationContext::tr("List of available voices for audio book conversion."); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +int PDFToolAudioBookVoices::execute(const PDFToolOptions& options) +{ + if (!SUCCEEDED(::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY))) + { + return ErrorCOM; + } + + int returnCode = showVoiceList(options); + + ::CoUninitialize(); + + return returnCode; +} + +PDFToolAbstractApplication::Options PDFToolAudioBookVoices::getOptionsFlags() const +{ + return ConsoleFormat | VoiceSelector; +} + +QString PDFToolAudioBook::getStandardString(StandardString standardString) const +{ + switch (standardString) + { + case Command: + return "audio-book"; + + case Name: + return PDFToolTranslationContext::tr("Audio book convertor"); + + case Description: + return PDFToolTranslationContext::tr("Convert your document to a simple audio book."); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +int PDFToolAudioBook::execute(const PDFToolOptions& options) +{ + if (!SUCCEEDED(::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY))) + { + return ErrorCOM; + } + + ::CoUninitialize(); + + return ExitSuccess; +} + +PDFToolAbstractApplication::Options PDFToolAudioBook::getOptionsFlags() const +{ + return ConsoleFormat | OpenDocument | PageSelector | VoiceSelector | TextAnalysis; +} + } // namespace pdftool #endif diff --git a/PdfTool/pdftoolaudiobook.h b/PdfTool/pdftoolaudiobook.h index 5e5b336..332cb06 100644 --- a/PdfTool/pdftoolaudiobook.h +++ b/PdfTool/pdftoolaudiobook.h @@ -58,16 +58,30 @@ private: using PDFVoiceInfoList = std::vector; -class PDFToolAudioBook : public PDFToolAbstractApplication +class PDFToolAudioBookBase : public PDFToolAbstractApplication +{ +public: + PDFToolAudioBookBase() = default; + +protected: + int fillVoices(const PDFToolOptions& options, PDFVoiceInfoList& list, bool fillVoicePointers); + int showVoiceList(const PDFToolOptions& options); +}; + +class PDFToolAudioBookVoices : public PDFToolAudioBookBase { public: virtual QString getStandardString(StandardString standardString) const override; virtual int execute(const PDFToolOptions& options) override; virtual Options getOptionsFlags() const override; +}; -private: - int fillVoices(const PDFToolOptions& options, PDFVoiceInfoList& list, bool fillVoicePointers); - int showVoiceList(const PDFToolOptions& options); +class PDFToolAudioBook : public PDFToolAudioBookBase +{ +public: + virtual QString getStandardString(StandardString standardString) const override; + virtual int execute(const PDFToolOptions& options) override; + virtual Options getOptionsFlags() const override; }; } // namespace pdftool