mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19:46 +00:00
Let FFMPEG_PATH and FFPROBE_PATH be configurable
This commit is contained in:
parent
8a94be803d
commit
414a3b7651
7 changed files with 42 additions and 12 deletions
|
|
@ -480,4 +480,14 @@ export class DittoConf {
|
|||
get precheck(): boolean {
|
||||
return optionalBooleanSchema.parse(this.env.get('DITTO_PRECHECK')) ?? true;
|
||||
}
|
||||
|
||||
/** Path to `ffmpeg` executable. */
|
||||
get ffmpegPath(): string {
|
||||
return this.env.get('FFMPEG_PATH') || 'ffmpeg';
|
||||
}
|
||||
|
||||
/** Path to `ffprobe` executable. */
|
||||
get ffprobePath(): string {
|
||||
return this.env.get('FFPROBE_PATH') || 'ffprobe';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export async function uploadFile(
|
|||
perf.mark('start');
|
||||
|
||||
const { conf, uploader } = c.var;
|
||||
const { ffmpegPath, ffprobePath } = conf;
|
||||
|
||||
if (!uploader) {
|
||||
throw new HTTPException(500, {
|
||||
|
|
@ -43,7 +44,7 @@ export async function uploadFile(
|
|||
const [baseType] = file.type.split('/');
|
||||
|
||||
perf.mark('probe-start');
|
||||
const probe = await analyzeFile(file.stream()).catch(() => null);
|
||||
const probe = await analyzeFile(file.stream(), { ffprobePath }).catch(() => null);
|
||||
perf.mark('probe-end');
|
||||
|
||||
perf.mark('transcode-start');
|
||||
|
|
@ -62,7 +63,7 @@ export async function uploadFile(
|
|||
}
|
||||
|
||||
if (needsTranscode) {
|
||||
const stream = transcodeVideo(file.stream());
|
||||
const stream = transcodeVideo(file.stream(), { ffmpegPath });
|
||||
const transcoded = await new Response(stream).bytes();
|
||||
file = new File([transcoded], file.name, { type: 'video/mp4' });
|
||||
}
|
||||
|
|
@ -103,7 +104,7 @@ export async function uploadFile(
|
|||
}
|
||||
|
||||
if (baseType === 'video' && (!image || !thumb)) {
|
||||
const bytes = await extractVideoFrame(file.stream());
|
||||
const bytes = await extractVideoFrame(file.stream(), '00:00:01', { ffmpegPath });
|
||||
const [[, url]] = await uploader.upload(new File([bytes], 'thumb.jpg', { type: 'image/jpeg' }), { signal });
|
||||
|
||||
if (!image) {
|
||||
|
|
|
|||
|
|
@ -87,13 +87,16 @@ interface Disposition {
|
|||
still_image: number;
|
||||
}
|
||||
|
||||
export function analyzeFile(input: URL | ReadableStream<Uint8Array>): Promise<AnalyzeResult> {
|
||||
export function analyzeFile(
|
||||
input: URL | ReadableStream<Uint8Array>,
|
||||
opts?: { ffprobePath?: string | URL },
|
||||
): Promise<AnalyzeResult> {
|
||||
const stream = ffprobe(input, {
|
||||
'v': 'error',
|
||||
'show_streams': '',
|
||||
'show_format': '',
|
||||
'of': 'json',
|
||||
});
|
||||
}, opts);
|
||||
|
||||
return new Response(stream).json();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,13 @@ export interface FFmpegFlags {
|
|||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
export function ffmpeg(input: URL | ReadableStream<Uint8Array>, flags: FFmpegFlags): ReadableStream<Uint8Array> {
|
||||
export function ffmpeg(
|
||||
input: URL | ReadableStream<Uint8Array>,
|
||||
flags: FFmpegFlags,
|
||||
opts?: { ffmpegPath?: string | URL },
|
||||
): ReadableStream<Uint8Array> {
|
||||
const { ffmpegPath = 'ffmpeg' } = opts ?? {};
|
||||
|
||||
const args = ['-i', input instanceof URL ? input.href : 'pipe:0'];
|
||||
|
||||
for (const [key, value] of Object.entries(flags)) {
|
||||
|
|
@ -28,7 +34,7 @@ export function ffmpeg(input: URL | ReadableStream<Uint8Array>, flags: FFmpegFla
|
|||
args.push('pipe:1'); // Output to stdout
|
||||
|
||||
// Spawn the FFmpeg process
|
||||
const command = new Deno.Command('ffmpeg', {
|
||||
const command = new Deno.Command(ffmpegPath, {
|
||||
args,
|
||||
stdin: input instanceof ReadableStream ? 'piped' : 'null',
|
||||
stdout: 'piped',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ export interface FFprobeFlags {
|
|||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
export function ffprobe(input: URL | ReadableStream<Uint8Array>, flags: FFprobeFlags): ReadableStream<Uint8Array> {
|
||||
export function ffprobe(
|
||||
input: URL | ReadableStream<Uint8Array>,
|
||||
flags: FFprobeFlags,
|
||||
opts?: { ffprobePath?: string | URL },
|
||||
): ReadableStream<Uint8Array> {
|
||||
const { ffprobePath = 'ffprobe' } = opts ?? {};
|
||||
|
||||
const args = [];
|
||||
|
||||
for (const [key, value] of Object.entries(flags)) {
|
||||
|
|
@ -26,7 +32,7 @@ export function ffprobe(input: URL | ReadableStream<Uint8Array>, flags: FFprobeF
|
|||
}
|
||||
|
||||
// Spawn the FFprobe process
|
||||
const command = new Deno.Command('ffprobe', {
|
||||
const command = new Deno.Command(ffprobePath, {
|
||||
args,
|
||||
stdin: input instanceof ReadableStream ? 'piped' : 'null',
|
||||
stdout: 'piped',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { ffmpeg } from './ffmpeg.ts';
|
|||
export function extractVideoFrame(
|
||||
input: URL | ReadableStream<Uint8Array>,
|
||||
ss: string = '00:00:01',
|
||||
opts?: { ffmpegPath?: string | URL },
|
||||
): Promise<Uint8Array> {
|
||||
const output = ffmpeg(input, {
|
||||
'ss': ss, // Seek to timestamp
|
||||
|
|
@ -10,7 +11,7 @@ export function extractVideoFrame(
|
|||
'q:v': '2', // High-quality JPEG (lower = better quality)
|
||||
'f': 'image2', // Force image format
|
||||
'loglevel': 'fatal',
|
||||
});
|
||||
}, opts);
|
||||
|
||||
return new Response(output).bytes();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { ffmpeg } from './ffmpeg.ts';
|
||||
|
||||
export function transcodeVideo(input: URL | ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
|
||||
export function transcodeVideo(
|
||||
input: URL | ReadableStream<Uint8Array>,
|
||||
opts?: { ffmpegPath?: string | URL },
|
||||
): ReadableStream<Uint8Array> {
|
||||
return ffmpeg(input, {
|
||||
'safe': '1', // Safe mode
|
||||
'nostdin': '', // Disable stdin
|
||||
|
|
@ -12,5 +15,5 @@ export function transcodeVideo(input: URL | ReadableStream<Uint8Array>): Readabl
|
|||
'b:a': '128k', // Audio bitrate
|
||||
'movflags': 'frag_keyframe+empty_moov', // Ensures MP4 streaming compatibility
|
||||
'f': 'mp4', // Force MP4 format
|
||||
});
|
||||
}, opts);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue