From d1b1fa7209288b9ec8707f4489b2980d6df2dc4d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 11 Jan 2023 20:53:48 +0530 Subject: [PATCH] Get listing all available voices working --- setup/extensions.json | 4 +- src/calibre/utils/windows/common.h | 36 ++++++++- src/calibre/utils/windows/winspeech.cpp | 101 +++++++++++++++++++++++- src/calibre/utils/windows/winutil.cpp | 11 --- 4 files changed, 137 insertions(+), 15 deletions(-) diff --git a/setup/extensions.json b/setup/extensions.json index c07af3580d..6161b71d3f 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -189,8 +189,8 @@ "only": "windows", "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", "sources": "calibre/utils/windows/winspeech.cpp", - "libraries": "shlwapi runtimeobject", - "cflags": "/X" + "libraries": "WindowsApp", + "cflags": "/X /std:c++17 /ZW /bigobj /await /permissive- /WX /Zc:twoPhase-" }, { "name": "wpd", diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index c732e1eb8c..76366f0921 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -24,8 +24,42 @@ set_error_from_hresult(PyObject *exc_type, const char *file, const int line, con } #define error_from_hresult(hr, ...) set_error_from_hresult(PyExc_OSError, __FILE__, __LINE__, hr, __VA_ARGS__) +class scoped_com_initializer { // {{{ + public: + scoped_com_initializer() : m_succeded(false), hr(0) { + hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) m_succeded = true; + } + ~scoped_com_initializer() { if (succeeded()) CoUninitialize(); } + + explicit operator bool() const noexcept { return m_succeded; } + + bool succeeded() const noexcept { return m_succeded; } + + PyObject* set_python_error() const noexcept { + if (hr == RPC_E_CHANGED_MODE) { + PyErr_SetString(PyExc_OSError, "COM initialization failed as it was already initialized in multi-threaded mode"); + } else { + _com_error err(hr); + PyObject *pmsg = PyUnicode_FromWideChar(err.ErrorMessage(), -1); + PyErr_Format(PyExc_OSError, "COM initialization failed: %V", pmsg, "Out of memory"); + } + return NULL; + } + + void detach() noexcept { m_succeded = false; } + + private: + bool m_succeded; + HRESULT hr; + scoped_com_initializer( const scoped_com_initializer & ) ; + scoped_com_initializer & operator=( const scoped_com_initializer & ) ; +}; // }}} + +#define INITIALIZE_COM_IN_FUNCTION scoped_com_initializer com; if (!com) return com.set_python_error(); + static inline void co_task_mem_free(void* m) { CoTaskMemFree(m); } -typedef generic_raii com_wchar_raii; +typedef generic_raii(NULL)> com_wchar_raii; static inline void handle_destructor(HANDLE p) { CloseHandle(p); } typedef generic_raii handle_raii; diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 6af9621e26..9e811e9c4a 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -4,11 +4,94 @@ * * Distributed under terms of the GPL3 license. */ - #include "common.h" +#include +#include +#include +#include +#include +#include + +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Media::SpeechSynthesis; +using namespace Windows::Storage::Streams; + +typedef struct { + PyObject_HEAD + SpeechSynthesizer ^synth; +} Synthesizer; + + +static PyTypeObject SynthesizerType = { + PyVarObject_HEAD_INIT(NULL, 0) +}; + +static PyObject * +Synthesizer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { INITIALIZE_COM_IN_FUNCTION + Synthesizer *self = (Synthesizer *) type->tp_alloc(type, 0); + if (self) { + self->synth = ref new SpeechSynthesizer(); + } + if (self && !PyErr_Occurred()) com.detach(); + return (PyObject*)self; +} + +static void +Synthesizer_dealloc(Synthesizer *self) { + self->synth = nullptr; + CoUninitialize(); +} + +static PyObject* +voice_as_dict(VoiceInformation ^voice) { + const char *gender = ""; + switch (voice->Gender) { + case VoiceGender::Male: gender = "male"; break; + case VoiceGender::Female: gender = "female"; break; + } + return Py_BuildValue("{su su su su ss}", + "display_name", voice->DisplayName? voice->DisplayName->Data() : NULL, + "description", voice->Description ? voice->Description->Data() : NULL, + "id", voice->Id ? voice->Id->Data(): NULL, + "language", voice->Language ? voice->Language->Data() : NULL, + "gender", gender + ); +} + +static PyObject* +all_voices(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION + IVectorView^ voices = SpeechSynthesizer::AllVoices; + pyobject_raii ans(PyTuple_New(voices->Size)); + if (!ans) return NULL; + Py_ssize_t i = 0; + for(auto voice : voices) { + PyObject *v = voice_as_dict(voice); + if (v) { + PyTuple_SET_ITEM(ans.ptr(), i++, v); + } else { + return NULL; + } + } + return ans.detach(); +} + +static PyObject* +default_voice(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION + return voice_as_dict(SpeechSynthesizer::DefaultVoice); +} + +#define M(name, args) { #name, (PyCFunction)Synthesizer_##name, args, ""} +static PyMethodDef Synthesizer_methods[] = { + {NULL, NULL, 0, NULL} +}; +#undef M + #define M(name, args) { #name, name, args, ""} static PyMethodDef methods[] = { + M(all_voices, METH_NOARGS), + M(default_voice, METH_NOARGS), {NULL, NULL, 0, NULL} }; #undef M @@ -16,6 +99,22 @@ static PyMethodDef methods[] = { static int exec_module(PyObject *m) { + SynthesizerType.tp_name = "winspeech.Synthesizer"; + SynthesizerType.tp_doc = "Wrapper for SpeechSynthesizer"; + SynthesizerType.tp_basicsize = sizeof(Synthesizer); + SynthesizerType.tp_itemsize = 0; + SynthesizerType.tp_flags = Py_TPFLAGS_DEFAULT; + SynthesizerType.tp_new = Synthesizer_new; + SynthesizerType.tp_methods = Synthesizer_methods; + SynthesizerType.tp_dealloc = (destructor)Synthesizer_dealloc; + if (PyType_Ready(&SynthesizerType) < 0) return -1; + + Py_INCREF(&SynthesizerType); + if (PyModule_AddObject(m, "Synthesizer", (PyObject *) &SynthesizerType) < 0) { + Py_DECREF(&SynthesizerType); + return -1; + } + return 0; } diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index f2a6608f6b..024ddf53f9 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -273,17 +273,6 @@ class DeleteFileProgressSink : public IFileOperationProgressSink { // {{{ ULONG m_cRef; }; // }}} -class scoped_com_initializer { // {{{ - public: - scoped_com_initializer() : m_succeded(false) { if (SUCCEEDED(CoInitialize(NULL))) m_succeded = true; } - ~scoped_com_initializer() { CoUninitialize(); } - bool succeeded() { return m_succeded; } - private: - bool m_succeded; - scoped_com_initializer( const scoped_com_initializer & ) ; - scoped_com_initializer & operator=( const scoped_com_initializer & ) ; -}; // }}} - static PyObject* get_computer_name(PyObject *self, PyObject *args) { COMPUTER_NAME_FORMAT fmt = ComputerNameDnsFullyQualified;