Skip to content

Instantly share code, notes, and snippets.

@yunyu950908
Forked from breezewish/biaori-to-anki.js
Created January 9, 2020 18:04
Show Gist options
  • Save yunyu950908/59cfb290fdd00403b4a6305bf1a681bd to your computer and use it in GitHub Desktop.
Save yunyu950908/59cfb290fdd00403b4a6305bf1a681bd to your computer and use it in GitHub Desktop.
将新标准日语初级和中级的音频和单词导入到 anki https://ankiweb.net/shared/info/1939635284
const child_process = require('child_process');
const path = require('path');
const glob = require('glob');
const crypto = require('crypto');
const fs = require('fs-extra');
const _ = require('lodash');
const id3 = require('node-id3');
const sort = require('alphanum-sort');
const extractAudio = false;
function decryptDataV1(path, key) {
const encData = fs.readFileSync(path);
const iv = Buffer.alloc(128 / 8);
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
const data = [];
data.push(decipher.update(encData));
data.push(decipher.final());
return Buffer.concat(data).toString('utf8');
}
function pad(str, n = 2) {
return _.padStart(str, n, '0');
}
function processBook(name, cipherKey, bookId, basePath) {
let counter = 0;
const list = sort(glob.sync(path.join(basePath, '*/lesson*')));
const outputDir = `./output/${name}/mp3`;
const notes = [];
fs.ensureDirSync(outputDir);
list.forEach(p => {
const m = p.match(/unit(\d+)\/lesson(\d+)/);
if (!m) {
throw new Error('Path not matching');
}
const [ unit, lesson ] = m.slice(1);
const words = JSON.parse(decryptDataV1(path.join(p, 'words.dat'), cipherKey))
.content
.filter(word => word.linetype === '1');
words.forEach((word, idx) => {
const audioName = `book${pad(bookId)}-lesson${pad(lesson)}-${pad(idx)}.mp3`;
const audioPath = path.join(outputDir, audioName);
word.word = word.word.replace(/\<[^\>]+\>/g, '');
word.word = word.word.replace(//g, '(').replace(//g, ')');
console.log('unit %s, lesson %s, word %d / %d: %s', unit, lesson, idx, words.length, word.word);
const data = {
kana: word.word.trim().replace(/\([^\)]+\)/g, '').trim() || '/',
type: word.wordtype.replace(/[\[\]]/g, '').trim() || '/',
trans: word.trans.trim() || '/',
jp: word.word.trim() || '/',
lesson: pad(lesson),
id: pad(counter++, 4),
audio: audioName,
other: ' ',
};
notes.push(data);
if (extractAudio) {
child_process.spawnSync('ffmpeg', [
'-i',
path.join(p, 'lesson_words.pepm'),
'-codec:a',
'libmp3lame',
'-qscale:a', // reduce file size by setting a lower quality
'6',
'-ss',
word.starttime.trim(),
'-to',
word.endtime.trim(),
'-metadata',
`title="${word.word.trim()}"`,
'-id3v2_version',
'3',
'-write_id3v1',
'1',
'-y',
audioPath
], { shell: true });
}
});
});
fs.writeFileSync(`./output/${name}/data.txt`, notes
.map(data => [data.id, data.kana, data.type, data.trans, data.jp, data.lesson, `[sound:${data.audio}]`].join('\t'))
.join('\n')
);
}
processBook('新标准日本语初级', '@@www.pep.com.cn', 1, './resources/book1');
processBook('新标准日本语中级', '@@pepmres.com.cn', 2, './resources/book2');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment