From 3eba739f623924517d191cb47632b2cc86e63761 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Oct 2020 15:09:30 +0530 Subject: [PATCH] Basic speech controls --- src/calibre/utils/windows/winsapi.cpp | 64 +++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/calibre/utils/windows/winsapi.cpp b/src/calibre/utils/windows/winsapi.cpp index af88fd99b2..af28313dec 100644 --- a/src/calibre/utils/windows/winsapi.cpp +++ b/src/calibre/utils/windows/winsapi.cpp @@ -18,6 +18,7 @@ extern CComModule _Module; #include #pragma warning( pop ) +// Structures {{{ typedef struct { PyObject_HEAD ISpVoice *voice; @@ -53,7 +54,7 @@ Voice_dealloc(Voice *self) { if (self->voice) { self->voice->Release(); self->voice = NULL; } CoUninitialize(); } - +// }}} // Enumeration {{{ static PyObject* @@ -197,7 +198,6 @@ Voice_get_all_voices(Voice *self, PyObject *args) { } // }}} - // Volume and rate {{{ static PyObject* Voice_get_current_volume(Voice *self, PyObject *args) { @@ -235,11 +235,52 @@ Voice_set_current_volume(Voice *self, PyObject *args) { } // }}} +static PyObject* +Voice_speak(Voice *self, PyObject *args) { + wchar_raii text_or_path; + unsigned long flags = SPF_DEFAULT; + if (!PyArg_ParseTuple(args, "O&|k", py_to_wchar, &text_or_path, &flags)) return NULL; + ULONG stream_number; + HRESULT hr = S_OK; + if (FAILED(hr = self->voice->Speak(text_or_path.ptr(), flags, &stream_number))) return error_from_hresult(hr, "Failed to speak", PyTuple_GET_ITEM(args, 0)); + return PyLong_FromLong(stream_number); +} + +static PyObject* +Voice_wait_until_done(Voice *self, PyObject *args) { + unsigned long timeout = INFINITE; + if (!PyArg_ParseTuple(args, "|k", &timeout)) return NULL; + HRESULT hr = self->voice->WaitUntilDone(timeout); + if (hr == S_OK) Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +static PyObject* +Voice_pause(Voice *self, PyObject *args) { + HRESULT hr = self->voice->Pause(); + if (FAILED(hr)) return error_from_hresult(hr); + Py_RETURN_NONE; +} + +static PyObject* +Voice_resume(Voice *self, PyObject *args) { + HRESULT hr = self->voice->Resume(); + if (FAILED(hr)) return error_from_hresult(hr); + Py_RETURN_NONE; +} + + +// Boilerplate {{{ #define M(name, args) { #name, (PyCFunction)Voice_##name, args, ""} static PyMethodDef Voice_methods[] = { M(get_all_voices, METH_NOARGS), M(get_all_sound_outputs, METH_NOARGS), + M(speak, METH_VARARGS), + M(wait_until_done, METH_VARARGS), + M(pause, METH_NOARGS), + M(resume, METH_NOARGS), + M(get_current_rate, METH_NOARGS), M(get_current_volume, METH_NOARGS), M(get_current_voice, METH_NOARGS), @@ -294,8 +335,25 @@ CALIBRE_MODINIT_FUNC PyInit_winsapi(void) { Py_DECREF(m); return NULL; } +#define AI(name) if (PyModule_AddIntMacro(m, name) != 0) { Py_DECREF(m); Py_DECREF(&VoiceType); return NULL; } + AI(SPF_DEFAULT); + AI(SPF_ASYNC); + AI(SPF_PURGEBEFORESPEAK); + AI(SPF_IS_FILENAME); + AI(SPF_IS_XML); + AI(SPF_IS_NOT_XML); + AI(SPF_PERSIST_XML); + AI(SPF_NLP_SPEAK_PUNC); + AI(SPF_PARSE_SSML); + AI(SPF_PARSE_AUTODETECT); + AI(SPF_NLP_MASK); + AI(SPF_PARSE_MASK); + AI(SPF_VOICE_MASK); + AI(SPF_UNUSED_FLAGS); + AI(INFINITE); +#undef AI return m; } -} +} // }}}