diff --git a/PdfForQt.pro b/PdfForQt.pro index 00ee9ae..45ab93e 100644 --- a/PdfForQt.pro +++ b/PdfForQt.pro @@ -31,3 +31,4 @@ PdfForQtViewer.depends = PdfForQtLib JBIG2_Viewer.depends = PdfForQtLib PDFExampleGenerator.depends = PdfForQtLib CodeGenerator.depends = PdfForQtLib +PdfTool.depends = PdfForQtLib diff --git a/PdfTool/PdfTool.pro b/PdfTool/PdfTool.pro index 7213667..a78a7da 100644 --- a/PdfTool/PdfTool.pro +++ b/PdfTool/PdfTool.pro @@ -43,6 +43,7 @@ SOURCES += \ pdfoutputformatter.cpp \ pdftoolabstractapplication.cpp \ pdftoolattachments.cpp \ + pdftoolaudiobook.cpp \ pdftoolfetchtext.cpp \ pdftoolinfo.cpp \ pdftoolinfojavascript.cpp \ @@ -66,6 +67,7 @@ HEADERS += \ pdfoutputformatter.h \ pdftoolabstractapplication.h \ pdftoolattachments.h \ + pdftoolaudiobook.h \ pdftoolfetchtext.h \ pdftoolinfo.h \ pdftoolinfojavascript.h \ diff --git a/PdfTool/pdftoolabstractapplication.h b/PdfTool/pdftoolabstractapplication.h index 49b8f89..b98f478 100644 --- a/PdfTool/pdftoolabstractapplication.h +++ b/PdfTool/pdftoolabstractapplication.h @@ -115,7 +115,9 @@ public: ErrorDocumentReading, ErrorInvalidArguments, ErrorFailedWriteToFile, - ErrorPermissions + ErrorPermissions, + ErrorCOM, + ErrorSAPI }; enum StandardString diff --git a/PdfTool/pdftoolaudiobook.cpp b/PdfTool/pdftoolaudiobook.cpp new file mode 100644 index 0000000..7b5a965 --- /dev/null +++ b/PdfTool/pdftoolaudiobook.cpp @@ -0,0 +1,259 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt 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 +// (at your option) any later version. +// +// PdfForQt 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 PDFForQt. If not, see . + +#include "pdftoolaudiobook.h" + +#ifdef Q_OS_WIN + +#include +//#include + +#pragma comment(lib, "ole32") + +namespace pdftool +{ + +static PDFToolAudioBook s_audioBookApplication; + +PDFVoiceInfo::PDFVoiceInfo(std::map properties, ISpVoice* voice) : + m_properties(qMove(properties)), + m_voice(voice) +{ + +} + +PDFVoiceInfo::~PDFVoiceInfo() +{ + if (m_voice) + { + m_voice->Release(); + } +} + +QLocale PDFVoiceInfo::getLocale() const +{ + bool ok = false; + LCID locale = getLanguage().toInt(&ok, 16); + + if (ok) + { + 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); + } + + return QLocale(); +} + +QString PDFVoiceInfo::getStringValue(QString key) const +{ + auto it = m_properties.find(key); + if (it != m_properties.cend()) + { + return it->second; + } + + 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 result = ExitSuccess; + + ISpObjectTokenCategory* category = nullptr; + if (!SUCCEEDED(::CoCreateInstance(CLSID_SpObjectTokenCategory, NULL, CLSCTX_ALL, __uuidof(ISpObjectTokenCategory), (LPVOID*)&category))) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("SAPI Error: Cannot enumerate SAPI voices."), options.outputCodec); + return ErrorSAPI; + } + + if (!SUCCEEDED(category->SetId(SPCAT_VOICES, FALSE))) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("SAPI Error: Cannot enumerate SAPI voices."), options.outputCodec); + return ErrorSAPI; + } + + IEnumSpObjectTokens* enumTokensObject = nullptr; + if (SUCCEEDED(category->EnumTokens(NULL, NULL, &enumTokensObject))) + { + ISpObjectToken* token = nullptr; + while (SUCCEEDED(enumTokensObject->Next(1, &token, NULL))) + { + if (token) + { + /* Attributes can be for example: + * Version, + * Language, + * Gender, + * Age, + * Name + * Vendor */ + + std::map properties; + + ISpDataKey* attributes = nullptr; + if (SUCCEEDED(token->OpenKey(L"Attributes", &attributes))) + { + for (ULONG i = 0; ; ++i) + { + LPWSTR valueName = NULL; + if (SUCCEEDED(attributes->EnumValues(i, &valueName))) + { + LPWSTR data = NULL; + if (SUCCEEDED(attributes->GetStringValue(valueName, &data))) + { + QString propertyName = QString::fromWCharArray(valueName); + QString propertyValue = QString::fromWCharArray(data); + if (!propertyValue.isEmpty()) + { + properties[propertyName] = propertyValue; + } + ::CoTaskMemFree(data); + } + + ::CoTaskMemFree(valueName); + } + else + { + break; + } + } + attributes->Release(); + } + + ISpVoice* voice = nullptr; + if (fillVoicePointers) + { + token->QueryInterface(__uuidof(ISpVoice), (void**)&voice); + } + + list.emplace_back(qMove(properties), voice); + + token->Release(); + } + else + { + break; + } + } + } + else + { + PDFConsole::writeError(PDFToolTranslationContext::tr("SAPI Error: Cannot enumerate SAPI voices."), options.outputCodec); + result = ErrorSAPI; + } + + if (enumTokensObject) + { + enumTokensObject->Release(); + } + + if (category) + { + category->Release(); + } + + return result; +} + +int PDFToolAudioBook::showVoiceList(const PDFToolOptions& options) +{ + PDFVoiceInfoList voices; + int result = fillVoices(options, voices, false); + + PDFOutputFormatter formatter(options.outputStyle, options.outputCodec); + formatter.beginDocument("voices", PDFToolTranslationContext::tr("Available voices for given settings:")); + formatter.endl(); + + formatter.beginTable("voices", PDFToolTranslationContext::tr("Voice list")); + + formatter.beginTableHeaderRow("header"); + 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("locale", PDFToolTranslationContext::tr("Locale"), 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) + { + 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.endTableRow(); + } + + formatter.endTable(); + + formatter.endDocument(); + PDFConsole::writeText(formatter.getString(), options.outputCodec); + + return result; +} + +} // namespace pdftool + +#endif diff --git a/PdfTool/pdftoolaudiobook.h b/PdfTool/pdftoolaudiobook.h new file mode 100644 index 0000000..5e5b336 --- /dev/null +++ b/PdfTool/pdftoolaudiobook.h @@ -0,0 +1,77 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt 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 +// (at your option) any later version. +// +// PdfForQt 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 PDFForQt. If not, see . + +#ifndef PDFTOOLAUDIOBOOK_H +#define PDFTOOLAUDIOBOOK_H + +#include "pdftoolabstractapplication.h" + +#ifdef Q_OS_WIN + +struct ISpVoice; + +namespace pdftool +{ + +class PDFVoiceInfo +{ +public: + PDFVoiceInfo() = default; + PDFVoiceInfo(std::map properties, ISpVoice* voice); + + PDFVoiceInfo(const PDFVoiceInfo&) = delete; + PDFVoiceInfo(PDFVoiceInfo&&) = default; + PDFVoiceInfo& operator=(const PDFVoiceInfo&) = delete; + PDFVoiceInfo& operator=(PDFVoiceInfo&&) = default; + + ~PDFVoiceInfo(); + + QString getName() const { return getStringValue("Name"); } + QString getGender() const { return getStringValue("Gender"); } + QString getAge() const { return getStringValue("Age"); } + QString getVendor() const { return getStringValue("Vendor"); } + QString getLanguage() const { return getStringValue("Language"); } + QString getVersion() const { return getStringValue("Version"); } + QLocale getLocale() const; + + QString getStringValue(QString key) const; + ISpVoice* getVoice() const { return m_voice; } + +private: + std::map m_properties; + ISpVoice* m_voice = nullptr; +}; + +using PDFVoiceInfoList = std::vector; + +class PDFToolAudioBook : public PDFToolAbstractApplication +{ +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); +}; + +} // namespace pdftool + +#endif + +#endif // PDFTOOLAUDIOBOOK_H