Recently Written · git

subplz-web

git clone https://github.com/equwal/subplz-web

Log | Files | Refs


frontend/lab.html (4937 bytes)

1 <!DOCTYPE html>
2 <meta charset="utf-8">
3 <title>engine lab</title>
4 <body style="font:14px/1.5 ui-monospace,monospace;padding:20px;max-width:900px">
5 <h3>Browser engine lab</h3>
6 <p>Proves the client-side pieces one at a time. Not linked from the site.</p>
7 <input type="file" id="f"> <button id="go">run on /vendor/lab/ru.mp3</button>
8 <pre id="out"></pre>
9 <script type="module">
10 import { Media, quietestPoint } from '/engine/media.js';
11 import { Recogniser, hasWebGpu } from '/engine/asr.js';
12 
13 const out = document.getElementById('out');
14 const log = (...a) => { out.textContent += a.join(' ') + '\n'; };
15 window.lab = { done: false, error: null, results: {} };
16 
17 async function run(file) {
18   try {
19     log('crossOriginIsolated:', crossOriginIsolated, '| threads:', navigator.hardwareConcurrency,
20         '| webgpu:', await hasWebGpu());
21     let t = performance.now();
22     const media = await Media.open();
23     log(`ffmpeg loaded in ${((performance.now() - t) / 1000).toFixed(1)}s`);
24 
25     const path = await media.mount(file, '/in');
26     const info = await media.probe(path);
27     log('probe:', JSON.stringify(info));
28 
29     t = performance.now();
30     const pcm = await media.pcm(path, 0, 60);
31     log(`decoded ${(pcm.length / 16000).toFixed(1)}s of audio in ${((performance.now() - t) / 1000).toFixed(2)}s;`,
32         'cut at', (quietestPoint(pcm) / 16000).toFixed(2) + 's');
33 
34     t = performance.now();
35     const asr = await Recogniser.open((p) => { window.lab.results.download = `${p.file} ${Math.round(p.progress)}%`; });
36     log(`model ready on ${asr.device} in ${((performance.now() - t) / 1000).toFixed(1)}s`);
37 
38     const lang = new URLSearchParams(location.search).get('lang') || null;
39     t = performance.now();
40     await asr.transcribe(pcm.subarray(0, 16000 * 20), lang, 0);
41     log(`warm-up pass (shader compile) ${((performance.now() - t) / 1000).toFixed(1)}s, language=${lang}`);
42     t = performance.now();
43     const segments = await asr.transcribe(pcm, lang, 0);
44     const took = (performance.now() - t) / 1000;
45     log(`transcribed in ${took.toFixed(1)}s = ${(pcm.length / 16000 / took).toFixed(1)}x real time, ${segments.length} segments`);
46     for (const s of segments.slice(0, 8)) log(`  ${s.start.toFixed(2)}-${s.end.toFixed(2)}  ${s.text}`);
47     window.lab.results = { info, device: asr.device, took, segments };
48   } catch (e) {
49     log('FAILED:', e?.stack || e);
50     window.lab.error = String(e?.message || e);
51   }
52   window.lab.done = true;
53 }
54 
55 document.getElementById('go').onclick = async () => {
56   const blob = await (await fetch('/vendor/lab/ru.mp3')).blob();
57   run(new File([blob], 'ru.mp3'));
58 };
59 // The whole job: decode, transcribe, align, then both videos.
60 window.fullJob = async () => {
61   window.lab = { done: false, error: null, results: {} };
62   out.textContent = '';
63   try {
64     const { Job } = await import('/engine/job.js');
65     const { detectLanguage, readBook } = await import('/engine/book.js');
66     const get = async (n) => new File([await (await fetch('/vendor/lab/' + n)).blob()], n);
67     const audio = await get('ru.mp3'), book = await get('moskva.epub');
68     const parsed = await readBook(book);
69     const lang = detectLanguage(parsed.paragraphs);
70     log(`book: ${parsed.paragraphs.length} paragraphs, cover ${parsed.cover ? parsed.cover.name : 'none'}, detected language ${lang}`);
71     let last = '';
72     const job = new Job({ audio: [audio], book, language: lang, onStatus: (st) => {
73       const line = `${st.phase}: ${st.detail}`;
74       if (line !== last) { last = line; log('  ' + line, st.speed ? `(${st.speed.toFixed(1)}x)` : ''); }
75     }});
76     let t = performance.now();
77     const r = await job.run();
78     log(`job done in ${((performance.now() - t) / 1000).toFixed(1)}s on ${job.device}: ${r.cues.length} cues, ` +
79         `${(r.matchRate * 100).toFixed(0)}% matched, ${r.paragraphsDropped}/${r.paragraphsDropped + r.paragraphsUsed} paragraphs dropped`);
80     log(r.srt.split('\n').slice(0, 16).join('\n'));
81     t = performance.now();
82     const made = await job.epub();
83     log(`epub: ${made.file.name} ${(made.file.size / 1e6).toFixed(2)} MB, ${made.located} of ${made.of} lines placed, in ${((performance.now() - t) / 1000).toFixed(1)}s`);
84     window.lab.results.epub = made.file;
85     const again = await readBook(made.file);
86     log(`epub reads back: ${again.paragraphs.length} paragraphs, same text: ${JSON.stringify(again.paragraphs) === JSON.stringify(parsed.paragraphs)}`);
87     for (const kind of ['mkv', 'mp4']) {
88       t = performance.now();
89       const file = await job.video(kind);
90       log(`${kind}: ${file.name} ${(file.size / 1e6).toFixed(2)} MB in ${((performance.now() - t) / 1000).toFixed(1)}s`);
91       window.lab.results[kind] = file;
92     }
93     window.lab.results.srt = r.srt;
94   } catch (e) { log('FAILED:', e?.stack || e); window.lab.error = String(e?.message || e); }
95   window.lab.done = true;
96 };
97 
98 document.getElementById('f').onchange = (e) => run(e.target.files[0]);
99 </script>