diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2020-03-10 19:20:34 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-03-10 19:20:34 -0400 | 
| commit | 36c55f0b17e7c2697543edc38e444d01da4f4a5c (patch) | |
| tree | beb7cb0baf66bf5d0f07919153c6586790345f33 /ext/bg/js | |
| parent | 7541517d8084b50e054393f3b4fa6d4630ad012e (diff) | |
| parent | 0cbf427ab50061b48c9027e63e9ee8a209946d37 (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.js | 57 | ||||
| -rw-r--r-- | ext/bg/js/settings/audio.js | 4 | 
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);          }      }); |