summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/media/mediabunny.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/lib/media/mediabunny.ts
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/lib/media/mediabunny.ts')
-rw-r--r--apps/web/src/lib/media/mediabunny.ts168
1 files changed, 0 insertions, 168 deletions
diff --git a/apps/web/src/lib/media/mediabunny.ts b/apps/web/src/lib/media/mediabunny.ts
deleted file mode 100644
index 1e97fc42..00000000
--- a/apps/web/src/lib/media/mediabunny.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import { Input, ALL_FORMATS, BlobSource } from "mediabunny";
-import { createTimelineAudioBuffer } from "@/lib/media/audio";
-import type { SceneTracks } from "@/lib/timeline";
-import type { MediaAsset } from "@/lib/media/types";
-import { TICKS_PER_SECOND } from "@/lib/wasm";
-
-export async function getVideoInfo({
- videoFile,
-}: {
- videoFile: File;
-}): Promise<{
- duration: number;
- width: number;
- height: number;
- fps: number;
- hasAudio: boolean;
-}> {
- const input = new Input({
- source: new BlobSource(videoFile),
- formats: ALL_FORMATS,
- });
-
- const duration = await input.computeDuration();
- const videoTrack = await input.getPrimaryVideoTrack();
-
- if (!videoTrack) {
- throw new Error("No video track found in the file");
- }
-
- const packetStats = await videoTrack.computePacketStats(100);
- const fps = packetStats.averagePacketRate;
- const audioTrack = await input.getPrimaryAudioTrack();
-
- return {
- duration,
- width: videoTrack.displayWidth,
- height: videoTrack.displayHeight,
- fps,
- hasAudio: audioTrack !== null,
- };
-}
-
-const SAMPLE_RATE = 44100;
-const NUM_CHANNELS = 2;
-const EMPTY_TIMELINE_SILENT_DURATION_SECONDS = 0.1;
-const MIN_SILENT_DURATION_SECONDS = 0.001;
-
-export const extractTimelineAudio = async ({
- tracks,
- mediaAssets,
- totalDuration,
- onProgress,
-}: {
- tracks: SceneTracks;
- mediaAssets: MediaAsset[];
- totalDuration: number;
- onProgress?: (progress: number) => void;
-}): Promise<Blob> => {
- if (totalDuration === 0) {
- return createWavBlob({
- samples: new Float32Array(
- SAMPLE_RATE * EMPTY_TIMELINE_SILENT_DURATION_SECONDS,
- ),
- });
- }
-
- onProgress?.(10);
-
- const audioBuffer = await createTimelineAudioBuffer({
- tracks,
- mediaAssets,
- duration: totalDuration,
- sampleRate: SAMPLE_RATE,
- });
-
- if (!audioBuffer) {
- const silentDurationSeconds = Math.max(
- MIN_SILENT_DURATION_SECONDS,
- totalDuration / TICKS_PER_SECOND,
- );
- const silentSamples = new Float32Array(
- Math.ceil(silentDurationSeconds * SAMPLE_RATE) * NUM_CHANNELS,
- );
- return createWavBlob({ samples: silentSamples });
- }
-
- onProgress?.(90);
-
- const interleavedSamples = interleaveAudioBuffer({ audioBuffer });
- onProgress?.(100);
-
- return createWavBlob({ samples: interleavedSamples });
-};
-
-function interleaveAudioBuffer({
- audioBuffer,
-}: {
- audioBuffer: AudioBuffer;
-}): Float32Array {
- const numChannels = Math.min(NUM_CHANNELS, audioBuffer.numberOfChannels);
- const interleavedSamples = new Float32Array(
- audioBuffer.length * NUM_CHANNELS,
- );
-
- for (let sampleIndex = 0; sampleIndex < audioBuffer.length; sampleIndex++) {
- for (let channel = 0; channel < NUM_CHANNELS; channel++) {
- const sourceChannel = Math.min(channel, Math.max(0, numChannels - 1));
- interleavedSamples[sampleIndex * NUM_CHANNELS + channel] =
- audioBuffer.getChannelData(sourceChannel)[sampleIndex] ?? 0;
- }
- }
-
- return interleavedSamples;
-}
-
-function createWavBlob({ samples }: { samples: Float32Array }): Blob {
- const numChannels = NUM_CHANNELS;
- const bitsPerSample = 16;
- const bytesPerSample = bitsPerSample / 8;
- const numSamples = samples.length / numChannels;
- const dataSize = numSamples * numChannels * bytesPerSample;
- const buffer = new ArrayBuffer(44 + dataSize);
- const view = new DataView(buffer);
-
- // riff header
- writeString({ view, offset: 0, str: "RIFF" });
- view.setUint32(4, 36 + dataSize, true);
- writeString({ view, offset: 8, str: "WAVE" });
-
- // fmt chunk
- writeString({ view, offset: 12, str: "fmt " });
- view.setUint32(16, 16, true);
- view.setUint16(20, 1, true);
- view.setUint16(22, numChannels, true);
- view.setUint32(24, SAMPLE_RATE, true);
- view.setUint32(28, SAMPLE_RATE * numChannels * bytesPerSample, true);
- view.setUint16(32, numChannels * bytesPerSample, true);
- view.setUint16(34, bitsPerSample, true);
-
- // data chunk
- writeString({ view, offset: 36, str: "data" });
- view.setUint32(40, dataSize, true);
-
- // convert float32 to int16 and write
- let offset = 44;
- for (let i = 0; i < samples.length; i++) {
- const sample = Math.max(-1, Math.min(1, samples[i]));
- const int16 = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
- view.setInt16(offset, int16, true);
- offset += 2;
- }
-
- return new Blob([buffer], { type: "audio/wav" });
-}
-
-function writeString({
- view,
- offset,
- str,
-}: {
- view: DataView;
- offset: number;
- str: string;
-}): void {
- for (let i = 0; i < str.length; i++) {
- view.setUint8(offset + i, str.charCodeAt(i));
- }
-}