diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6f66c74
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.zip
\ No newline at end of file
diff --git a/constants/constants.js b/constants/constants.js
index 6b1bd9e..1a68409 100644
--- a/constants/constants.js
+++ b/constants/constants.js
@@ -7,6 +7,7 @@ const CONTENT_COMMANDS = {
CHANGE_PLAYBACK_RATE: 'change_playback_rate',
GET_PLAYBACK_RATE: 'get_playback_rate',
UPDATE_VALUE: 'update_value',
+ PLAY_SSML: 'play_ssml',
};
const BACKGROUND_COMMANDS = {
@@ -29,6 +30,7 @@ const WEBRICE_KEYS = {
SUBSTITUTIONS: 'webrice_substitutions',
VOLUME: 'webrice_volume',
AWS_CREDS: 'webrice_aws_creds',
+ SSML_TEXT: 'webrice_ssml_text',
};
const BACKENDS = {
diff --git a/content/content.js b/content/content.js
index d3cbb5b..230c12e 100644
--- a/content/content.js
+++ b/content/content.js
@@ -19,6 +19,7 @@ const settings = {
pitch_default: true,
subs: [],
text: '',
+ ssml: '',
};
// Sends messages to the background script
@@ -60,15 +61,15 @@ const getText = () => {
* Start playing audio. If needed setup and requesting of new audio is done.
* @returns SUCCESS or an error message
*/
-const play = async () => {
- const text = getText();
+const play = async ({ ssml = false } = {}) => {
+ const text = ssml ? settings.ssml : getText();
if (player.sameTextAndVoice(text, settings.voice)) {
player.setPlaybackRate(settings.playbackRate);
player.play();
return 'SUCCESS';
}
- const result = getRequestHeaderAndContent(text, settings);
+ const result = getRequestHeaderAndContent(text, settings, ssml);
if (result.requests.length == 0) {
return 'Unable to formulate tts requests.';
@@ -168,6 +169,9 @@ const updateSetting = (setting, value) => {
settings.volume = value;
player.setVolume(value);
break;
+ case WEBRICE_KEYS.SSML_TEXT:
+ settings.ssml = value;
+ break;
default:
break;
}
@@ -202,6 +206,8 @@ const commandHandler = async (message) => {
const { setting, value } = message.settings;
updateSetting(setting, value);
break;
+ case CONTENT_COMMANDS.PLAY_SSML:
+ return await play({ ssml: false });
default:
console.log(`WebRice extension: Unknown command -> ${message.command}`);
break;
diff --git a/content/fetch.js b/content/fetch.js
index 82f7ef6..0a89d4c 100644
--- a/content/fetch.js
+++ b/content/fetch.js
@@ -53,14 +53,26 @@ const normalizeText = (text, specialTrim = false) => {
* Normalizes the text and outputs an array of tts requests
* @param {string} text text to normalize for tts
* @param {object} settings eventual settings that might be used to change voice
+ * @param {boolean} ssml default false, turns on SSML settings for the request
* @returns an array of requests, {url, content}
*/
-const getRequestHeaderAndContent = (text, settings) => {
+const getRequestHeaderAndContent = (text, settings, ssml = false) => {
const audioType = 'mp3';
const voiceName = settings?.voice ? settings.voice : DEFAULT_VOICE;
+ const awsVoice = AWS_VOICES.includes(settings?.voice);
+
+ if (ssml) {
+ const ssml = `${text}`;
+ const request = awsVoice
+ ? awsRequest(ssml, audioType, voiceName, true)
+ : tiroRequest(ssml, audioType, voiceName, true);
+ return {
+ backend: awsVoice ? BACKENDS.POLLY : BACKENDS.TIRO,
+ requests: [request],
+ };
+ }
const specialTrim = SPECIAL_VOICES.includes(voiceName);
const normalizedTexts = normalizeText(text, specialTrim);
- const awsVoice = AWS_VOICES.includes(settings?.voice);
const requests = normalizedTexts.map((text) => {
const request = awsVoice
@@ -72,7 +84,15 @@ const getRequestHeaderAndContent = (text, settings) => {
return { backend: awsVoice ? BACKENDS.POLLY : BACKENDS.TIRO, requests };
};
-const tiroRequest = (text, audioType, voiceName) => {
+/**
+ * Creates the Tiro tts request.
+ * @param {string} text The text that should be converted to TTS
+ * @param {string} audioType The wanted audio output type
+ * @param {string} voiceName The voice used for TTS
+ * @param {boolean} ssml If the request handles SSML or not
+ * @returns
+ */
+const tiroRequest = (text, audioType, voiceName, ssml = false) => {
const url = 'https://tts.tiro.is/v0/speech';
return {
@@ -92,13 +112,21 @@ const tiroRequest = (text, audioType, voiceName) => {
SampleRate: '16000',
SpeechMarkTypes: [],
Text: text,
- TextType: 'text',
+ TextType: ssml ? 'ssml' : 'text',
VoiceId: voiceName,
}),
},
};
};
-const awsRequest = (text, audioType, voiceName) => {
- return pollyParams(text, audioType, voiceName);
+/**
+ * Creates the AWS polly request.
+ * @param {string} text The text that should be converted to TTS
+ * @param {string} audioType The wanted audio output type
+ * @param {string} voiceName The voice used for TTS
+ * @param {boolean} ssml If the request handles SSML or not
+ * @returns
+ */
+const awsRequest = (text, audioType, voiceName, ssml = false) => {
+ return pollyParams(text, audioType, voiceName, ssml);
};
diff --git a/manifest.json b/manifest.json
index 3b55212..412b122 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "WebRICE",
- "version": "1.0.3",
+ "version": "1.0.4",
"description": "Text to speech service for icelandic",
"homepage_url": "https://www.webrice.is",
diff --git a/popup/popup.css b/popup/popup.css
index e3d4606..d5aef01 100644
--- a/popup/popup.css
+++ b/popup/popup.css
@@ -260,6 +260,11 @@ details > summary {
margin-bottom: 0.25rem;
}
+.small_margins {
+ margin-bottom: 0.25rem;
+ margin-top: 0.25rem;
+}
+
.verification_item {
border-radius: 1rem;
background: white;
diff --git a/popup/popup.html b/popup/popup.html
index 0c34c16..7ae1973 100644
--- a/popup/popup.html
+++ b/popup/popup.html
@@ -171,6 +171,41 @@
Hljóðstyrkur
/>
+
+
+ SSML
+
+
+ Speech Synthesis Markup Language (SSML) gerir kleift að sérsníða
+ Text-To-Speech.
+
+
Upplýsingar
+
+ Sjálfgefið bakendakerfi er veitt af Tiro og studd SSML merki og
+ dæmi má finna
+ hér.
+
+
+ Fyrir Amazon Polly raddir (Karl, Dora) eru viðbótar SSML merki
+ studd. Dæmi og studd merki má finna
+ hér.
+
+
+ Sláðu inn SSML í textareitinn fyrir neðan og smelltu á
+ spilunarhnappinn ofan til að prófa.
+
+
<speak>
+
+
</speak>
+
+
+
+
TTS bakgrunnskerfi
diff --git a/popup/popup.js b/popup/popup.js
index def8278..7c68396 100644
--- a/popup/popup.js
+++ b/popup/popup.js
@@ -28,6 +28,9 @@ const resetAWSCredsButton = document.getElementById('aws_reset_button');
const resetAWSCredsDiv = document.getElementById('aws_reset');
const doraRadio = document.getElementById('dora_aws');
const karlRadio = document.getElementById('karl_aws');
+const helpAWS = document.getElementById('help_aws');
+const SSMLText = document.getElementById('webrice_ssml_text');
+const SSMLdetails = document.getElementById('ssml_details');
loadingIcon.style.display = 'none'; // start by hiding loading icon
@@ -101,7 +104,12 @@ const toggleLoad = () => {
*/
const onPlayClicked = async () => {
toggleLoad();
- const result = await sendToContent('play clicked', CONTENT_COMMANDS.PLAY);
+ // If SSML section is open and the more container is visible => request TTS with SSML
+ const command =
+ SSMLdetails.open && !isHidden(moreContainer)
+ ? CONTENT_COMMANDS.PLAY_SSML
+ : CONTENT_COMMANDS.PLAY;
+ const result = await sendToContent('play clicked', command);
toggleLoad();
return result;
};
@@ -168,6 +176,11 @@ const onRadioClicked = (e) => {
updateValue(WEBRICE_KEYS.VOICE, voice);
};
+/**
+ * Updates a value both in storage and in content
+ * @param {string} key The key of the value to update
+ * @param {any} value The value that should be stored.
+ */
const updateValue = (key, value) => {
saveToStorage(key, value);
updateContentValue(key, value);
@@ -190,10 +203,22 @@ const onPitchDefaultChanged = (e) => {
pitchSliderDiv.classList.remove('webrice_disabled');
};
+/**
+ * Updates values when volume slider changes
+ * @param {event} e
+ */
const onVolumeSliderChanged = (e) => {
updateValue(WEBRICE_KEYS.VOLUME, e.target.valueAsNumber);
};
+/**
+ * Keeps the SSML values updated in storage and content
+ * @param {event} e
+ */
+const onSSMLTextChanged = (e) => {
+ updateValue(WEBRICE_KEYS.SSML_TEXT, e.target.value);
+};
+
/**HTMLElement
* On the AWS form submit update the stored values
* @param {Event} e
@@ -225,16 +250,27 @@ const onAWSFormSubmit = async (e) => {
saveToStorage(WEBRICE_KEYS.AWS_CREDS, awsCreds);
};
+/**
+ * Hides AWS credential input fields
+ */
const hideAWSCreds = () => {
hide(awsForm);
show(resetAWSCredsDiv);
};
+/**
+ * Displays the AWS Form and hides the rest button
+ */
const onResetAWS = () => {
show(awsForm);
hide(resetAWSCredsDiv);
};
+/**
+ * Used to initialize values from storage into the popup form
+ * @param {string} key
+ * @param {any} value
+ */
const initialize = (key, value) => {
switch (key) {
case WEBRICE_KEYS.PITCH:
@@ -271,11 +307,17 @@ const initialize = (key, value) => {
hideAWSCreds();
show(doraRadio);
show(karlRadio);
+ show(helpAWS);
break;
}
break;
case WEBRICE_KEYS.VOICE:
updateContentValue(key, value);
+ break;
+ case WEBRICE_KEYS.SSML_TEXT:
+ SSMLText.value = value;
+ updateContentValue(key, value);
+ break;
default:
break;
}
@@ -301,6 +343,7 @@ pitchSlider.onchange = onPitchSliderChanged;
volumeSlider.oninput = onVolumeSliderChanged;
awsForm.onsubmit = onAWSFormSubmit;
resetAWSCredsButton.onclick = onResetAWS;
+SSMLText.onkeyup = onSSMLTextChanged;
let initialVoice = await getFromStorage(WEBRICE_KEYS.VOICE);
if (!initialVoice) {
diff --git a/utils/aws-helper.js b/utils/aws-helper.js
index 12fc608..1f8a7e6 100644
--- a/utils/aws-helper.js
+++ b/utils/aws-helper.js
@@ -12,10 +12,11 @@ const testAws = async (region, accessKeyId, secretAccessKey) => {
}
};
-const pollyParams = (text, audioType, voice) => {
+const pollyParams = (text, audioType, voice, ssml = false) => {
params = {
Engine: 'standard',
Text: text,
+ TextType: ssml ? 'ssml' : 'text',
OutputFormat: audioType,
VoiceId: voice,
};