From: Shaun Ren shaun.ren@linux.com
--- dlls/sapi/Makefile.in | 3 +- dlls/sapi/sapi_private.h | 4 ++ dlls/sapi/tests/tts.c | 7 +-- dlls/sapi/tts.c | 103 +++++++++++++++++++++++++++++---------- dlls/sapi/xml.c | 37 ++++++++++++++ 5 files changed, 123 insertions(+), 31 deletions(-) create mode 100644 dlls/sapi/xml.c
diff --git a/dlls/sapi/Makefile.in b/dlls/sapi/Makefile.in index a6c4d86037e..da91eca5b8c 100644 --- a/dlls/sapi/Makefile.in +++ b/dlls/sapi/Makefile.in @@ -14,4 +14,5 @@ SOURCES = \ sapi_typelib.idl \ stream.c \ token.c \ - tts.c + tts.c \ + xml.c diff --git a/dlls/sapi/sapi_private.h b/dlls/sapi/sapi_private.h index 219436f88c1..c4348180912 100644 --- a/dlls/sapi/sapi_private.h +++ b/dlls/sapi/sapi_private.h @@ -18,6 +18,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include "sapiddk.h" + #include "wine/list.h"
struct async_task @@ -63,3 +65,5 @@ enum type_id
HRESULT get_typeinfo( enum type_id tid, ITypeInfo **typeinfo ); void release_typelib( void ); + +HRESULT parse_sapi_xml( const WCHAR *contents, DWORD parse_flag, SPVTEXTFRAG **frag_list ); diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index a8f95dfcf82..b8a2178014a 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -987,9 +987,10 @@ static void test_spvoice_ssml(void)
/* XML and SSML autodetection when SPF_IS_XML is not specified. */ hr = ISpVoice_Speak(voice, text1, SPF_DEFAULT, NULL); - ok(hr == S_OK, "got %#lx.\n", hr); - ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); - todo_wine check_frag_text(0, L"text1"); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + todo_wine ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); + if (test_engine.frag_count == 1) + check_frag_text(0, L"text1");
reset_engine_params(&test_engine);
diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index 80f0298b51c..80ba145d47e 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -811,6 +811,18 @@ struct speak_task DWORD flags; };
+static void free_frag_list(SPVTEXTFRAG *frag) +{ + SPVTEXTFRAG *next; + + while (frag) + { + next = frag->pNext; + free(frag); + frag = next; + } +} + static HRESULT set_output_format(ISpStreamFormat *output, ISpTTSEngine *engine, GUID *fmtid, WAVEFORMATEX **wfx) { GUID output_fmtid; @@ -894,7 +906,7 @@ done: } CoTaskMemFree(wfx); ISpTTSEngine_Release(speak_task->engine); - free(speak_task->frag_list); + free_frag_list(speak_task->frag_list); ISpTTSEngineSite_Release(speak_task->site);
if (speak_task->result) @@ -911,23 +923,50 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR struct speech_voice *This = impl_from_ISpVoice(iface); ISpTTSEngineSite *site = NULL; ISpTTSEngine *engine = NULL; - SPVTEXTFRAG *frag; + SPVTEXTFRAG *frag_list; + BOOL async, purge; + DWORD parse_flag, nlp_flags; + BOOL xml; struct speak_task *speak_task = NULL; struct async_result *result = NULL; - size_t contents_len, contents_size; + size_t contents_len; ULONG stream_num; HRESULT hr;
TRACE("(%p, %p, %#lx, %p).\n", iface, contents, flags, stream_num_out);
- flags &= ~SPF_IS_NOT_XML; - if (flags & ~(SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_NLP_SPEAK_PUNC)) + async = flags & SPF_ASYNC; + purge = flags & SPF_PURGEBEFORESPEAK; + parse_flag = flags & SPF_PARSE_MASK; + nlp_flags = flags & SPF_NLP_MASK; + + xml = FALSE; + if ((flags & SPF_IS_XML) && (flags & SPF_IS_NOT_XML)) + return E_INVALIDARG; + else if (flags & SPF_IS_XML) + xml = TRUE; + else if (!(flags & SPF_IS_NOT_XML)) + { + if (contents) + { + const WCHAR *c = contents; + + while (*c && iswspace(*c)) c++; + xml = *c == L'<'; + } + } + + if (parse_flag == SPF_PARSE_MASK) + return E_INVALIDARG; + + flags &= ~(SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_XML | SPF_IS_NOT_XML | SPF_PARSE_MASK | SPF_NLP_MASK); + if (flags) { - FIXME("flags %#lx not implemented.\n", flags & ~(SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_NLP_SPEAK_PUNC)); + FIXME("flags %#lx not implemented.\n", flags); return E_NOTIMPL; }
- if (flags & SPF_PURGEBEFORESPEAK) + if (purge) { ISpAudio *audio;
@@ -954,9 +993,6 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR else if (!contents) return E_POINTER;
- contents_len = wcslen(contents); - contents_size = sizeof(WCHAR) * (contents_len + 1); - if (!This->output) { /* Create a new output stream with the default output. */ @@ -964,6 +1000,28 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR return hr; }
+ if (xml) + { + if (FAILED(hr = parse_sapi_xml(contents, parse_flag, &frag_list))) + return hr; + } + else + { + contents_len = wcslen(contents); + + if (!(frag_list = malloc(sizeof(*frag_list) + (contents_len + 1) * sizeof(WCHAR)))) + return E_OUTOFMEMORY; + + memcpy(frag_list + 1, contents, (contents_len + 1) * sizeof(WCHAR)); + + frag_list->pNext = NULL; + frag_list->State.eAction = SPVA_Speak; + frag_list->State.Volume = 100; + frag_list->pTextStart = (WCHAR *)(frag_list + 1); + frag_list->ulTextLen = contents_len; + frag_list->ulTextSrcOffset = 0; + } + EnterCriticalSection(&This->cs);
if (!This->engine_token) @@ -972,31 +1030,22 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR if (FAILED(hr = ISpVoice_SetVoice(iface, NULL))) { LeaveCriticalSection(&This->cs); - return hr; + goto fail; } } + if (!This->engine && FAILED(hr = ISpObjectToken_CreateInstance(This->engine_token, NULL, CLSCTX_ALL, &IID_ISpTTSEngine, (void **)&This->engine))) { LeaveCriticalSection(&This->cs); ERR("Failed to create engine: %#lx.\n", hr); - return hr; + goto fail; } engine = This->engine; ISpTTSEngine_AddRef(engine);
LeaveCriticalSection(&This->cs);
- if (!(frag = malloc(sizeof(*frag) + contents_size))) - return E_OUTOFMEMORY; - memset(frag, 0, sizeof(*frag)); - memcpy(frag + 1, contents, contents_size); - frag->State.eAction = SPVA_Speak; - frag->State.Volume = 100; - frag->pTextStart = (WCHAR *)(frag + 1); - frag->ulTextLen = contents_len; - frag->ulTextSrcOffset = 0; - stream_num = InterlockedIncrement(&This->cur_stream_num); if (FAILED(hr = ttsenginesite_create(This, stream_num, &site))) { @@ -1010,11 +1059,11 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR speak_task->result = NULL; speak_task->voice = This; speak_task->engine = engine; - speak_task->frag_list = frag; + speak_task->frag_list = frag_list; speak_task->site = site; - speak_task->flags = flags & SPF_NLP_SPEAK_PUNC; + speak_task->flags = nlp_flags;
- if (!(flags & SPF_ASYNC)) + if (!async) { if (!(result = malloc(sizeof(*result)))) { @@ -1035,7 +1084,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR if (stream_num_out) *stream_num_out = stream_num;
- if (flags & SPF_ASYNC) + if (async) return S_OK; else { @@ -1049,7 +1098,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR fail: if (site) ISpTTSEngineSite_Release(site); if (engine) ISpTTSEngine_Release(engine); - free(frag); + free_frag_list(frag_list); free(speak_task); if (result) { diff --git a/dlls/sapi/xml.c b/dlls/sapi/xml.c new file mode 100644 index 00000000000..eca8f9810bb --- /dev/null +++ b/dlls/sapi/xml.c @@ -0,0 +1,37 @@ +/* + * Speech API (SAPI) XML parser implementation. + * + * Copyright 2025 Shaun Ren for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define COBJMACROS + +#include "objbase.h" + +#include "sapiddk.h" + +#include "wine/debug.h" + +#include "sapi_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(sapi); + +HRESULT parse_sapi_xml(const WCHAR *contents, DWORD parse_flag, SPVTEXTFRAG **frag_list) +{ + FIXME("(%p, %#lx, %p): stub.\n", contents, parse_flag, frag_list); + return E_NOTIMPL; +}