diff --git a/packages/transcode/analyze.test.ts b/packages/transcode/analyze.test.ts index aeed6c94..e72e1619 100644 --- a/packages/transcode/analyze.test.ts +++ b/packages/transcode/analyze.test.ts @@ -4,8 +4,7 @@ import { getVideoDimensions } from './analyze.ts'; Deno.test('getVideoDimensions', async () => { const uri = new URL('./buckbunny.mp4', import.meta.url); - - const dimensions = await getVideoDimensions(uri.href); + const dimensions = await getVideoDimensions(uri); assertEquals(dimensions, { width: 1920, height: 1080 }); }); diff --git a/packages/transcode/analyze.ts b/packages/transcode/analyze.ts index 95a6f6d2..fe84a0c0 100644 --- a/packages/transcode/analyze.ts +++ b/packages/transcode/analyze.ts @@ -1,7 +1,9 @@ import { ffprobe } from './ffprobe.ts'; -export async function getVideoDimensions(path: string): Promise<{ width: number; height: number } | null> { - const stream = ffprobe(path, { +export async function getVideoDimensions( + input: URL | ReadableStream, +): Promise<{ width: number; height: number } | null> { + const stream = ffprobe(input, { 'v': 'error', 'select_streams': 'v:0', 'show_entries': 'stream=width,height', diff --git a/packages/transcode/ffprobe.test.ts b/packages/transcode/ffprobe.test.ts index 531e648e..6dbab12c 100644 --- a/packages/transcode/ffprobe.test.ts +++ b/packages/transcode/ffprobe.test.ts @@ -3,6 +3,21 @@ import { assertEquals } from '@std/assert'; import { ffprobe } from './ffprobe.ts'; Deno.test('ffprobe', async () => { + await using file = await Deno.open(new URL('./buckbunny.mp4', import.meta.url)); + + const stream = ffprobe(file.readable, { + 'v': 'error', + 'select_streams': 'v:0', + 'show_entries': 'stream=width,height', + 'of': 'json', + }); + + const { streams: [dimensions] } = await new Response(stream).json(); + + assertEquals(dimensions, { width: 1920, height: 1080 }); +}); + +Deno.test('ffprobe from file', async () => { const uri = new URL('./buckbunny.mp4', import.meta.url); const stream = ffprobe(uri, { diff --git a/packages/transcode/ffprobe.ts b/packages/transcode/ffprobe.ts index 3388cc30..3f5fe16f 100644 --- a/packages/transcode/ffprobe.ts +++ b/packages/transcode/ffprobe.ts @@ -6,19 +6,45 @@ export interface FFprobeFlags { [key: string]: string | undefined; } -export function ffprobe(path: URL | string, flags: FFprobeFlags): ReadableStream { +export function ffprobe(input: URL | ReadableStream, flags: FFprobeFlags): ReadableStream { const args = []; for (const [key, value] of Object.entries(flags)) { if (typeof value === 'string') { - args.push(`-${key}`, value); + if (value) { + args.push(`-${key}`, value); + } else { + args.push(`-${key}`); + } } } - args.push(path instanceof URL ? path.href : path); + if (input instanceof URL) { + args.push('-i', input.href); + } else { + args.push('-i', 'pipe:0'); + } + + // Spawn the FFprobe process + const command = new Deno.Command('ffprobe', { + args, + stdin: input instanceof ReadableStream ? 'piped' : 'null', + stdout: 'piped', + }); - const command = new Deno.Command('ffprobe', { args, stdout: 'piped' }); const child = command.spawn(); + // Pipe the input stream into FFmpeg stdin and ensure completion + if (input instanceof ReadableStream) { + input.pipeTo(child.stdin).catch((e: unknown) => { + if (e instanceof Error && e.name === 'BrokenPipe') { + // Ignore. ffprobe closes the pipe once it has read the metadata. + } else { + throw e; + } + }); + } + + // Return the FFmpeg stdout stream return child.stdout; }