Skip to content

Commit 60090c8

Browse files
save file
1 parent eb94e18 commit 60090c8

File tree

1 file changed

+299
-2
lines changed

1 file changed

+299
-2
lines changed

utils/video/video-thumbnail-generator/video-thumbnail-generator.html

Lines changed: 299 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,27 @@
5454
var menu;
5555

5656
var hdr;
57+
var filemod;
58+
var log;
5759

5860

59-
function init(){
61+
async function init(){
6062

6163
menu = menumod();
6264

6365

64-
hdr = mod['editors-hdr'];
66+
hdr = mod['video-hdr'];
67+
filemod = mod['file-mod'];
68+
log = mod['log-mod'];
69+
6570
hdr.initmod({ext,$,menu});
71+
filemod.initmod({ext,$,menumod,menu,complete,source,focus,log});
72+
log.initmod({ext,$});
6673

6774
await Promise.all([
6875
hdr.init(),
76+
filemod.init(),
77+
log.init(),
6978
]);
7079

7180
}//init
@@ -95,6 +104,52 @@
95104

96105

97106
<style>
107+
108+
html
109+
{height:100%;font-family:arial}
110+
body
111+
{min-height:calc(100% - 40px);display:flex;flex-direction:column;gap:20px;margin:20px;align-items:center;
112+
padding-bottom:200px;
113+
}
114+
body>*
115+
{max-width:1400px;width:100%}
116+
117+
[component]
118+
{display:none}
119+
120+
.description
121+
{max-width:1000px;text-align:justify;border-left:4px solid #4a90e2;padding:1rem 2rem;
122+
background-color:#f9f9f9;font-family:system-ui,sans-serif;font-size:1rem;line-height:1.6;color:#333}
123+
.description>p
124+
{margin:0}
125+
.description > p+p
126+
{margin:10px 0}
127+
128+
code
129+
{font-family:monospace;background:whitesmoke}
130+
code.inline
131+
{display:inline;padding:5px 10px}
132+
133+
a
134+
{color:#4a90e2;text-decoration:none;font-weight:500}
135+
a:hover
136+
{text-decoration:underline}
137+
.link-domain
138+
{font-size:0.85rem;color:#777;margin-left:0.25rem}
139+
.link-domain::before
140+
{content:'['}
141+
.link-domain::after
142+
{content:']'}
143+
.link-txt
144+
{}
145+
146+
input
147+
{font-size:16px;padding:5px 7px;box-sizing:border-box;}
148+
input[type=button]
149+
{cursor:pointer}
150+
151+
.visually-hidden
152+
{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);border:0}
98153

99154
</style>
100155

@@ -103,12 +158,254 @@
103158
<body>
104159

105160
<video-hdr component=grp>
161+
<img class=title src='images/video-thumbnail-generator.png' style='top:10px;height:55px' alt='video thumbnail generator'>
162+
<h1 slot=seo-hdr class=visually-hidden>video thumbnail generator</h1>
163+
<time slot=date datetime=2025-11-28>28 Nov 2025</time>
106164
</video-hdr>
107165

166+
<div>
167+
<file-mod component></file-mod>
168+
</div>
169+
170+
<div>
171+
thumbnail type
172+
<span id=png>
173+
<input type=checkbox checked>
174+
png
175+
</span>
176+
<span id=jpg>
177+
<input type=checkbox>
178+
jpg
179+
</span>
180+
<span>
181+
size
182+
<span id=width>
183+
width
184+
<input value=50>
185+
</span>
186+
<span id=height>
187+
height
188+
<input value=-1>
189+
</span>
190+
</span>
191+
<input value=generate type=button>
192+
</div>
193+
194+
<div>
195+
<video controls></video>
196+
</div>
197+
<div>
198+
<img id=thumbnail>
199+
</div>
200+
201+
<pre id=output></pre>
202+
203+
<log-mod component></log-mod>
204+
108205
</body>
109206

110207
<script>
111208

209+
var _blob;
210+
var _uint8;
211+
212+
var ffmpeg;
213+
var cur = {};
214+
var video;
215+
var img;
216+
217+
218+
var btn = {};
219+
var gen = {};
220+
221+
function initdom(){
222+
223+
224+
hdr.initdom();
225+
filemod.initdom();
226+
log.initdom();
227+
228+
229+
$('[value=generate]').onclick = btn.generate;
230+
231+
video = $('video');
232+
img = $('#thumbnail');
233+
234+
output = $('#output');
235+
236+
}//initdom
237+
238+
239+
//:
240+
241+
242+
btn.generate = function(){
243+
244+
if(cur.status){
245+
log.red('already running');
246+
return;
247+
}
248+
249+
if(!cur.file){
250+
log.red('no file selected');
251+
return;
252+
}
253+
254+
var w = $('#width [value]').value;
255+
w = Number(w);
256+
if(isNaN(w)){
257+
log.red('invalid width');
258+
return;
259+
}
260+
261+
var h = $('#height [value]').value;
262+
h = Number(h);
263+
if(isNaN(h)){
264+
log.red('invalid height');
265+
return
266+
}
267+
268+
cur.status = true;
269+
270+
if($('#png [type]').checked){
271+
gen.png();
272+
}
273+
274+
if($('#jpg [type]').checked){
275+
gen.jpg();
276+
}
277+
278+
}//generate
279+
280+
281+
gen.png = async function(){
282+
283+
var fn = cur.file.filename;
284+
var fn2 = fn.split('.')[0];
285+
var thumb = fn2+'.png';
286+
287+
var uint8 = _uint8(cur.blob);
288+
289+
await ffmpeg.writeFile(fn,uint8);
290+
291+
var cmd = `ffmpeg -nostdin -ss 00:00:03.00 -i ${fn} -vframes 1 -vf scale=-1:50 ${thumb}`;
292+
var args = cmd.split(' ').slice(1);
293+
294+
var data = await ffmpeg.readFile(thumb);
295+
var thumbnail = _blob(data);
296+
gen.complete(thumbnail,thumb);
297+
298+
}//png
299+
300+
301+
gen.jpg = function(){
302+
}//jpg
303+
304+
305+
gen.complete = function(blob,fn){
306+
307+
var url = window.URL.createObjectURL(thumbnail);
308+
img.src = url;
309+
310+
}//complete
311+
312+
//:
313+
314+
315+
function source(){
316+
}//source
317+
318+
319+
var complete = {};
320+
321+
complete.load = function(file,blob){
322+
323+
cur.file = file;
324+
cur.blob = blob;
325+
var url = window.URL.createObjectURL(blob);
326+
video.src = url;
327+
328+
}//load
329+
330+
331+
complete.save = function(file){
332+
}//save
333+
334+
335+
//:
336+
337+
338+
(async()=>{
339+
340+
var {zip} = await import('https://cdn.jsdelivr.net/gh/javascript-2020/libs/js/io/tiny-unzip/tiny-unzip.m.js');
341+
var get = url=>fetch(url).then(res=>res.blob());
342+
var blob = await get('https://raw.githubusercontent.com/javascript-2020/external/main/ffmpeg/ffmpeg-wasm/ffmpeg-wasm.zip');
343+
var fnstr = (fn,_,js,i1,i2)=>(js=fn+'',i1=js.indexOf('{'),i2=js.lastIndexOf('}'),js.slice(i1+1,i2));
344+
_blob = v=>new Blob([v]);
345+
_uint8 = async v=>dtype(v)=='blob' ? new Uint8Array(await v.arrayBuffer()) : new Uint8Array(v);
346+
347+
var files = await zip.rd(blob);
348+
files.forEach(({name,blob})=>files[name]=blob);
349+
files['ffmpeg-core.wasm'] = window.URL.createObjectURL(files['ffmpeg-core.wasm']);
350+
var txt = ['ffmpeg-core.js','index.js','ffmpeg.js','814.ffmpeg.js'];
351+
await Promise.all(txt.map(async name=>files[name]=await files[name].text()));
352+
353+
var sandbox = {};
354+
355+
sandbox.worker = function(){
356+
357+
self.fetch = url=>new Promise(async res=>res(new Response('Not Found',{status:404})));
358+
self.onmessage = ({data:{lib,file}})=>{var importScripts=()=>self.eval(lib);eval(file)}
359+
360+
}//worker
361+
362+
sandbox.main = function(){
363+
364+
(()=>{
365+
366+
var globalThis = {document:{currentScript:{src:'https://null.com/'}}};
367+
function Worker(url){
368+
console.log('worker-sandbox',`${url}`);
369+
var js = fnstr(sandbox.worker);
370+
var blob = new Blob([js]);
371+
var url2 = window.URL.createObjectURL(blob);
372+
var worker = new window.Worker(url2);
373+
var lib = files['ffmpeg-core.js'];
374+
var name = url.pathname.split('/').at(-1);
375+
var file = files[name];
376+
worker.postMessage({lib,file});
377+
return worker;
378+
379+
}//worker
380+
381+
eval(files['ffmpeg.js']);
382+
383+
})();
384+
385+
}//main
386+
387+
eval(fnstr(sandbox.main));
388+
eval(files['index.js']);
389+
390+
var {fetchFile} = FFmpegUtil;
391+
var {FFmpeg} = FFmpegWASM;
392+
393+
ffmpeg = new FFmpeg();
394+
await ffmpeg.load({coreURL:'ffmpeg-core.js',wasmURL:files['ffmpeg-core.wasm']});
395+
396+
ffmpeg.on('log',({message})=>console.log(message));
397+
await ffmpeg.exec(['-version']);
398+
399+
})();
400+
401+
function disp(str){
402+
403+
var div = document.createElement('div');
404+
div.textContent = str;
405+
output.append(div);
406+
407+
}//disp
408+
112409
</script>
113410

114411
</html>

0 commit comments

Comments
 (0)