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;
static inline void handle_destructor(HANDLE p) { CloseHandle(p); }
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 {
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");
}
parts.erase(parts.begin(), parts.begin() + 2);
auto address = join(parts);
if (address.size() == 0) throw std::string("Address missing");
std::wstring address;
id_type shm_size = 0;
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);
}

View File

@ -3,11 +3,13 @@
import json
import struct
import sys
from contextlib import closing
from queue import Queue
from threading import Thread
from calibre.utils.shm import SharedMemory
from calibre.utils.ipc.simple_worker import start_pipe_worker
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())')
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()
print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True)
q = Queue()
@ -48,18 +79,20 @@ def develop_speech(text=SSML_SAMPLE):
Thread(name='Echo', target=echo_output, args=(p,), daemon=True).start()
exit_code = 0
with closing(p.stdin), closing(p.stdout):
text = text.replace('\n', ' ')
with closing(p.stdin), closing(p.stdout), SharedMemory(size=max_buffer_size(text)) as shm:
st = 'ssml' if '<speak' in text else 'text'
sz = encode_to_file_object(text, shm)
try:
send('1 echo Synthesizer started')
send('1 volume 0.1')
send(f'2 speak {st} inline', text)
send(f'2 speak {st} shm {sz} {shm.name}')
while True:
m = q.get()
if m['related_to'] != 2:
continue
if m['payload_type'] == 'media_state_changed' and m['state'] == 'ended':
break
if m['payload_type'] == 'error' and m['related_to'] == 2:
if m['payload_type'] == 'error':
exit_code = 1
break
send(f'3 echo Synthesizer exiting with exit code: {exit_code}')