summaryrefslogtreecommitdiff
path: root/ext/bg/js
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2020-03-10 19:20:34 -0400
committerGitHub <noreply@github.com>2020-03-10 19:20:34 -0400
commit36c55f0b17e7c2697543edc38e444d01da4f4a5c (patch)
treebeb7cb0baf66bf5d0f07919153c6586790345f33 /ext/bg/js
parent7541517d8084b50e054393f3b4fa6d4630ad012e (diff)
parent0cbf427ab50061b48c9027e63e9ee8a209946d37 (diff)
Merge pull request #401 from toasted-nutbread/audio-refactor
Audio refactor
Diffstat (limited to 'ext/bg/js')
-rw-r--r--ext/bg/js/audio-uri-builder.js (renamed from ext/bg/js/audio.js)148
-rw-r--r--ext/bg/js/backend.js57
-rw-r--r--ext/bg/js/settings/audio.js4
3 files changed, 110 insertions, 99 deletions
diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio-uri-builder.js
index c94121ae..15cea995 100644
--- a/ext/bg/js/audio.js
+++ b/ext/bg/js/audio-uri-builder.js
@@ -18,8 +18,49 @@
/*global jpIsStringEntirelyKana*/
-const audioUrlBuilders = new Map([
- ['jpod101', async (definition) => {
+class AudioUriBuilder {
+ constructor() {
+ this._getUrlHandlers = new Map([
+ ['jpod101', this._getUriJpod101.bind(this)],
+ ['jpod101-alternate', this._getUriJpod101Alternate.bind(this)],
+ ['jisho', this._getUriJisho.bind(this)],
+ ['text-to-speech', this._getUriTextToSpeech.bind(this)],
+ ['text-to-speech-reading', this._getUriTextToSpeechReading.bind(this)],
+ ['custom', this._getUriCustom.bind(this)]
+ ]);
+ }
+
+ normalizeUrl(url, baseUrl, basePath) {
+ if (url) {
+ if (url[0] === '/') {
+ if (url.length >= 2 && url[1] === '/') {
+ // Begins with "//"
+ url = baseUrl.substring(0, baseUrl.indexOf(':') + 1) + url;
+ } else {
+ // Begins with "/"
+ url = baseUrl + url;
+ }
+ } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) {
+ // No URI scheme => relative path
+ url = baseUrl + basePath + url;
+ }
+ }
+ return url;
+ }
+
+ async getUri(definition, source, options) {
+ const handler = this._getUrlHandlers.get(source);
+ if (typeof handler === 'function') {
+ try {
+ return await handler(definition, options);
+ } catch (e) {
+ // NOP
+ }
+ }
+ return null;
+ }
+
+ async _getUriJpod101(definition) {
let kana = definition.reading;
let kanji = definition.expression;
@@ -37,8 +78,9 @@ const audioUrlBuilders = new Map([
}
return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`;
- }],
- ['jpod101-alternate', async (definition) => {
+ }
+
+ async _getUriJpod101Alternate(definition) {
const response = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
@@ -54,7 +96,7 @@ const audioUrlBuilders = new Map([
const url = row.querySelector('audio>source[src]').getAttribute('src');
const reading = row.getElementsByClassName('dc-vocab_kana').item(0).textContent;
if (url && reading && (!definition.reading || definition.reading === reading)) {
- return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/');
+ return this.normalizeUrl(url, 'https://www.japanesepod101.com', '/learningcenter/reference/');
}
} catch (e) {
// NOP
@@ -62,8 +104,9 @@ const audioUrlBuilders = new Map([
}
throw new Error('Failed to find audio URL');
- }],
- ['jisho', async (definition) => {
+ }
+
+ async _getUriJisho(definition) {
const response = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', `https://jisho.org/search/${definition.expression}`);
@@ -78,7 +121,7 @@ const audioUrlBuilders = new Map([
if (audio !== null) {
const url = audio.getElementsByTagName('source').item(0).getAttribute('src');
if (url) {
- return audioUrlNormalize(url, 'https://jisho.org', '/search/');
+ return this.normalizeUrl(url, 'https://jisho.org', '/search/');
}
}
} catch (e) {
@@ -86,99 +129,28 @@ const audioUrlBuilders = new Map([
}
throw new Error('Failed to find audio URL');
- }],
- ['text-to-speech', async (definition, options) => {
+ }
+
+ async _getUriTextToSpeech(definition, options) {
const voiceURI = options.audio.textToSpeechVoice;
if (!voiceURI) {
throw new Error('No voice');
}
return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`;
- }],
- ['text-to-speech-reading', async (definition, options) => {
+ }
+
+ async _getUriTextToSpeechReading(definition, options) {
const voiceURI = options.audio.textToSpeechVoice;
if (!voiceURI) {
throw new Error('No voice');
}
return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`;
- }],
- ['custom', async (definition, options) => {
- const customSourceUrl = options.audio.customSourceUrl;
- return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0));
- }]
-]);
-
-async function audioGetUrl(definition, mode, options, download) {
- const handler = audioUrlBuilders.get(mode);
- if (typeof handler === 'function') {
- try {
- return await handler(definition, options, download);
- } catch (e) {
- // NOP
- }
}
- return null;
-}
-function audioUrlNormalize(url, baseUrl, basePath) {
- if (url) {
- if (url[0] === '/') {
- if (url.length >= 2 && url[1] === '/') {
- // Begins with "//"
- url = baseUrl.substring(0, baseUrl.indexOf(':') + 1) + url;
- } else {
- // Begins with "/"
- url = baseUrl + url;
- }
- } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) {
- // No URI scheme => relative path
- url = baseUrl + basePath + url;
- }
- }
- return url;
-}
-
-function audioBuildFilename(definition) {
- if (definition.reading || definition.expression) {
- let filename = 'yomichan';
- if (definition.reading) {
- filename += `_${definition.reading}`;
- }
- if (definition.expression) {
- filename += `_${definition.expression}`;
- }
-
- return filename += '.mp3';
- }
- return null;
-}
-
-async function audioInject(definition, fields, sources, optionsContext, audioSystem) {
- let usesAudio = false;
- for (const fieldValue of Object.values(fields)) {
- if (fieldValue.includes('{audio}')) {
- usesAudio = true;
- break;
- }
- }
-
- if (!usesAudio) {
- return true;
- }
-
- try {
- const expressions = definition.expressions;
- const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition;
-
- const {uri} = await audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext});
- const filename = audioBuildFilename(audioSourceDefinition);
- if (filename !== null) {
- definition.audio = {url: uri, filename};
- }
-
- return true;
- } catch (e) {
- return false;
+ async _getUriCustom(definition, options) {
+ const customSourceUrl = options.audio.customSourceUrl;
+ return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0));
}
}
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index d0d53a36..349fb4eb 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -21,9 +21,8 @@ conditionsTestValue, profileConditionsDescriptor
handlebarsRenderDynamic
requestText, requestJson, optionsLoad
dictConfigured, dictTermsSort, dictEnabledSet
-audioGetUrl, audioInject
jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana
-AnkiNoteBuilder, AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/
+AnkiNoteBuilder, AudioSystem, AudioUriBuilder, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/
class Backend {
constructor() {
@@ -36,6 +35,7 @@ class Backend {
this.optionsSchema = null;
this.defaultAnkiFieldTemplates = null;
this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)});
+ this.audioUriBuilder = new AudioUriBuilder();
this.optionsContext = {
depth: 0,
url: window.location.href
@@ -67,7 +67,7 @@ class Backend {
['noteView', this._onApiNoteView.bind(this)],
['templateRender', this._onApiTemplateRender.bind(this)],
['commandExec', this._onApiCommandExec.bind(this)],
- ['audioGetUrl', this._onApiAudioGetUrl.bind(this)],
+ ['audioGetUri', this._onApiAudioGetUri.bind(this)],
['screenshotGet', this._onApiScreenshotGet.bind(this)],
['forward', this._onApiForward.bind(this)],
['frameInformationGet', this._onApiFrameInformationGet.bind(this)],
@@ -434,12 +434,11 @@ class Backend {
const templates = this.defaultAnkiFieldTemplates;
if (mode !== 'kanji') {
- await audioInject(
+ await this._audioInject(
definition,
options.anki.terms.fields,
options.audio.sources,
- optionsContext,
- this.audioSystem
+ optionsContext
);
}
@@ -514,9 +513,9 @@ class Backend {
return this._runCommand(command, params);
}
- async _onApiAudioGetUrl({definition, source, optionsContext}) {
+ async _onApiAudioGetUri({definition, source, optionsContext}) {
const options = this.getOptions(optionsContext);
- return await audioGetUrl(definition, source, options);
+ return await this.audioUriBuilder.getUri(definition, source, options);
}
_onApiScreenshotGet({options}, sender) {
@@ -772,7 +771,36 @@ class Backend {
}
const options = this.getOptions(optionsContext);
- return await audioGetUrl(definition, source, options);
+ return await this.audioUriBuilder.getUri(definition, source, options);
+ }
+
+ async _audioInject(definition, fields, sources, optionsContext) {
+ let usesAudio = false;
+ for (const fieldValue of Object.values(fields)) {
+ if (fieldValue.includes('{audio}')) {
+ usesAudio = true;
+ break;
+ }
+ }
+
+ if (!usesAudio) {
+ return true;
+ }
+
+ try {
+ const expressions = definition.expressions;
+ const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition;
+
+ const {uri} = await this.audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext});
+ const filename = this._createInjectedAudioFileName(audioSourceDefinition);
+ if (filename !== null) {
+ definition.audio = {url: uri, filename};
+ }
+
+ return true;
+ } catch (e) {
+ return false;
+ }
}
async _injectScreenshot(definition, fields, screenshot) {
@@ -815,6 +843,17 @@ class Backend {
return handlebarsRenderDynamic(template, data);
}
+ _createInjectedAudioFileName(definition) {
+ const {reading, expression} = definition;
+ if (!reading && !expression) { return null; }
+
+ let filename = 'yomichan';
+ if (reading) { filename += `_${reading}`; }
+ if (expression) { filename += `_${expression}`; }
+ filename += '.mp3';
+ return filename;
+ }
+
static _getTabUrl(tab) {
return new Promise((resolve) => {
chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {
diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js
index 6f581d9b..c825be6b 100644
--- a/ext/bg/js/settings/audio.js
+++ b/ext/bg/js/settings/audio.js
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-/*global getOptionsContext, getOptionsMutable, settingsSaveOptions, apiAudioGetUrl
+/*global getOptionsContext, getOptionsMutable, settingsSaveOptions, apiAudioGetUri
AudioSystem, AudioSourceUI*/
let audioSourceUI = null;
@@ -26,7 +26,7 @@ async function audioSettingsInitialize() {
audioSystem = new AudioSystem({
getAudioUri: async (definition, source) => {
const optionsContext = getOptionsContext();
- return await apiAudioGetUrl(definition, source, optionsContext);
+ return await apiAudioGetUri(definition, source, optionsContext);
}
});