Implement getting and setting current voice

This commit is contained in:
Kovid Goyal 2020-10-22 13:35:09 +05:30
parent ee374f5d93
commit 61d44ea95e
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 89 additions and 7 deletions

View File

@ -13,11 +13,13 @@
#include <comdef.h>
static inline PyObject*
set_error_from_hresult(const char *file, const int line, const HRESULT hr, const char *prefix="") {
set_error_from_hresult(const char *file, const int line, const HRESULT hr, const char *prefix="", PyObject *name=NULL) {
_com_error err(hr);
LPCWSTR msg = err.ErrorMessage();
PyObject *pmsg = PyUnicode_FromWideChar(msg, -1);
PyObject *ans = PyErr_Format(PyExc_OSError, "%s:%d:%s:%V", file, line, prefix, pmsg, "Out of memory");
PyObject *ans;
if (name) ans = PyErr_Format(PyExc_OSError, "%s:%d:%s:%V: %S", file, line, prefix, pmsg, "Out of memory", name);
else ans = PyErr_Format(PyExc_OSError, "%s:%d:%s:%V", file, line, prefix, pmsg, "Out of memory");
Py_CLEAR(pmsg);
return ans;
}
@ -41,6 +43,7 @@ class wchar_raii {
wchar_t *ptr() { return handle; }
void set_ptr(wchar_t *val) { handle = val; }
explicit operator bool() const { return handle != NULL; }
};

View File

@ -20,7 +20,7 @@ extern CComModule _Module;
typedef struct {
PyObject_HEAD
ISpVoice *voice;
ISpVoice *voice;
} Voice;
@ -81,7 +81,36 @@ Voice_get_all_sound_outputs(Voice *self, PyObject *args) {
if (PyList_Append(ans.ptr(), dict.ptr()) != 0) return NULL;
}
return ans.detach();
return PyList_AsTuple(ans.ptr());
}
static PyObject*
Voice_get_current_voice(Voice *self, PyObject *args) {
HRESULT hr = S_OK;
CComPtr<ISpObjectToken> token = NULL;
if (FAILED(hr = self->voice->GetVoice(&token))) {
return error_from_hresult(hr, "Failed to get current voice");
}
com_wchar_raii id;
if (FAILED(hr = token->GetId(id.address()))) return error_from_hresult(hr, "Failed to get ID for current voice");
return PyUnicode_FromWideChar(id.ptr(), -1);
}
static PyObject*
Voice_set_current_voice(Voice *self, PyObject *args) {
wchar_raii id;
if (!PyArg_ParseTuple(args, "|O&", py_to_wchar, &id)) return NULL;
HRESULT hr = S_OK;
if (id) {
CComPtr<ISpObjectToken> token = NULL;
if (FAILED(hr = SpGetTokenFromId(id.ptr(), &token))) {
return error_from_hresult(hr, "Failed to find voice with id", PyTuple_GET_ITEM(args, 0));
}
if (FAILED(hr = self->voice->SetVoice(token))) return error_from_hresult(hr, "Failed to set voice to default");
} else {
if (FAILED(hr = self->voice->SetVoice(NULL))) return error_from_hresult(hr, "Failed to set voice to default");
}
Py_RETURN_NONE;
}
static PyObject*
@ -131,13 +160,15 @@ Voice_get_all_voices(Voice *self, PyObject *args) {
}
if (PyList_Append(ans.ptr(), dict.ptr()) != 0) return NULL;
}
return ans.detach();
return PyList_AsTuple(ans.ptr());
}
#define M(name, args) { #name, (PyCFunction)Voice_##name, args, ""}
static PyMethodDef Voice_methods[] = {
M(get_all_voices, METH_NOARGS),
M(get_current_voice, METH_NOARGS),
M(set_current_voice, METH_VARARGS),
M(get_all_sound_outputs, METH_NOARGS),
{NULL, NULL, 0, NULL}
};
@ -166,7 +197,7 @@ static struct PyModuleDef winsapi_module = {
extern "C" {
CALIBRE_MODINIT_FUNC PyInit_winsapi(void) {
VoiceType.tp_name = "winsapi.Voice";
VoiceType.tp_name = "winsapi.ISpVoice";
VoiceType.tp_doc = "Wrapper for ISpVoice";
VoiceType.tp_basicsize = sizeof(Voice);
VoiceType.tp_itemsize = 0;
@ -180,7 +211,7 @@ CALIBRE_MODINIT_FUNC PyInit_winsapi(void) {
if (m == NULL) return NULL;
Py_INCREF(&VoiceType);
if (PyModule_AddObject(m, "Voice", (PyObject *) &VoiceType) < 0) {
if (PyModule_AddObject(m, "ISpVoice", (PyObject *) &VoiceType) < 0) {
Py_DECREF(&VoiceType);
Py_DECREF(m);
return NULL;

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from calibre_extensions.winsapi import ISpVoice
def develop():
from pprint import pprint
spv = ISpVoice()
voices = spv.get_all_voices()
pprint(voices)
for voice in voices:
spv.set_current_voice(voice['id'])
def find_tests():
import unittest
class TestSAPI(unittest.TestCase):
def setUp(self):
self.sapi = ISpVoice()
def tearDown(self):
self.sapi = None
def test_enumeration_of_voices(self):
default_voice = self.sapi.get_current_voice()
self.assertTrue(default_voice)
all_voices = self.sapi.get_all_voices()
self.assertTrue(all_voices)
self.assertIn(default_voice, {x['id'] for x in all_voices})
for voice in all_voices:
for key in ('name', 'gender', 'age', 'language'):
self.assertIn(key, voice)
self.sapi.set_current_voice(voice['id'])
self.assertEqual(self.sapi.get_current_voice(), voice['id'])
self.sapi.set_current_voice()
self.assertEqual(self.sapi.get_current_voice(), default_voice)
return unittest.defaultTestLoader.loadTestsFromTestCase(TestSAPI)
def run_tests():
from calibre.utils.run_tests import run_tests
run_tests(find_tests)