Files
FuerDieCuts/server/media.js
T
ferdi2goandClaude Opus 5.5 72e4b8ab7a Einstellungen und Zusatzdateien für den Schnitt ablegen
Intro, Outro, Backing Track und Voice-overs pro Clip laufen über
denselben Upload in Stücken wie die Clips. Die Schnitt-Einstellungen
werden feldweise geprüft; ffprobe akzeptiert jetzt auch reine Tonspuren.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:48:53 +00:00

50 lines
1.8 KiB
JavaScript

import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
function parseRate(rate) {
const [numerator, denominator] = String(rate || '0/1').split('/').map(Number);
return denominator ? numerator / denominator : 0;
}
// Handyvideos liegen oft quer im Container und tragen die Drehung als Metadatum.
function readRotation(stream) {
const side = stream.side_data_list?.find((entry) => entry.rotation !== undefined);
if (side) return Number(side.rotation);
if (stream.tags?.rotate) return Number(stream.tags.rotate);
return 0;
}
// Liefert die Eckdaten einer Video- oder Audiodatei; Breite und Höhe so,
// wie das Video angezeigt wird.
export async function probe(file) {
const { stdout } = await execFileAsync(
'ffprobe',
['-v', 'error', '-print_format', 'json', '-show_streams', '-show_format', file],
{ maxBuffer: 10 * 1024 * 1024 },
);
const info = JSON.parse(stdout);
const video = info.streams.find(
(stream) => stream.codec_type === 'video' && !stream.disposition?.attached_pic,
);
const hasAudio = info.streams.some((stream) => stream.codec_type === 'audio');
if (!video && !hasAudio) throw new Error('Weder Bild noch Ton gefunden.');
const duration = Number(info.format?.duration ?? video?.duration ?? 0);
if (!video) {
return { duration, hasVideo: false, width: null, height: null, rotation: 0, fps: 0, hasAudio };
}
const rotation = readRotation(video);
const sideways = Math.abs(rotation) % 180 === 90;
return {
duration,
hasVideo: true,
width: sideways ? video.height : video.width,
height: sideways ? video.width : video.height,
rotation,
fps: Math.round(parseRate(video.avg_frame_rate || video.r_frame_rate) * 100) / 100,
hasAudio,
};
}