Fixing loudness issue
This commit is contained in:
+51
-46
@@ -395,7 +395,7 @@ def build_ffmpeg_command(plan: RenderPlan, output_path: Path) -> list[str]:
|
||||
video_path = _resolve_video_path(
|
||||
videos_dir, event.video_source, shared_assets_dir, project_path
|
||||
)
|
||||
skip = event.video_source.skip
|
||||
skip = event.video_source.skip or 0.0
|
||||
if skip > 0:
|
||||
cmd.extend(["-ss", f"{skip:.3f}"])
|
||||
cmd.extend(["-analyzeduration", "0", "-probesize", "1000"])
|
||||
@@ -425,7 +425,7 @@ def build_ffmpeg_command(plan: RenderPlan, output_path: Path) -> list[str]:
|
||||
video_path = _resolve_video_path(
|
||||
videos_dir, event.video_source, shared_assets_dir, project_path
|
||||
)
|
||||
skip = event.video_source.skip
|
||||
skip = event.video_source.skip or 0.0
|
||||
if skip > 0:
|
||||
cmd.extend(["-ss", f"{skip:.3f}"])
|
||||
cmd.extend(["-analyzeduration", "0", "-probesize", "1000"])
|
||||
@@ -455,7 +455,10 @@ def build_ffmpeg_command(plan: RenderPlan, output_path: Path) -> list[str]:
|
||||
|
||||
for event in plan.audio_events:
|
||||
if event.audio_id not in audio_inputs:
|
||||
audio_path = audio_dir / event.audio_def.file
|
||||
if event.audio_def.is_shared and plan.shared_assets_dir:
|
||||
audio_path = plan.shared_assets_dir / "media" / "audio" / event.audio_def.file
|
||||
else:
|
||||
audio_path = audio_dir / event.audio_def.file
|
||||
audio_path, _ = resolve_with_cache(audio_path, project_path)
|
||||
# Use pre-probed duration from audio.json if available (set by import).
|
||||
# For MP3 without Xing/VBRI headers this is critical — FFmpeg otherwise
|
||||
@@ -802,13 +805,14 @@ def build_filter_complex(
|
||||
"""
|
||||
Build the filter_complex string for FFmpeg.
|
||||
|
||||
Layer structure:
|
||||
Layer structure (bottom to top):
|
||||
- Layer 1: Background (solid color, image, or video)
|
||||
- Layer 2: Always visible videos (like talking head) in cutouts
|
||||
- Layer 3: Slides (with time-based enable)
|
||||
- Layer 4: Triggered videos in cutouts (with time-based enable)
|
||||
- Layer 5: Camera transform
|
||||
- Layer 6: Outro videos (fullscreen, after narration ends)
|
||||
- Layer 2: "below" triggered videos (vfb/vsb) — behind talking head
|
||||
- Layer 3: Always visible videos (like talking head) in cutouts
|
||||
- Layer 4: Slides (with time-based enable)
|
||||
- Layer 5: "above" triggered videos (vft/vst) — in front of slides
|
||||
- Layer 6: Camera transform
|
||||
- Layer 7: Outro videos (fullscreen, after narration ends)
|
||||
- Audio: Main audio mixed with triggered sound effects and outro audio
|
||||
"""
|
||||
outro_inputs = outro_inputs or {}
|
||||
@@ -835,6 +839,44 @@ def build_filter_complex(
|
||||
|
||||
current_label = "bg"
|
||||
|
||||
# Add "below" triggered video overlays (vfb/vsb) BEFORE the talking head
|
||||
# so they sit behind it in the composite stack.
|
||||
for i, event in enumerate(plan.video_events):
|
||||
if event.layer != "below":
|
||||
continue
|
||||
video_idx = video_inputs[i]
|
||||
cut_x, cut_y, cut_width, cut_height = _calculate_cutout_position(
|
||||
event.cutout, width, height
|
||||
)
|
||||
|
||||
duration = event.end_time - event.start_time
|
||||
if event.video_source.take is not None:
|
||||
duration = min(duration, event.video_source.take)
|
||||
effective_end = event.start_time + duration
|
||||
|
||||
zoom = event.video_source.zoom
|
||||
zoomed_width = int(cut_width * zoom)
|
||||
zoomed_height = int(cut_height * zoom)
|
||||
|
||||
video_label = f"tvb{i}"
|
||||
start_pts = event.start_time
|
||||
filters.append(
|
||||
f"[{video_idx}:v]format=yuva444p10le,"
|
||||
f"setpts=PTS-STARTPTS+{start_pts:.3f}/TB,"
|
||||
f"scale={zoomed_width}:{zoomed_height}:force_original_aspect_ratio=increase,"
|
||||
f"crop={cut_width}:{cut_height}:(iw-{cut_width})/2:(ih-{cut_height})/2,"
|
||||
f"format=rgba[{video_label}]"
|
||||
)
|
||||
|
||||
next_label = f"tvbbase{i}"
|
||||
enable_expr = f"between(t\\,{event.start_time:.3f}\\,{effective_end:.3f})"
|
||||
filters.append(
|
||||
f"[{current_label}][{video_label}]overlay="
|
||||
f"x={cut_x}:y={cut_y}:enable={enable_expr}"
|
||||
f"[{next_label}]"
|
||||
)
|
||||
current_label = next_label
|
||||
|
||||
# Overlay always_visible videos (like talking head)
|
||||
# If there are narration pauses, we need to segment the video
|
||||
for i, (video_id, video_source, cutout) in enumerate(plan.narration_videos):
|
||||
@@ -898,43 +940,6 @@ def build_filter_complex(
|
||||
)
|
||||
current_label = next_label
|
||||
|
||||
# Add "below-slides" triggered video overlays (vfb/vsb or layer="below")
|
||||
for i, event in enumerate(plan.video_events):
|
||||
if event.layer != "below":
|
||||
continue
|
||||
video_idx = video_inputs[i]
|
||||
cut_x, cut_y, cut_width, cut_height = _calculate_cutout_position(
|
||||
event.cutout, width, height
|
||||
)
|
||||
|
||||
duration = event.end_time - event.start_time
|
||||
if event.video_source.take is not None:
|
||||
duration = min(duration, event.video_source.take)
|
||||
effective_end = event.start_time + duration
|
||||
|
||||
zoom = event.video_source.zoom
|
||||
zoomed_width = int(cut_width * zoom)
|
||||
zoomed_height = int(cut_height * zoom)
|
||||
|
||||
video_label = f"tvb{i}"
|
||||
start_pts = event.start_time
|
||||
filters.append(
|
||||
f"[{video_idx}:v]format=yuva444p10le,"
|
||||
f"setpts=PTS-STARTPTS+{start_pts:.3f}/TB,"
|
||||
f"scale={zoomed_width}:{zoomed_height}:force_original_aspect_ratio=increase,"
|
||||
f"crop={cut_width}:{cut_height}:(iw-{cut_width})/2:(ih-{cut_height})/2,"
|
||||
f"format=rgba[{video_label}]"
|
||||
)
|
||||
|
||||
next_label = f"tvbbase{i}"
|
||||
enable_expr = f"between(t\\,{event.start_time:.3f}\\,{effective_end:.3f})"
|
||||
filters.append(
|
||||
f"[{current_label}][{video_label}]overlay="
|
||||
f"x={cut_x}:y={cut_y}:enable={enable_expr}"
|
||||
f"[{next_label}]"
|
||||
)
|
||||
current_label = next_label
|
||||
|
||||
# Add slide overlays with time-based enable
|
||||
for i, event in enumerate(plan.slide_events):
|
||||
slide_idx = slide_inputs[event.slide_id]
|
||||
|
||||
Reference in New Issue
Block a user