mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19:46 +00:00
Rework ffmpeg to accept file URIs
This commit is contained in:
parent
d36efb7a30
commit
e46b7bfa85
6 changed files with 74 additions and 4 deletions
11
packages/transcode/analyze.test.ts
Normal file
11
packages/transcode/analyze.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { assertEquals } from '@std/assert';
|
||||||
|
|
||||||
|
import { ffmpegDim } from './analyze.ts';
|
||||||
|
|
||||||
|
Deno.test('ffmpegDim', async () => {
|
||||||
|
await using file = await Deno.open(new URL('./buckbunny.mp4', import.meta.url));
|
||||||
|
|
||||||
|
const result = await ffmpegDim(file.readable);
|
||||||
|
|
||||||
|
assertEquals(result, { width: 1280, height: 720 });
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { ffmpeg } from './ffmpeg.ts';
|
||||||
|
|
||||||
|
export async function ffmpegDim(
|
||||||
|
input: ReadableStream<Uint8Array>,
|
||||||
|
): Promise<{ width: number; height: number } | undefined> {
|
||||||
|
const result = ffmpeg(input, {
|
||||||
|
'vf': 'showinfo', // Output as JSON
|
||||||
|
'f': 'null', // Tell FFmpeg not to produce an output file
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await new Response(result).json();
|
||||||
|
console.log(text);
|
||||||
|
const output = JSON.parse(text);
|
||||||
|
|
||||||
|
const [stream] = output.streams ?? [];
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
@ -14,3 +14,16 @@ Deno.test('ffmpeg', async () => {
|
||||||
await Deno.mkdir(new URL('./tmp', import.meta.url), { recursive: true });
|
await Deno.mkdir(new URL('./tmp', import.meta.url), { recursive: true });
|
||||||
await Deno.writeFile(new URL('./tmp/buckbunny-transcoded.mp4', import.meta.url), output);
|
await Deno.writeFile(new URL('./tmp/buckbunny-transcoded.mp4', import.meta.url), output);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test('ffmpeg from file', async () => {
|
||||||
|
const output = ffmpeg(new URL('./buckbunny.mp4', import.meta.url), {
|
||||||
|
'c:v': 'libx264',
|
||||||
|
'preset': 'veryfast',
|
||||||
|
'loglevel': 'fatal',
|
||||||
|
'movflags': 'frag_keyframe+empty_moov',
|
||||||
|
'f': 'mp4',
|
||||||
|
});
|
||||||
|
|
||||||
|
await Deno.mkdir(new URL('./tmp', import.meta.url), { recursive: true });
|
||||||
|
await Deno.writeFile(new URL('./tmp/buckbunny-transcoded-fromfile.mp4', import.meta.url), output);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ export interface FFmpegFlags {
|
||||||
[key: string]: string | undefined;
|
[key: string]: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ffmpeg(input: ReadableStream<Uint8Array>, flags: FFmpegFlags): ReadableStream<Uint8Array> {
|
export function ffmpeg(input: URL | ReadableStream<Uint8Array>, flags: FFmpegFlags): ReadableStream<Uint8Array> {
|
||||||
const args = ['-i', 'pipe:0']; // Input from stdin
|
const args = ['-i', input instanceof URL ? input.href : 'pipe:0'];
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(flags)) {
|
for (const [key, value] of Object.entries(flags)) {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
|
|
@ -22,11 +22,18 @@ export function ffmpeg(input: ReadableStream<Uint8Array>, flags: FFmpegFlags): R
|
||||||
args.push('pipe:1'); // Output to stdout
|
args.push('pipe:1'); // Output to stdout
|
||||||
|
|
||||||
// Spawn the FFmpeg process
|
// Spawn the FFmpeg process
|
||||||
const command = new Deno.Command('ffmpeg', { args, stdin: 'piped', stdout: 'piped' });
|
const command = new Deno.Command('ffmpeg', {
|
||||||
|
args,
|
||||||
|
stdin: input instanceof ReadableStream ? 'piped' : undefined,
|
||||||
|
stdout: 'piped',
|
||||||
|
});
|
||||||
|
|
||||||
const child = command.spawn();
|
const child = command.spawn();
|
||||||
|
|
||||||
// Pipe the input stream into FFmpeg stdin and ensure completion
|
// Pipe the input stream into FFmpeg stdin and ensure completion
|
||||||
input.pipeTo(child.stdin);
|
if (input instanceof ReadableStream) {
|
||||||
|
input.pipeTo(child.stdin);
|
||||||
|
}
|
||||||
|
|
||||||
// Return the FFmpeg stdout stream
|
// Return the FFmpeg stdout stream
|
||||||
return child.stdout;
|
return child.stdout;
|
||||||
|
|
|
||||||
9
packages/transcode/frame.test.ts
Normal file
9
packages/transcode/frame.test.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { extractVideoFrame } from './frame.ts';
|
||||||
|
|
||||||
|
Deno.test('extractVideoFrame', async () => {
|
||||||
|
const uri = new URL('./buckbunny.mp4', import.meta.url);
|
||||||
|
const result = await extractVideoFrame(uri);
|
||||||
|
|
||||||
|
await Deno.mkdir(new URL('./tmp', import.meta.url), { recursive: true });
|
||||||
|
await Deno.writeFile(new URL('./tmp/buckbunny-poster.jpg', import.meta.url), result);
|
||||||
|
});
|
||||||
13
packages/transcode/frame.ts
Normal file
13
packages/transcode/frame.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { ffmpeg } from './ffmpeg.ts';
|
||||||
|
|
||||||
|
export function extractVideoFrame(file: URL, ss: string = '00:00:01'): Promise<Uint8Array> {
|
||||||
|
const output = ffmpeg(file, {
|
||||||
|
'ss': ss, // Seek to timestamp
|
||||||
|
'frames:v': '1', // Extract only 1 frame
|
||||||
|
'q:v': '2', // High-quality JPEG (lower = better quality)
|
||||||
|
'f': 'image2', // Force image format
|
||||||
|
'loglevel': 'fatal',
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(output).bytes();
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue