Allow passing speech text with shared memory

This commit is contained in:
Kovid Goyal 2023-01-25 10:12:55 +05:30
parent 83891ed63e
commit 623f999a54
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 58 additions and 8 deletions

View File

@ -62,6 +62,8 @@ static inline void co_task_mem_free(void* m) { CoTaskMemFree(m); }
typedef generic_raii<wchar_t*, co_task_mem_free, static_cast<wchar_t*>(NULL)> com_wchar_raii; typedef generic_raii<wchar_t*, co_task_mem_free, static_cast<wchar_t*>(NULL)> com_wchar_raii;
static inline void handle_destructor(HANDLE p) { CloseHandle(p); } static inline void handle_destructor(HANDLE p) { CloseHandle(p); }
typedef generic_raii<HANDLE, handle_destructor, INVALID_HANDLE_VALUE> handle_raii; typedef generic_raii<HANDLE, handle_destructor, INVALID_HANDLE_VALUE> handle_raii;
static inline void mapping_destructor(void *p) { UnmapViewOfFile(p); }
typedef generic_raii<void*, mapping_destructor, static_cast<void*>(NULL)> mapping_raii;
struct prop_variant : PROPVARIANT { struct prop_variant : PROPVARIANT {
prop_variant(VARTYPE vt=VT_EMPTY) noexcept : PROPVARIANT{} { PropVariantInit(this); this->vt = vt; } prop_variant(VARTYPE vt=VT_EMPTY) noexcept : PROPVARIANT{} { PropVariantInit(this); this->vt = vt; }

View File

@ -865,10 +865,25 @@ handle_speak(id_type cmd_id, std::vector<std::wstring_view> &parts) {
throw std::string("Not a well formed speak command"); throw std::string("Not a well formed speak command");
} }
parts.erase(parts.begin(), parts.begin() + 2); parts.erase(parts.begin(), parts.begin() + 2);
auto address = join(parts); std::wstring address;
if (address.size() == 0) throw std::string("Address missing"); id_type shm_size = 0;
if (is_shm) { if (is_shm) {
throw std::string("TODO: Implement support for SHM"); shm_size = parse_id(parts.at(0));
address = parts.at(1);
handle_raii handle(OpenFileMappingW(FILE_MAP_READ, false, address.data()));
if (handle.ptr() == INVALID_HANDLE_VALUE) {
output_error(cmd_id, "Could not open shared memory at", winrt::to_string(address), __LINE__);
return;
}
mapping_raii mapping(MapViewOfFile(handle.ptr(), FILE_MAP_READ, 0, 0, (SIZE_T)shm_size));
if (mapping.ptr() == NULL) {
output_error(cmd_id, "Could not map shared memory with error", std::to_string(GetLastError()), __LINE__);
return;
}
address = winrt::to_hstring((const char*)mapping.ptr());
} else {
address = join(parts);
if (address.size() == 0) throw std::string("Address missing");
} }
sx.speak(cmd_id, address, is_ssml); sx.speak(cmd_id, address, is_ssml);
} }

View File

@ -3,11 +3,13 @@
import json import json
import struct
import sys import sys
from contextlib import closing from contextlib import closing
from queue import Queue from queue import Queue
from threading import Thread from threading import Thread
from calibre.utils.shm import SharedMemory
from calibre.utils.ipc.simple_worker import start_pipe_worker from calibre.utils.ipc.simple_worker import start_pipe_worker
SSML_SAMPLE = ''' SSML_SAMPLE = '''
@ -30,7 +32,36 @@ def start_worker():
return start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; raise SystemExit(run_main_loop())') return start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; raise SystemExit(run_main_loop())')
def develop_speech(text=SSML_SAMPLE): def max_buffer_size(text) -> int:
if isinstance(text, str):
text = [text]
ans = 0
for x in text:
if isinstance(x, int):
ans += 5
else:
ans += 4 * len(x)
return ans
def encode_to_file_object(text, output) -> int:
if isinstance(text, str):
text = [text]
p = struct.pack
sz = 0
for x in text:
if isinstance(x, int):
output.write(b'\0')
output.write(p('=I', x))
sz += 5
else:
b = x.encode('utf-8')
output.write(b)
sz += len(b)
return sz
def develop_speech(text='Lucca Brazzi sleeps with the fishes.'):
p = start_worker() p = start_worker()
print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True)
q = Queue() q = Queue()
@ -48,18 +79,20 @@ def develop_speech(text=SSML_SAMPLE):
Thread(name='Echo', target=echo_output, args=(p,), daemon=True).start() Thread(name='Echo', target=echo_output, args=(p,), daemon=True).start()
exit_code = 0 exit_code = 0
with closing(p.stdin), closing(p.stdout): with closing(p.stdin), closing(p.stdout), SharedMemory(size=max_buffer_size(text)) as shm:
text = text.replace('\n', ' ')
st = 'ssml' if '<speak' in text else 'text' st = 'ssml' if '<speak' in text else 'text'
sz = encode_to_file_object(text, shm)
try: try:
send('1 echo Synthesizer started') send('1 echo Synthesizer started')
send('1 volume 0.1') send('1 volume 0.1')
send(f'2 speak {st} inline', text) send(f'2 speak {st} shm {sz} {shm.name}')
while True: while True:
m = q.get() m = q.get()
if m['related_to'] != 2:
continue
if m['payload_type'] == 'media_state_changed' and m['state'] == 'ended': if m['payload_type'] == 'media_state_changed' and m['state'] == 'ended':
break break
if m['payload_type'] == 'error' and m['related_to'] == 2: if m['payload_type'] == 'error':
exit_code = 1 exit_code = 1
break break
send(f'3 echo Synthesizer exiting with exit code: {exit_code}') send(f'3 echo Synthesizer exiting with exit code: {exit_code}')