@galeh/anki
Version:
A CLI tool that helps creating anki decks using a video file and its corresponding subtitle. For example, you give it an mkv file name and its subtitle (in the format of srt), it splits the video file based on the silent points of the video then creates a
2 lines • 17.9 kB
JavaScript
(()=>{"use strict";var e={596:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(314)),o=n(928),s=n(987);(0,i.default)((0,o.hideBin)(process.argv)).command("$0 <input>","Create anki decks using a video and its subtitle",(e=>{e.positional("input",{describe:"The input file path",type:"string"}).option("srt",{describe:"The SRT file(s)",type:"array",default:[]}).option("concurrent",{type:"number",alias:"c",default:1,description:"Maximum concurrent output files to be created"}).option("silence",{type:"number",alias:"s",default:20,description:"silence level which detects split points in the media, less silence causes more split points and more cards"}).option("silence-duration",{type:"number",default:.2,description:"minimum duration of silence (in seconds) that can be split point, the less silence-duration the more cards"}).option("play",{type:"boolean",default:!1,description:"only play the split parts and do not export anything"}).option("deck",{alias:"d",type:"string",description:"Anki deck name, default is the input file name"})}),(async({input:e,srt:t,concurrent:n,deck:r,silence:i,silenceDuration:o,play:a,cache:l})=>{await(0,s.main)(e,t,n,r,i,o,a).catch(console.error)})).help().argv},767:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.AnkiExporter=function(e,t){return new d(e,{template:(0,s.createTemplate)(t),sql:a.default})};const i=r(n(81)),o=r(n(172)),s=n(760),a=r(n(110)),l=r(n(896));class d{constructor(e,{template:t,sql:n}){this.db=new n.Database,this.db.run(t);const r=Date.now(),i=this._getId("cards","did",r),s=this._getId("notes","mid",r);this.deckName=e,this.zip=(0,o.default)("zip",{zlib:{level:9}}),this.media=[],this.topDeckId=i,this.topModelId=s,this.separator="";const a=this._getInitialRowValue("col","decks"),l=u(a);l.name=this.deckName,l.id=i,a[i+""]=l,this._update("update col set decks=:decks where id=1",{":decks":JSON.stringify(a)});const d=this._getInitialRowValue("col","models"),c=u(d);c.name=this.deckName,c.did=this.topDeckId,c.id=s,d[`${s}`]=c,this._update("update col set models=:models where id=1",{":models":JSON.stringify(d)})}save(e){return new Promise(((t,n)=>{const r=l.default.createWriteStream(e);this.zip.on("close",(function(){t()})),this.zip.on("error",(function(e){n(e)}));const i=this.db.export(),o=this.media.reduce(((e,t,n)=>(e[n]=t.filename,e)),{});this.zip.append(Buffer.from(i),{name:"collection.anki2"}),this.zip.append(JSON.stringify(o),{name:"media"}),this.media.forEach(((e,t)=>this.zip.file(e.filePath,{name:`${t}`}))),this.zip.pipe(r),this.zip.finalize()}))}addMedia(e,t){this.media.push({filename:e,filePath:t})}addCard(e,t,{tags:n}={tags:void 0}){const{topDeckId:r,topModelId:i,separator:o}=this,s=Date.now(),a=this._getNoteGuid(r,e,t),l=this._getNoteId(a,s);let d="";return"string"==typeof n?d=n:Array.isArray(n)&&(d=this._tagsToStr(n)),this._update("insert or replace into notes values(:id,:guid,:mid,:mod,:usn,:tags,:flds,:sfld,:csum,:flags,:data)",{":id":l,":guid":a,":mid":i,":mod":this._getId("notes","mod",s),":usn":-1,":tags":d,":flds":e+o+t,":sfld":e,":csum":this._checksum(e+o+t),":flags":0,":data":""}),this._update("insert or replace into cards values(:id,:nid,:did,:ord,:mod,:usn,:type,:queue,:due,:ivl,:factor,:reps,:lapses,:left,:odue,:odid,:flags,:data)",{":id":this._getCardId(l,s),":nid":l,":did":r,":ord":0,":mod":this._getId("cards","mod",s),":usn":-1,":type":0,":queue":0,":due":179,":ivl":0,":factor":0,":reps":0,":lapses":0,":left":0,":odue":0,":odid":0,":flags":0,":data":""})}_update(e,t){this.db.prepare(e).getAsObject(t)}_getInitialRowValue(e,t="id"){const n=`select ${t} from ${e}`;return this._getFirstVal(n)}_checksum(e){return parseInt((0,i.default)(e).substr(0,8),16)}_getFirstVal(e){return JSON.parse(this.db.exec(e)[0].values[0])}_tagsToStr(e=[]){return" "+e.map((e=>e.replace(/ /g,"_"))).join(" ")+" "}_getId(e,t,n){const r=`SELECT ${t} from ${e} WHERE ${t} >= :ts ORDER BY ${t} DESC LIMIT 1`,i=this.db.prepare(r).getAsObject({":ts":n});return i[t]?+i[t]+1:n}_getNoteId(e,t){return this.db.prepare("SELECT id from notes WHERE guid = :guid ORDER BY id DESC LIMIT 1").getAsObject({":guid":e}).id||this._getId("notes","id",t)}_getNoteGuid(e,t,n){return(0,i.default)(`${e}${t}${n}`)}_getCardId(e,t){return this.db.prepare("SELECT id from cards WHERE nid = :note_id ORDER BY id DESC LIMIT 1").getAsObject({":note_id":e}).id||this._getId("cards","id",t)}}function u(e){const t=Object.keys(e),n=t[t.length-1],r=e[n];return delete e[n],r}},736:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.createAnki=async function(e,t,n,r){const u=e||o.default.basename(t).split(".").shift(),c=o.default.resolve(t),p=function(e){const t=e.replace(/[\\\/:*?"<>|]/g,"");return/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(t)?t+"_":t}(u);console.log("Creating splitted files...");const{prefix:f,getFileName:m,getPrefixedFileName:g}=await(0,s.outputFileNameCalculate)(n.length);console.log(`Temporary directory: ${f}`),console.log();const h="[32m",_="[0m",y=new i.default(`${h}Splitting progress (:bar) :current/:total ${_}`,{complete:"█",incomplete:"░",clear:!0,total:n.length}),x=n.map(((e,t)=>{const n=m(t),r=g(t);return{job:(0,a.splitAudio)(c,e.startMargin,e.endMargin,r),card:{text:e.text,media:r,fileName:n}}})),v=x.map((e=>e.job)),E=x.map((e=>e.card)),T=new Map;return(0,l.lastValueFrom)((0,l.from)(v).pipe((0,l.mergeMap)((e=>e),r),(0,l.tap)({next:e=>{T.set(e,1);const t=Array.from(T.values()).reduce(((e,t)=>e+t),0);y.update(t/n.length)},error:e=>{console.error(e)},complete:()=>{y.terminate(),console.log(),function(e,t){const n=Math.random().toString().split(".")[1],r=`${e}-${n}`;return new Promise(((n,i)=>{const s=(0,d.AnkiExporter)(e);t.forEach((e=>{const t=`${r}-${e.fileName}`,n=e.text.reduce(((e,t,n)=>e+`<p ${n%2==0?'style="color: yellow"':""}>${t}</p>`),"");s.addMedia(t,e.media),s.addCard(`[sound:${t}]`,`${n}`)})),s.save(o.default.resolve(`${e}.apkg`)).then((()=>{n()})).catch((e=>i(e.stack||e)))}))}(p,E).then((()=>{console.log(`${h}${p} created!${_}`)})).catch((e=>{console.error("Error in creation of anki deck."),console.error(e)}))}})))};const i=r(n(671)),o=r(n(547)),s=n(277),a=n(15),l=n(573),d=n(767)},55:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.extractTextFromSubtitle=function(e,t){const n=t.map((e=>({start:(0,r.timeToSeconds)(e.startTime),end:(0,r.timeToSeconds)(e.endTime),text:e.text})));return e.map((({start:e,end:t,text:r,startMargin:i,endMargin:o})=>{const s=n.filter((n=>Math.max(e,n.start)<=Math.min(t,n.end))).reduce(((e,t)=>e+" "+t.text),"");return{startMargin:i,start:e,end:t,endMargin:o,text:r?[...r,s]:[s]}}))};const r=n(721)},318:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.getSplitTimes=function(e,t,n){return new Promise(((o,s)=>{const a=[];let l;const d=(0,r.spawn)("ffmpeg",["-i",e,"-af",`silencedetect=noise=-${t}dB:d=${n}`,"-f","null","-"]);d.stderr.on("data",(e=>{const t=e.toString(),n=/silence_start: ([0-9.]+)/,r=/silence_end: ([0-9.]+)/;l=t.match(/Duration: (\d{2}:\d{2}:\d{2}\.\d{2})/)||l,t.split("\n").forEach((e=>{const t=n.exec(e),i=r.exec(e);if(t&&a.push({start:parseFloat(t[1])}),i){const e=a[a.length-1];e&&(e.end=parseFloat(i[1]))}}))})),d.on("error",(e=>{s(`Error running ffmpeg: ${e.message}`)})),d.on("close",(e=>{if(0!==e)s(`ffmpeg exited with code ${e}`);else{const e=(0,i.timeToSeconds)(l[1]);o(function(e,t){if(!e||!e.length)return[];const n=e.map((({start:e,end:t})=>({midPoint:(e+t)/2,start:e,end:t}))),r=[];for(let e=0;e<n.length-1;e++)r.push({startMargin:n[e].midPoint,start:n[e].end,end:n[e+1].start,endMargin:n[e+1].midPoint,text:[]});return n[0].start>0&&r.unshift({startMargin:0,start:0,end:n[0].start,endMargin:n[0].midPoint,text:[]}),n[n.length-1].end<t&&r.push({startMargin:n[n.length-1].midPoint,start:n[n.length-1].end,end:t,endMargin:t,text:[]}),r}(a,e))}}))}))};const r=n(317),i=n(721)},818:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getSubtitleAndTimes=async function(e,t,n,r){const d=e.map((e=>i.default.resolve(e))),u=[];for(const e of d)u.push(await(0,o.getSubtitleBlocks)(e));const c=u.reduce(((e,t)=>(0,a.extractTextFromSubtitle)(e,t)),await(0,s.getSplitTimes)(t,n,r));return e.length>0?(0,l.reduceTime)(c):c};const i=r(n(547)),o=n(220),s=n(318),a=n(55),l=n(872)},220:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getSubtitleBlocks=function(e){return Promise.resolve().then(n.bind(n,964)).then((t=>{const n=new(0,t.default);return new Promise(((t,r)=>{i.default.readFile(e,"utf8",((e,i)=>{if(e)return void r("Error reading file:"+e);const o=n.fromSrt(i);t(o)}))}))}))};const i=r(n(896))},987:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.main=async function(e,t,n,r,l,d,u){const c=i.default.resolve(e);console.log("Analysing the video file...");const p=await(0,s.getSubtitleAndTimes)(t,c,l,d);u?(0,a.startServer)(p,c):await(0,o.createAnki)(r,e,p,n)};const i=r(n(547)),o=n(736),s=n(818),a=n(22)},277:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.outputFileNameCalculate=async function(e){const t=a(),n=(""+e).length;function r(e){return`${t=e+1,r=n,t.toString().padStart(r,"0")}.mp4`;var t,r}return{prefix:t,getFileName:r,getPrefixedFileName:e=>o.default.join(t,r(e))}},t.getPrefix=a;const i=r(n(896)),o=r(n(547)),s=r(n(857));function a(){const e=o.default.join(s.default.tmpdir(),Math.random().toString().split(".")[1]);return i.default.mkdirSync(e),e}},872:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.reduceTime=function(e){if(e.length<2)return e;const t=[],n=(r=e,function(e,t){return r[e].text[0]===r[t].text[0]});var r;let i=0;for(;i<e.length;){let r=i;for(r=i;r<e.length&&n(i,r);r++);r--,t.push({start:e[i].start,end:e[r].end,text:e[i].text,startMargin:e[i].startMargin,endMargin:e[r].endMargin}),i=r+1}return t}},15:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.splitAudio=function(e,t,n,o){const s=n-t;if(s<0)throw"duration can not be negative";return new r.Observable((n=>{const r=["-i",e,"-ss",`${t}`,"-t",`${s}`,"-c:v","libx264","-c:a","aac","-b:a","128k",o];(0,i.spawn)("ffmpeg",r).on("close",(e=>{0===e?(n.next(o),n.complete()):n.error(`ffmpeg exited with code ${e} \n command: ffmpeg ${r.join(" ")}`)}))}))};const r=n(573),i=n(317)},22:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.startServer=function(e,t){a.use(i.default.static(o.default.join(__dirname,"..","ui"))),a.get("/video",((e,n)=>{n.sendFile(o.default.join(t))})),a.get("/subtitle.json",((t,n)=>{n.json(e)})),a.listen(l,"0.0.0.0",(()=>{console.log(`server running on port http://${s.hostname}:${l}`)}))};const i=r(n(252)),o=r(n(547)),s=n(857),a=(0,i.default)(),l=8080},760:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.createTemplate=function({questionFormat:e="{{Front}}",answerFormat:t='{{FrontSide}}\n\n<hr id="answer">\n\n{{Back}}',css:n=".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\nbackground-color: white;\n}\n"}={}){const r={1388596687391:{veArs:[],name:"Basic-f15d2",tags:["Tag"],did:1435588830424,usn:-1,req:[[0,"all",[0]]],flds:[{name:"Front",media:[],sticky:!1,rtl:!1,ord:0,font:"Arial",size:20},{name:"Back",media:[],sticky:!1,rtl:!1,ord:1,font:"Arial",size:20}],sortf:0,latexPre:"\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage[utf8]{inputenc}\n\\usepackage{amssymb,amsmath}\n\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n\\begin{document}\n",tmpls:[{name:"Card 1",qfmt:e,did:null,bafmt:"",afmt:t,ord:0,bqfmt:""}],latexPost:"\\end{document}",type:0,id:1388596687391,css:n,mod:1435645658}};return`\n PRAGMA foreign_keys=OFF;\n BEGIN TRANSACTION;\n CREATE TABLE col (\n id integer primary key,\n crt integer not null,\n mod integer not null,\n scm integer not null,\n ver integer not null,\n dty integer not null,\n usn integer not null,\n ls integer not null,\n conf text not null,\n models text not null,\n decks text not null,\n dconf text not null,\n tags text not null\n );\n INSERT INTO "col" VALUES(\n 1,\n 1388548800,\n 1435645724219,\n 1435645724215,\n 11,\n 0,\n 0,\n 0,\n '${JSON.stringify({nextPos:1,estTimes:!0,activeDecks:[1],sortType:"noteFld",timeLim:0,sortBackwards:!1,addToCur:!0,curDeck:1,newBury:!0,newSpread:0,dueCounts:!0,curModel:"1435645724216",collapseTime:1200})}',\n '${JSON.stringify(r)}',\n '${JSON.stringify({1:{desc:"",name:"Default",extendRev:50,usn:0,collapsed:!1,newToday:[0,0],timeToday:[0,0],dyn:0,extendNew:10,conf:1,revToday:[0,0],lrnToday:[0,0],id:1,mod:1435645724},1435588830424:{desc:"",name:"Template",extendRev:50,usn:-1,collapsed:!1,newToday:[545,0],timeToday:[545,0],dyn:0,extendNew:10,conf:1,revToday:[545,0],lrnToday:[545,0],id:1435588830424,mod:1435588830}})}',\n '${JSON.stringify({1:{name:"Default",replayq:!0,lapse:{leechFails:8,minInt:1,delays:[10],leechAction:0,mult:0},rev:{perDay:100,fuzz:.05,ivlFct:1,maxIvl:36500,ease4:1.3,bury:!0,minSpace:1},timer:0,maxTaken:60,usn:0,new:{perDay:20,delays:[1,10],separate:!0,ints:[1,4,7],initialFactor:2500,bury:!0,order:1},mod:0,id:1,autoplay:!0}})}',\n '{}'\n );\n CREATE TABLE notes (\n id integer primary key, /* 0 */\n guid text not null, /* 1 */\n mid integer not null, /* 2 */\n mod integer not null, /* 3 */\n usn integer not null, /* 4 */\n tags text not null, /* 5 */\n flds text not null, /* 6 */\n sfld integer not null, /* 7 */\n csum integer not null, /* 8 */\n flags integer not null, /* 9 */\n data text not null /* 10 */\n );\n CREATE TABLE cards (\n id integer primary key, /* 0 */\n nid integer not null, /* 1 */\n did integer not null, /* 2 */\n ord integer not null, /* 3 */\n mod integer not null, /* 4 */\n usn integer not null, /* 5 */\n type integer not null, /* 6 */\n queue integer not null, /* 7 */\n due integer not null, /* 8 */\n ivl integer not null, /* 9 */\n factor integer not null, /* 10 */\n reps integer not null, /* 11 */\n lapses integer not null, /* 12 */\n left integer not null, /* 13 */\n odue integer not null, /* 14 */\n odid integer not null, /* 15 */\n flags integer not null, /* 16 */\n data text not null /* 17 */\n );\n CREATE TABLE revlog (\n id integer primary key,\n cid integer not null,\n usn integer not null,\n ease integer not null,\n ivl integer not null,\n lastIvl integer not null,\n factor integer not null,\n time integer not null,\n type integer not null\n );\n CREATE TABLE graves (\n usn integer not null,\n oid integer not null,\n type integer not null\n );\n ANALYZE sqlite_master;\n INSERT INTO "sqlite_stat1" VALUES('col',NULL,'1');\n CREATE INDEX ix_notes_usn on notes (usn);\n CREATE INDEX ix_cards_usn on cards (usn);\n CREATE INDEX ix_revlog_usn on revlog (usn);\n CREATE INDEX ix_cards_nid on cards (nid);\n CREATE INDEX ix_cards_sched on cards (did, queue, due);\n CREATE INDEX ix_revlog_cid on revlog (cid);\n CREATE INDEX ix_notes_csum on notes (csum);\n COMMIT;\n `}},721:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.timeToSeconds=function(e){const t=e.split(":"),n=t[2].includes(",")?t[2].split(","):t[2].split(".");return 3600*parseInt(t[0],10)+60*parseInt(t[1],10)+parseInt(n[0],10)+parseInt(n[1],10)/1e3}},172:e=>{e.exports=require("archiver")},252:e=>{e.exports=require("express")},671:e=>{e.exports=require("progress")},573:e=>{e.exports=require("rxjs")},81:e=>{e.exports=require("sha1")},110:e=>{e.exports=require("sql.js/js/sql-memory-growth")},314:e=>{e.exports=require("yargs")},928:e=>{e.exports=require("yargs/helpers")},964:e=>{e.exports=import("srt-parser-2")},317:e=>{e.exports=require("child_process")},896:e=>{e.exports=require("fs")},857:e=>{e.exports=require("os")},547:e=>{e.exports=require("path")}},t={};!function n(r){var i=t[r];if(void 0!==i)return i.exports;var o=t[r]={exports:{}};return e[r].call(o.exports,o,o.exports,n),o.exports}(596)})();