From 74d016e9a9fad5e331e9cefa246c5d6779f134cf Mon Sep 17 00:00:00 2001 From: daijro Date: Mon, 4 Nov 2024 02:52:10 -0600 Subject: [PATCH] feat: Voice spoofing - Added `voices` parameter, which takes a list maps for each voice to add. Example: `[ {"isLocalService": true, "isDefault": true, "voiceUri": "Ting-Ting", "name": "Ting-Ting", "lang": "zh-CN" } ... ]` - Added `voices:blockIfNotDefined` has been added to block system voices - Added `voices:fakeCompletion: bool` and `voices:fakeCompletion:charsPerSecond: double` to set a fake playback speed. --- additions/camoucfg/MaskConfig.hpp | 26 +++++++++ patches/voice-spoofing.patch | 90 +++++++++++++++++++++++++++++++ settings/properties.json | 4 ++ 3 files changed, 120 insertions(+) create mode 100644 patches/voice-spoofing.patch diff --git a/additions/camoucfg/MaskConfig.hpp b/additions/camoucfg/MaskConfig.hpp index 0e012f0..1b61b65 100644 --- a/additions/camoucfg/MaskConfig.hpp +++ b/additions/camoucfg/MaskConfig.hpp @@ -283,4 +283,30 @@ inline std::optional> MShaderData( return std::nullopt; } +inline std::optional< + std::vector>> +MVoices() { + auto data = GetJson(); + if (!data.contains("voices") || !data["voices"].is_array()) { + return std::nullopt; + } + + std::vector> + voices; + for (const auto& voice : data["voices"]) { + // Check if voice has all required fields + if (!voice.contains("lang") || !voice.contains("name") || + !voice.contains("voiceUri") || !voice.contains("isDefault") || + !voice.contains("isLocalService")) { + continue; + } + + voices.emplace_back( + voice["lang"].get(), voice["name"].get(), + voice["voiceUri"].get(), voice["isDefault"].get(), + voice["isLocalService"].get()); + } + return voices; +} + } // namespace MaskConfig \ No newline at end of file diff --git a/patches/voice-spoofing.patch b/patches/voice-spoofing.patch new file mode 100644 index 0000000..755c069 --- /dev/null +++ b/patches/voice-spoofing.patch @@ -0,0 +1,90 @@ +diff --git a/dom/media/webspeech/synth/moz.build b/dom/media/webspeech/synth/moz.build +index 2cf19982b2..dcdcdb5cbf 100644 +--- a/dom/media/webspeech/synth/moz.build ++++ b/dom/media/webspeech/synth/moz.build +@@ -63,3 +63,6 @@ FINAL_LIBRARY = "xul" + LOCAL_INCLUDES += [ + "ipc", + ] ++ ++# DOM Mask ++LOCAL_INCLUDES += ['/camoucfg'] +\ No newline at end of file +diff --git a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp +index e5a1353d6b..4a4a5b080b 100644 +--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp ++++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp +@@ -27,6 +27,7 @@ + + #include "SpeechSynthesisChild.h" + #include "SpeechSynthesisParent.h" ++#include "MaskConfig.hpp" + + using mozilla::intl::LocaleService; + +@@ -169,6 +170,20 @@ nsSynthVoiceRegistry* nsSynthVoiceRegistry::GetInstance() { + // Start up all speech synth services. + NS_CreateServicesFromCategory(NS_SPEECH_SYNTH_STARTED, nullptr, + NS_SPEECH_SYNTH_STARTED); ++ // Load voices from MaskConfig ++ if (auto voices = MaskConfig::MVoices()) { ++ for (const auto& [lang, name, uri, isDefault, isLocal] : ++ voices.value()) { ++ gSynthVoiceRegistry->AddVoiceImpl( ++ nullptr, NS_ConvertUTF8toUTF16(uri), NS_ConvertUTF8toUTF16(name), ++ NS_ConvertUTF8toUTF16(lang), isLocal, ++ false); // queuesUtterances set to false ++ if (isDefault) { ++ gSynthVoiceRegistry->SetDefaultVoice(NS_ConvertUTF8toUTF16(uri), ++ true); ++ } ++ } ++ } + } + } + +@@ -305,6 +320,8 @@ nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService, + return NS_ERROR_NOT_AVAILABLE; + } + ++ if (MaskConfig::GetBool("voices:blockIfNotDefined")) return NS_OK; ++ + return AddVoiceImpl(aService, aUri, aName, aLang, aLocalService, + aQueuesUtterances); + } +@@ -779,6 +796,35 @@ void nsSynthVoiceRegistry::SpeakImpl(VoiceData* aVoice, nsSpeechTask* aTask, + NS_ConvertUTF16toUTF8(aText).get(), + NS_ConvertUTF16toUTF8(aVoice->mUri).get(), aRate, aPitch)); + ++ // Check if this voice is in our MVoices config ++ if (auto voices = MaskConfig::MVoices()) { ++ for (const auto& [lang, name, uri, isDefault, isLocal] : voices.value()) { ++ if (NS_ConvertUTF8toUTF16(uri).Equals(aVoice->mUri)) { ++ printf_stderr("Tried to speak a fake voice: %s", ++ NS_ConvertUTF16toUTF8(aVoice->mUri).get()); ++ aTask->Init(); ++ // If fake completion is disabled, throw an error ++ if (!MaskConfig::GetBool("voices:fakeCompletion")) { ++ aTask->DispatchError(0, 0); ++ return; ++ } ++ float charsPerSecond; ++ if (auto value = ++ MaskConfig::GetDouble("voices:fakeCompletion:charsPerSecond")) { ++ charsPerSecond = value.value(); ++ } else { ++ charsPerSecond = 12.5f; ++ } ++ // Return a fake success with a speach rate of 150wpm ++ aTask->DispatchStart(); ++ float fakeElapsedTime = ++ static_cast(aText.Length()) / (charsPerSecond * aRate); ++ aTask->DispatchEnd(fakeElapsedTime, aText.Length()); ++ return; ++ } ++ } ++ } ++ + aTask->Init(); + + if (NS_FAILED(aVoice->mService->Speak(aText, aVoice->mUri, aVolume, aRate, diff --git a/settings/properties.json b/settings/properties.json index 905f19e..a269a34 100644 --- a/settings/properties.json +++ b/settings/properties.json @@ -83,6 +83,10 @@ { "property": "webGl2:shaderPrecisionFormats:blockIfNotDefined", "type": "bool" }, { "property": "webGl:contextAttributes", "type": "dict" }, { "property": "webGl2:contextAttributes", "type": "dict" }, + { "property": "voices", "type": "array" }, + { "property": "voices:blockIfNotDefined", "type": "bool" }, + { "property": "voices:fakeCompletion", "type": "bool" }, + { "property": "voices:fakeCompletion:charsPerSecond", "type": "double" }, { "property": "memorysaver", "type": "bool" }, { "property": "debug", "type": "bool" } ] \ No newline at end of file