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/app.css | 1 + src/app.d.ts | 13 + src/app.html | 14 + 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 ++++++++++++++++++++ src/routes/+layout.svelte | 12 + src/routes/+page.svelte | 6 + src/routes/ffmpeg-test/+page.svelte | 45 + src/routes/render/+page.svelte | 6 + src/user/Sneky Snitch.mp4 | Bin 0 -> 1620945 bytes src/user/index.ts | 21 + 19 files changed, 1926 insertions(+) create mode 100644 src/app.css create mode 100644 src/app.d.ts create mode 100644 src/app.html 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 create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+page.svelte create mode 100644 src/routes/ffmpeg-test/+page.svelte create mode 100644 src/routes/render/+page.svelte create mode 100644 src/user/Sneky Snitch.mp4 create mode 100644 src/user/index.ts (limited to 'src') diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..d4b5078 --- /dev/null +++ b/src/app.css @@ -0,0 +1 @@ +@import 'tailwindcss'; diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..d1e03cb --- /dev/null +++ b/src/app.html @@ -0,0 +1,14 @@ + + + + + + + %sveltekit.head% + + + +
%sveltekit.body%
+ + + 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} +
+ + + + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..8c56a3c --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,12 @@ + + + + + + +{@render children?.()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..8d09da5 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/routes/ffmpeg-test/+page.svelte b/src/routes/ffmpeg-test/+page.svelte new file mode 100644 index 0000000..226fc3c --- /dev/null +++ b/src/routes/ffmpeg-test/+page.svelte @@ -0,0 +1,45 @@ + + +
+ + +
+ +

{message}

+
diff --git a/src/routes/render/+page.svelte b/src/routes/render/+page.svelte new file mode 100644 index 0000000..e9ecd91 --- /dev/null +++ b/src/routes/render/+page.svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/user/Sneky Snitch.mp4 b/src/user/Sneky Snitch.mp4 new file mode 100644 index 0000000..b9d53b5 Binary files /dev/null and b/src/user/Sneky Snitch.mp4 differ diff --git a/src/user/index.ts b/src/user/index.ts new file mode 100644 index 0000000..2caf88c --- /dev/null +++ b/src/user/index.ts @@ -0,0 +1,21 @@ +import { Video as BaseVideo, type FrameTime } from '$/lib/Player/Video'; +import SneakySnitchUrl from './Sneky Snitch.mp4?url' + +export default class Video extends BaseVideo { + public ctx!: CanvasRenderingContext2D + public init(): void | Promise { + // this.resize(this.canvas.clientWidth,this.canvas.clientHeight) + this.resize(1920, 1080) + this.ctx = this.canvas.getContext('2d')! + } + public renderFrame(time: FrameTime): Promise | void { + this.ctx.fillStyle = '#000' + this.ctx.fillRect(0, 0, this.w, this.h) + this.ctx.font = "50px Nunito"; + this.ctx.fillStyle = '#fff' + this.ctx.fillText(`${time.seconds.toFixed(3)}`, 0, 50) + } + public fps = 30; + public length = 3 * this.fps; + public audioUrl = ['sneakysnitch.mp4', SneakySnitchUrl] as const +} -- cgit v1.2.3