music video renderer web app deftoku.digi.rip
audio visualizer webapp
1

Configure Feed

Select the types of activity you want to include in your feed.

ux: added rendering percentage progress overlay

digi.rip (Jun 21, 2026, 9:52 PM EDT) 841006ef 4d10380c

+76 -36
+2
src/output.ts
··· 22 22 fps: number, 23 23 quality: Quality, 24 24 options: VisualizerOptions = {}, 25 + onProgress?: (current: number, total: number) => void, 25 26 ) { 26 27 const frames = await extractFreqFrames(buffer, fps); 27 28 ··· 48 49 for (let i = 0; i < frames.length; i++) { 49 50 draw(canvas, frames[i], options, 1 / fps); 50 51 await sourceVideo.add(i / fps, 1 / fps); 52 + onProgress?.(i + 1, frames.length); 51 53 } 52 54 sourceVideo.close(); 53 55
+59 -36
src/ui/render.ts
··· 32 32 }, 33 33 ) => Promise<FileSystemFileHandle>; 34 34 35 + function createOverlay(): HTMLDivElement { 36 + const overlay = document.createElement("div"); 37 + overlay.className = "render-overlay"; 38 + overlay.textContent = "0%"; 39 + document.querySelector(".main-content")!.appendChild(overlay); 40 + return overlay; 41 + } 42 + 35 43 export async function handleRender( 36 44 canvas: HTMLCanvasElement, 37 45 fpsSelect: HTMLSelectElement, ··· 44 52 return; 45 53 } 46 54 47 - if ("showSaveFilePicker" in globalThis) { 48 - let handle: FileSystemFileHandle; 49 - try { 50 - handle = await ( 51 - globalThis as unknown as { showSaveFilePicker: ShowSaveFilePicker } 52 - ).showSaveFilePicker({ 53 - suggestedName: filename, 54 - types: [ 55 - { description: "WebM Video", accept: { "video/webm": [".webm"] } }, 56 - ], 57 - }); 58 - } catch { 55 + const renderBtn = document.querySelector<HTMLButtonElement>("#render")!; 56 + renderBtn.disabled = true; 57 + const overlay = createOverlay(); 58 + 59 + try { 60 + if ("showSaveFilePicker" in globalThis) { 61 + let handle: FileSystemFileHandle; 62 + try { 63 + handle = await ( 64 + globalThis as unknown as { showSaveFilePicker: ShowSaveFilePicker } 65 + ).showSaveFilePicker({ 66 + suggestedName: filename, 67 + types: [ 68 + { description: "WebM Video", accept: { "video/webm": [".webm"] } }, 69 + ], 70 + }); 71 + } catch { 72 + return; 73 + } 74 + 75 + const fps = Number(fpsSelect.value); 76 + const quality = qualityMap[qualitySelect.value]; 77 + const def = visualizers[select.value as VisualizerName]; 78 + const blob = await output( 79 + canvas, 80 + audioBuffer, 81 + def.draw, 82 + fps, 83 + quality, 84 + getOpts(), 85 + (current, total) => { 86 + overlay.textContent = `${Math.round((current / total) * 100)}%`; 87 + }, 88 + ); 89 + 90 + const writable = await handle.createWritable(); 91 + await writable.write(blob); 92 + await writable.close(); 93 + console.log("render done"); 59 94 return; 60 95 } 61 96 ··· 69 104 fps, 70 105 quality, 71 106 getOpts(), 107 + (current, total) => { 108 + overlay.textContent = `${Math.round((current / total) * 100)}%`; 109 + }, 72 110 ); 73 111 74 - const writable = await handle.createWritable(); 75 - await writable.write(blob); 76 - await writable.close(); 112 + const url = URL.createObjectURL(blob); 113 + const a = document.createElement("a"); 114 + a.href = url; 115 + a.download = filename; 116 + a.click(); 117 + URL.revokeObjectURL(url); 77 118 console.log("render done"); 78 - return; 119 + } finally { 120 + overlay.remove(); 121 + renderBtn.disabled = false; 79 122 } 80 - 81 - const fps = Number(fpsSelect.value); 82 - const quality = qualityMap[qualitySelect.value]; 83 - const def = visualizers[select.value as VisualizerName]; 84 - const blob = await output( 85 - canvas, 86 - audioBuffer, 87 - def.draw, 88 - fps, 89 - quality, 90 - getOpts(), 91 - ); 92 - 93 - const url = URL.createObjectURL(blob); 94 - const a = document.createElement("a"); 95 - a.href = url; 96 - a.download = filename; 97 - a.click(); 98 - URL.revokeObjectURL(url); 99 - console.log("render done"); 100 123 }
+15
style.css
··· 64 64 65 65 /* ===== Layout ===== */ 66 66 .main-content { 67 + position: relative; 67 68 display: flex; 68 69 flex: 1; 69 70 align-items: center; 70 71 justify-content: center; 72 + } 73 + 74 + /* ===== Render Overlay ===== */ 75 + .render-overlay { 76 + position: absolute; 77 + inset: 0; 78 + display: flex; 79 + align-items: center; 80 + justify-content: center; 81 + background: var(--color-bg); 82 + color: var(--color-fg); 83 + font-size: 4rem; 84 + z-index: 10; 85 + user-select: none; 71 86 } 72 87 73 88 #canvas {