implemented 15,12,18 didn't check 18
This commit is contained in:
@ -1,13 +1,16 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useEditorStore } from '../store/editorStore';
|
||||
import { Download, Loader2, Zap, Cog, Info } from 'lucide-react';
|
||||
import { Download, Loader2, Zap, Cog, Info, Volume2 } from 'lucide-react';
|
||||
import type { ExportOptions } from '../types/project';
|
||||
|
||||
export default function ExportDialog() {
|
||||
const { videoPath, words, cutRanges, muteRanges, gainRanges, speedRanges, globalGainDb, isExporting, exportProgress, backendUrl, setExporting, getKeepSegments } =
|
||||
const { videoPath, words, cutRanges, muteRanges, gainRanges, speedRanges, globalGainDb, isExporting, exportProgress, backendUrl, setExporting, setExportedAudioPath, getKeepSegments } =
|
||||
useEditorStore();
|
||||
|
||||
const hasCuts = cutRanges.length > 0;
|
||||
const [isNormalizing, setIsNormalizing] = useState(false);
|
||||
const [normalizeTarget, setNormalizeTarget] = useState(-14);
|
||||
const [normalizeResult, setNormalizeResult] = useState<string | null>(null);
|
||||
|
||||
const [options, setOptions] = useState<Omit<ExportOptions, 'outputPath'>>({
|
||||
mode: 'fast',
|
||||
@ -78,6 +81,41 @@ export default function ExportDialog() {
|
||||
}
|
||||
}, [videoPath, options, backendUrl, setExporting, getKeepSegments, cutRanges, muteRanges, gainRanges, speedRanges, globalGainDb, words]);
|
||||
|
||||
const handleNormalize = useCallback(async () => {
|
||||
if (!videoPath) return;
|
||||
setIsNormalizing(true);
|
||||
setNormalizeResult(null);
|
||||
try {
|
||||
const res = await fetch(`${backendUrl}/audio/normalize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
input_path: videoPath,
|
||||
target_lufs: normalizeTarget,
|
||||
output_path: '',
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
let detail = res.statusText;
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body?.detail) detail = String(body.detail);
|
||||
} catch {
|
||||
// Keep statusText fallback
|
||||
}
|
||||
throw new Error(detail);
|
||||
}
|
||||
const data = await res.json();
|
||||
setExportedAudioPath(data.output_path);
|
||||
setNormalizeResult(`Normalized to ${data.target_lufs} LUFS → ${data.output_path.split('/').pop() || 'done'}`);
|
||||
} catch (err) {
|
||||
console.error('Normalize error:', err);
|
||||
setNormalizeResult(`Error: ${err instanceof Error ? err.message : 'Normalization failed'}`);
|
||||
} finally {
|
||||
setIsNormalizing(false);
|
||||
}
|
||||
}, [videoPath, backendUrl, normalizeTarget, setExportedAudioPath]);
|
||||
|
||||
return (
|
||||
<div className="p-4 space-y-5">
|
||||
<h3 className="text-sm font-semibold">Export Video</h3>
|
||||
@ -129,6 +167,46 @@ export default function ExportDialog() {
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Audio normalization */}
|
||||
<div className="space-y-2 border-t border-editor-border pt-3">
|
||||
<h4 className="text-xs font-semibold flex items-center gap-1.5">
|
||||
<Volume2 className="w-3.5 h-3.5" />
|
||||
Audio Normalization
|
||||
</h4>
|
||||
<p className="text-[10px] text-editor-text-muted leading-relaxed">
|
||||
Normalize loudness to a target LUFS level. YouTube uses <strong>-14 LUFS</strong>,
|
||||
Spotify uses <strong>-16 LUFS</strong>, broadcast uses <strong>-23 LUFS</strong>.
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={normalizeTarget}
|
||||
onChange={(e) => setNormalizeTarget(Number(e.target.value))}
|
||||
className="flex-1 px-2 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:outline-none focus:border-editor-accent"
|
||||
>
|
||||
<option value={-14}>YouTube (-14 LUFS)</option>
|
||||
<option value={-16}>Spotify (-16 LUFS)</option>
|
||||
<option value={-23}>Broadcast (-23 LUFS)</option>
|
||||
<option value={-11}>Loud (-11 LUFS)</option>
|
||||
<option value={-9}>Very Loud (-9 LUFS)</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={handleNormalize}
|
||||
disabled={isNormalizing || !videoPath}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30 disabled:opacity-40 transition-colors"
|
||||
>
|
||||
{isNormalizing ? (
|
||||
<Loader2 className="w-3 h-3 animate-spin" />
|
||||
) : (
|
||||
<Volume2 className="w-3 h-3" />
|
||||
)}
|
||||
{isNormalizing ? 'Normalizing...' : 'Normalize'}
|
||||
</button>
|
||||
</div>
|
||||
{normalizeResult && (
|
||||
<p className="text-[10px] text-editor-success">{normalizeResult}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Audio enhancement */}
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user