Merge pull request #427 from toasted-nutbread/anki-card-media-injection
Anki card media injection refactor
This commit is contained in:
commit
623469272e
@ -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;
|
||||||
|
@ -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) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user