From e55f4c2fe8a6e1d62a0b005777b46c80e360d37e Mon Sep 17 00:00:00 2001 From: memdmp Date: Thu, 31 Jul 2025 22:48:01 +0200 Subject: feat: initial commit --- src/lib/Player/FrameSlider.svelte | 67 ++ src/lib/Player/Keybinds.svelte | 47 + src/lib/Player/Player.svelte | 161 +++ src/lib/Player/Video.ts | 55 ++ src/lib/Renderer/Renderer.svelte | 146 +++ src/lib/assets/favicon.svg | 1 + src/lib/index.ts | 1 + src/lib/vendor/svelte-range-slider/README | 1 + .../vendor/svelte-range-slider/range-pips.svelte | 303 ++++++ .../vendor/svelte-range-slider/range-slider.svelte | 1026 ++++++++++++++++++++ 10 files changed, 1808 insertions(+) create mode 100644 src/lib/Player/FrameSlider.svelte create mode 100644 src/lib/Player/Keybinds.svelte create mode 100644 src/lib/Player/Player.svelte create mode 100644 src/lib/Player/Video.ts create mode 100644 src/lib/Renderer/Renderer.svelte create mode 100644 src/lib/assets/favicon.svg create mode 100644 src/lib/index.ts create mode 100644 src/lib/vendor/svelte-range-slider/README create mode 100644 src/lib/vendor/svelte-range-slider/range-pips.svelte create mode 100644 src/lib/vendor/svelte-range-slider/range-slider.svelte (limited to 'src/lib') diff --git a/src/lib/Player/FrameSlider.svelte b/src/lib/Player/FrameSlider.svelte new file mode 100644 index 0000000..a632aa9 --- /dev/null +++ b/src/lib/Player/FrameSlider.svelte @@ -0,0 +1,67 @@ + + +
+
+ +
+
+ +
+
+ { + e.stopPropagation(); + }} + onkeydown={(e) => { + e.stopPropagation(); + }} + /> + of {frameCount} + +
+
diff --git a/src/lib/Player/Keybinds.svelte b/src/lib/Player/Keybinds.svelte new file mode 100644 index 0000000..756a865 --- /dev/null +++ b/src/lib/Player/Keybinds.svelte @@ -0,0 +1,47 @@ + + + { + switch (e.key) { + case ' ': + e.preventDefault(); + playing = !playing; + if (playing) playbackStarted = performance.now(); + break; + + // default: + // if (dev) console.debug('Keypress:', e.key); + // break; + } + }} + onkeydown={(e) => { + switch (e.key) { + case 'ArrowLeft': + e.preventDefault(); + frame = Math.max(frame - (e.ctrlKey ? (fps ?? 60) : 1), 0); + break; + case 'ArrowRight': + e.preventDefault(); + frame = Math.min(frame + (e.ctrlKey ? (fps ?? 60) : 1), frameCount); + break; + + // default: + // if (dev) console.debug('Keydown:', e.key); + // break; + } + }} +/> diff --git a/src/lib/Player/Player.svelte b/src/lib/Player/Player.svelte new file mode 100644 index 0000000..f6df121 --- /dev/null +++ b/src/lib/Player/Player.svelte @@ -0,0 +1,161 @@ + + + { + if (canvas && video) { + (async () => { + video['_isInit'] = true; + renderPromise = await video.init(); + video['_isInit'] = false; + renderPreviewFrame(video, frame); + })(); + } + }} +/> + + + +
+
+
+ + Your browser doesn't support the canvas API. + + {#if audioSource} + + {/if} +
+
+ +
diff --git a/src/lib/Player/Video.ts b/src/lib/Player/Video.ts new file mode 100644 index 0000000..78b3b8f --- /dev/null +++ b/src/lib/Player/Video.ts @@ -0,0 +1,55 @@ +export type FrameTime = { + milliseconds: number, + seconds: number, + frames: number +} +export abstract class Video { + public constructor(public canvas: HTMLCanvasElement) { }; + public abstract renderFrame(time: FrameTime): Promise | void; + /** (re-)Initializes the Video object. Also called on window resizes. */ + public abstract init(): void | Promise; + private _isInit = false; + /** The frames per second to render at */ + public abstract get fps(): number; + /** Length in frames */ + public abstract get length(): number; + /** A URL (and matching filename) to an ffmpeg-compatible audio file */ + public audioUrl?: readonly [filename: string, fileUrl: string]; + /** Resizes the canvas to a predetermined render resolution - must only be called in init() - do not overwrite */ + public resize(x: number, y: number) { + if (!this._isInit) throw new Error('Must only call resize() in init.') + this.canvas.width = x; + this.canvas.height = y; + const parentW = this.canvas.parentElement!.clientWidth, + parentH = this.canvas.parentElement!.clientHeight + if (x <= parentW && y <= parentH) { + this.canvas.style.width = `${x}px`; + this.canvas.style.height = `${y}px`; + } else if (x <= parentW && y > parentH) { + this.canvas.style.width = `${x / y * parentH}px`; + this.canvas.style.height = `${parentH}px`; + } else if (y <= parentH && x > parentW) { + this.canvas.style.width = `${parentW}px`; + this.canvas.style.height = `${y / x * parentW}px`; + } else { + if ((parentW / x) * y > parentH) { + this.canvas.style.width = `${(parentH / y) * x}px` + this.canvas.style.height = `${parentH}px` + } else { + this.canvas.style.width = `${parentW}px` + this.canvas.style.height = `${(parentW / x) * y}px` + } + } + } + /** The width of the video, in pixels */ + public get w() { + return this.canvas.width; + } + /** The height of the video, in pixels */ + public get h() { + return this.canvas.height; + } + /** Use to cleanup any mess you made - do not remove the canvas, it may be reused. */ + public cleanup() { }; +} +export type VideoConstructor = new (...params: ConstructorParameters) => Video diff --git a/src/lib/Renderer/Renderer.svelte b/src/lib/Renderer/Renderer.svelte new file mode 100644 index 0000000..72c270b --- /dev/null +++ b/src/lib/Renderer/Renderer.svelte @@ -0,0 +1,146 @@ + + +
+ {#if videoUrl} + + + {:else} +
+
+ + Your browser doesn't support the canvas API. + +
+
+ {/if} +

+ {message} +

+
diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/vendor/svelte-range-slider/README b/src/lib/vendor/svelte-range-slider/README new file mode 100644 index 0000000..b17797b --- /dev/null +++ b/src/lib/vendor/svelte-range-slider/README @@ -0,0 +1 @@ +https://github.com/roycrippen4/svelte-range-slider/tree/master diff --git a/src/lib/vendor/svelte-range-slider/range-pips.svelte b/src/lib/vendor/svelte-range-slider/range-pips.svelte new file mode 100644 index 0000000..418fc7e --- /dev/null +++ b/src/lib/vendor/svelte-range-slider/range-pips.svelte @@ -0,0 +1,303 @@ + + + + +
+ {#if (all && first !== false) || first} + labelUp(min, e)} + > + {#if all === 'label' || first === 'label'} + + {prefix}{formatter(fixFloat(min), 0, 0)}{suffix} + + {/if} + + {/if} + + {#if (all && rest !== false) || rest} + + {#each Array(pipCount + 1) as _, i} + {#if pipVal(i) !== min && pipVal(i) !== max} + labelUp(pipVal(i), e)} + > + {#if all === 'label' || rest === 'label'} + + {prefix}{formatter(pipVal(i), i, percentOf(pipVal(i)))}{suffix} + + {/if} + + {/if} + {/each} + {/if} + + {#if (all && last !== false) || last} + labelUp(max, e)} + > + {#if all === 'label' || last === 'label'} + + {prefix}{formatter(fixFloat(max), pipCount, 100)}{suffix} + + {/if} + + {/if} +
+ + diff --git a/src/lib/vendor/svelte-range-slider/range-slider.svelte b/src/lib/vendor/svelte-range-slider/range-slider.svelte new file mode 100644 index 0000000..7f522e2 --- /dev/null +++ b/src/lib/vendor/svelte-range-slider/range-slider.svelte @@ -0,0 +1,1026 @@ + + + + +
+ {#each values as value, index} + + + {#if float} + + {prefix}{handleFormatter(value, index, percentOf(value))}{suffix} + + {/if} + + {/each} + + {#if range} + + {/if} + + {#if pips} + + {/if} +
+ + + + -- cgit v1.2.3