add stabilisation camera recorder

This commit is contained in:
Michael committed 2026-01-22 17:27:17 +01:00
1 parent 6add333ce6
commit b4347d068d
2 files changed
+62 -18

No files matched your search

+49 -14
View File
@@ -53,11 +53,22 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) {
console.log(`📸 Starting camera: ${config.label}, facing: ${facingMode}`); console.log(`📸 Starting camera: ${config.label}, facing: ${facingMode}`);
const constraints: MediaStreamConstraints = { const constraints: MediaStreamConstraints = {
audio: true, audio: {
echoCancellation: true,
noiseSuppression: true,
},
video: { video: {
facingMode: facingMode, facingMode: facingMode,
width: { ideal: config.width, min: 640 }, width: { ideal: config.width, min: 640 },
height: { ideal: config.height, min: 480 }, height: { ideal: config.height, min: 480 },
frameRate: { ideal: 30 },
// @ts-ignore - Advanced constraints for iOS stabilization
advanced: [
{ zoom: 1.0 },
// Enable video stabilization on supported devices (iOS)
{ videoStabilizationMode: 'cinematic' },
{ videoStabilizationMode: 'auto' },
]
} }
}; };
@@ -240,18 +251,23 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) {
<X className="h-6 w-6" /> <X className="h-6 w-6" />
</Button> </Button>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="bg-black/30 backdrop-blur-md px-3 py-1 rounded-full text-white font-mono text-sm"> {/* Recording timer or quality selector */}
{isRecording ? formatTime(timer) : (resolutionInfo || 'Camera')} {isRecording ? (
</div> <div className="bg-red-600 px-3 py-1 rounded-full text-white font-mono text-sm flex items-center gap-2">
{!isRecording && ( <div className="w-2 h-2 bg-white rounded-full animate-pulse" />
<Button {formatTime(timer)}
variant="ghost" </div>
size="icon" ) : (
<button
onClick={() => setShowSettings(!showSettings)} onClick={() => setShowSettings(!showSettings)}
className="text-white" className="bg-black/50 backdrop-blur-md px-3 py-2 rounded-full text-white font-medium text-sm flex items-center gap-2 border border-white/30"
> >
<Settings2 className="h-5 w-5" /> <Settings2 className="h-4 w-4" />
</Button> {quality.toUpperCase()}
<span className="text-white/60 text-xs">
{resolutionInfo || QUALITY_CONFIGS[quality].label}
</span>
</button>
)} )}
</div> </div>
<Button variant="ghost" size="icon" onClick={switchCamera} className="text-white"> <Button variant="ghost" size="icon" onClick={switchCamera} className="text-white">
@@ -272,8 +288,8 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) {
setShowSettings(false); setShowSettings(false);
}} }}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-all ${quality === q className={`px-3 py-2 rounded-lg text-sm font-medium transition-all ${quality === q
? 'bg-white text-black' ? 'bg-white text-black'
: 'bg-white/20 text-white hover:bg-white/30' : 'bg-white/20 text-white hover:bg-white/30'
}`} }`}
> >
{q.toUpperCase()} {q.toUpperCase()}
@@ -298,7 +314,26 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) {
</div> </div>
{/* Controls */} {/* Controls */}
<div className="absolute bottom-0 left-0 right-0 p-8 pb-12 flex justify-center items-center bg-gradient-to-t from-black/80 to-transparent"> <div className="absolute bottom-0 left-0 right-0 p-6 pb-10 flex flex-col items-center bg-gradient-to-t from-black/80 to-transparent">
{/* Quality selector - visible before recording */}
{!isRecording && (
<div className="flex gap-2 mb-6">
{(Object.keys(QUALITY_CONFIGS) as VideoQuality[]).map((q) => (
<button
key={q}
onClick={() => setQuality(q)}
className={`px-4 py-2 rounded-full text-sm font-bold transition-all ${quality === q
? 'bg-white text-black'
: 'bg-white/20 text-white border border-white/40'
}`}
>
{q.toUpperCase()}
</button>
))}
</div>
)}
{/* Record button */}
{isRecording ? ( {isRecording ? (
<button <button
onClick={stopRecording} onClick={stopRecording}
+13 -4
View File
@@ -310,15 +310,22 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
# (Original audio is never used per user request) # (Original audio is never used per user request)
if has_music: if has_music:
# Music volume # Music volume - ensure it's properly scaled (0.0 to 1.0)
music_vol = request.music_volume * 0.4 if has_tts else request.music_volume # Apply volume more aggressively for noticeable effect
music_vol = request.music_volume
if has_tts:
# Reduce music even more when TTS is active so voice is clear
music_vol = music_vol * 0.3
print(
f"🎵 Music volume applied: {music_vol:.2f} (requested: {request.music_volume})"
)
filter_complex_parts.append(f"[1:a]volume={music_vol}[a1]") filter_complex_parts.append(f"[1:a]volume={music_vol}[a1]")
mix_inputs += 1 mix_inputs += 1
if has_tts: if has_tts:
tts_idx = 2 if has_music else 1 tts_idx = 2 if has_music else 1
# Voice needs to be loud and clear # Voice needs to be loud and clear
filter_complex_parts.append(f"[{tts_idx}:a]volume=2.0[a2]") filter_complex_parts.append(f"[{tts_idx}:a]volume=1.5[a2]")
mix_inputs += 1 mix_inputs += 1
# Build mix command # Build mix command
@@ -330,8 +337,10 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
if has_tts: if has_tts:
inputs_str += "[a2]" inputs_str += "[a2]"
# IMPORTANT: normalize=0 prevents amix from auto-adjusting volumes
# which was causing volume slider to have no effect
filter_complex_parts.append( filter_complex_parts.append(
f"{inputs_str}amix=inputs={mix_inputs}:duration=first:dropout_transition=2[aout]" f"{inputs_str}amix=inputs={mix_inputs}:duration=first:dropout_transition=2:normalize=0[aout]"
) )
cmd.extend(["-filter_complex", ";".join(filter_complex_parts)]) cmd.extend(["-filter_complex", ";".join(filter_complex_parts)])