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>
77 lines
2.6 KiB
JavaScript
77 lines
2.6 KiB
JavaScript
import { badRequest } from './http-error.js';
|
||
|
||
export const TARGET_SECONDS = 10;
|
||
|
||
export function defaultSettings() {
|
||
return {
|
||
intro: { enabled: false, volume: 100 },
|
||
outro: { enabled: false, volume: 100 },
|
||
speedUp: true,
|
||
muteClips: true,
|
||
clipVolume: 100,
|
||
music: { enabled: false, volume: 60 },
|
||
voiceVolume: 100,
|
||
// Pro Clip-Index: Lautstärke des Voice-overs, 0–100
|
||
voiceVolumes: {},
|
||
// Clip-Indizes, die nicht ins Video sollen
|
||
excluded: [],
|
||
};
|
||
}
|
||
|
||
function volume(value, name) {
|
||
if (!Number.isInteger(value) || value < 0 || value > 100) {
|
||
throw badRequest(`${name}: Lautstärke muss zwischen 0 und 100 liegen.`);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
function flag(value, name) {
|
||
if (typeof value !== 'boolean') throw badRequest(`${name}: ungültiger Wert.`);
|
||
return value;
|
||
}
|
||
|
||
function clipIndex(value, clipCount) {
|
||
const index = Number(value);
|
||
if (!Number.isInteger(index) || index < 0 || index >= clipCount) {
|
||
throw badRequest('Unbekannter Clip.');
|
||
}
|
||
return index;
|
||
}
|
||
|
||
// Übernimmt nur bekannte Felder aus patch und prüft jedes; der Rest bleibt.
|
||
export function mergeSettings(current, patch, clipCount) {
|
||
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
|
||
throw badRequest('Ungültige Einstellungen.');
|
||
}
|
||
const next = structuredClone({ ...defaultSettings(), ...current });
|
||
|
||
for (const part of ['intro', 'outro', 'music']) {
|
||
if (patch[part] === undefined) continue;
|
||
const value = patch[part];
|
||
if (!value || typeof value !== 'object') throw badRequest(`${part}: ungültiger Wert.`);
|
||
if (value.enabled !== undefined) next[part].enabled = flag(value.enabled, part);
|
||
if (value.volume !== undefined) next[part].volume = volume(value.volume, part);
|
||
}
|
||
for (const key of ['speedUp', 'muteClips']) {
|
||
if (patch[key] !== undefined) next[key] = flag(patch[key], key);
|
||
}
|
||
for (const key of ['clipVolume', 'voiceVolume']) {
|
||
if (patch[key] !== undefined) next[key] = volume(patch[key], key);
|
||
}
|
||
if (patch.voiceVolumes !== undefined) {
|
||
if (!patch.voiceVolumes || typeof patch.voiceVolumes !== 'object') {
|
||
throw badRequest('voiceVolumes: ungültiger Wert.');
|
||
}
|
||
for (const [key, value] of Object.entries(patch.voiceVolumes)) {
|
||
next.voiceVolumes[clipIndex(key, clipCount)] = volume(value, 'Voice-over');
|
||
}
|
||
}
|
||
if (patch.excluded !== undefined) {
|
||
if (!Array.isArray(patch.excluded)) throw badRequest('excluded: ungültiger Wert.');
|
||
next.excluded = [...new Set(patch.excluded.map((value) => clipIndex(value, clipCount)))].sort(
|
||
(a, b) => a - b,
|
||
);
|
||
}
|
||
return next;
|
||
}
|