Merge pull request #427 from toasted-nutbread/anki-card-media-injection

Anki card media injection refactor
This commit is contained in:
toasted-nutbread 2020-04-07 19:27:44 -04:00 committed by GitHub
commit 623469272e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 81 deletions

View File

@ -17,7 +17,8 @@
*/ */
class AnkiNoteBuilder { class AnkiNoteBuilder {
constructor({renderTemplate}) { constructor({audioSystem, renderTemplate}) {
this._audioSystem = audioSystem;
this._renderTemplate = renderTemplate; this._renderTemplate = renderTemplate;
} }
@ -84,6 +85,70 @@ class AnkiNoteBuilder {
}); });
} }
async injectAudio(definition, fields, sources, optionsContext) {
if (!this._containsMarker(fields, 'audio')) { return; }
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};
}
} catch (e) {
// NOP
}
}
async injectScreenshot(definition, fields, screenshot, anki) {
if (!this._containsMarker(fields, 'screenshot')) { return; }
const now = new Date(Date.now());
const filename = `yomichan_browser_screenshot_${definition.reading}_${this._dateToString(now)}.${screenshot.format}`;
const data = screenshot.dataUrl.replace(/^data:[\w\W]*?,/, '');
try {
await anki.storeMediaFile(filename, data);
} catch (e) {
return;
}
definition.screenshotFileName = filename;
}
_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;
}
_dateToString(date) {
const year = date.getUTCFullYear();
const month = date.getUTCMonth().toString().padStart(2, '0');
const day = date.getUTCDate().toString().padStart(2, '0');
const hours = date.getUTCHours().toString().padStart(2, '0');
const minutes = date.getUTCMinutes().toString().padStart(2, '0');
const seconds = date.getUTCSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`;
}
_containsMarker(fields, marker) {
marker = `{${marker}}`;
for (const fieldValue of Object.values(fields)) {
if (fieldValue.includes(marker)) {
return true;
}
}
return false;
}
static stringReplaceAsync(str, regex, replacer) { static stringReplaceAsync(str, regex, replacer) {
let match; let match;
let index = 0; let index = 0;

View File

@ -51,12 +51,16 @@ class Backend {
this.anki = new AnkiNull(); this.anki = new AnkiNull();
this.mecab = new Mecab(); this.mecab = new Mecab();
this.clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)}); this.clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)});
this.ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: this._renderTemplate.bind(this)});
this.options = null; this.options = null;
this.optionsSchema = null; this.optionsSchema = null;
this.defaultAnkiFieldTemplates = null; this.defaultAnkiFieldTemplates = null;
this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)});
this.audioUriBuilder = new AudioUriBuilder(); this.audioUriBuilder = new AudioUriBuilder();
this.ankiNoteBuilder = new AnkiNoteBuilder({
audioSystem: this.audioSystem,
renderTemplate: this._renderTemplate.bind(this)
});
this.optionsContext = { this.optionsContext = {
depth: 0, depth: 0,
url: window.location.href url: window.location.href
@ -460,7 +464,7 @@ class Backend {
const templates = this.defaultAnkiFieldTemplates; const templates = this.defaultAnkiFieldTemplates;
if (mode !== 'kanji') { if (mode !== 'kanji') {
await this._audioInject( await this.ankiNoteBuilder.injectAudio(
definition, definition,
options.anki.terms.fields, options.anki.terms.fields,
options.audio.sources, options.audio.sources,
@ -469,10 +473,11 @@ class Backend {
} }
if (details && details.screenshot) { if (details && details.screenshot) {
await this._injectScreenshot( await this.ankiNoteBuilder.injectScreenshot(
definition, definition,
options.anki.terms.fields, options.anki.terms.fields,
details.screenshot details.screenshot,
this.anki
); );
} }
@ -800,86 +805,10 @@ class Backend {
return await this.audioUriBuilder.getUri(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) {
let usesScreenshot = false;
for (const fieldValue of Object.values(fields)) {
if (fieldValue.includes('{screenshot}')) {
usesScreenshot = true;
break;
}
}
if (!usesScreenshot) {
return;
}
const dateToString = (date) => {
const year = date.getUTCFullYear();
const month = date.getUTCMonth().toString().padStart(2, '0');
const day = date.getUTCDate().toString().padStart(2, '0');
const hours = date.getUTCHours().toString().padStart(2, '0');
const minutes = date.getUTCMinutes().toString().padStart(2, '0');
const seconds = date.getUTCSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`;
};
const now = new Date(Date.now());
const filename = `yomichan_browser_screenshot_${definition.reading}_${dateToString(now)}.${screenshot.format}`;
const data = screenshot.dataUrl.replace(/^data:[\w\W]*?,/, '');
try {
await this.anki.storeMediaFile(filename, data);
} catch (e) {
return;
}
definition.screenshotFileName = filename;
}
async _renderTemplate(template, data) { async _renderTemplate(template, data) {
return handlebarsRenderDynamic(template, data); 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) { static _getTabUrl(tab) {
return new Promise((resolve) => { return new Promise((resolve) => {
chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => { chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {