Fixing audio that doesn't silence

This commit is contained in:
2026-08-02 21:12:43 +02:00
parent eeaada77d4
commit be5173aeb1
2 changed files with 75 additions and 9 deletions
+23 -2
View File
@@ -841,9 +841,22 @@ def _import_shared_audio(
existing: dict = _read_json(audio_json_path) if audio_json_path.exists() else {}
added = 0
normalized = 0
for f in audio_files:
audio_id = f.stem.lower()
if audio_id in existing:
# Backfill the audio schema onto an existing entry: make `loop`
# explicit (so it's not an invisible optional the way it was before),
# and drop `zoom` — a video-only field that means nothing for audio and
# only ended up here from legacy/hand-edited entries.
entry = existing[audio_id]
if isinstance(entry, dict):
if "loop" not in entry:
entry["loop"] = None
normalized += 1
if "zoom" in entry:
del entry["zoom"]
normalized += 1
if verbose:
print(f" Skipping {audio_id} (already in audio.json)")
continue
@@ -851,16 +864,24 @@ def _import_shared_audio(
"file": f.name,
"is_shared": True,
"volume": 1.0,
# One-shot by default (null → not looping); set true for background
# music/ambience that should loop for the whole video.
"loop": None,
}
added += 1
if verbose:
print(f" Added shared audio: {audio_id}")
if added > 0:
if added > 0 or normalized > 0:
with open(audio_json_path, "w", encoding="utf-8") as fh:
json.dump(existing, fh, indent=2)
parts = []
if added:
parts.append(f"+{added} shared audio files")
if normalized:
parts.append(f"{normalized} field(s) normalized")
print(
f" Updated {audio_json_path.relative_to(project_path)} (+{added} shared audio files)"
f" Updated {audio_json_path.relative_to(project_path)} ({', '.join(parts)})"
)
else:
if verbose:
+52 -7
View File
@@ -1544,16 +1544,61 @@ def build_filter_complex(
)
audio_labels_to_mix.append(f"[{label}]")
else:
# One-shot audio: delay to trigger time. Chunking v2: seek in if
# the clip began in an earlier chunk (docs/chunking_v2.md).
# One-shot audio. Freeze it through narration pauses too (like the
# looping branch above): split the source at each pause and delay
# the remainder, so the clip resumes on the exact sample it stopped
# on when the cutscene ends — it is never restarted. A pause set on
# a cutscene video therefore silences background one-shots for its
# duration. `ignore_pauses` opts a clip out (e.g. a stinger meant to
# keep playing under the freeze). Chunking v2: src_offset seeks in
# when the clip began in an earlier chunk (docs/chunking_v2.md).
label = f"aud{i}"
delay_ms = int(event.start_time * 1000)
_off = getattr(event, "src_offset", 0.0)
_seek = f"atrim={_off:.3f},asetpts=PTS-STARTPTS," if _off > 0 else ""
filters.append(
f"[{audio_idx}:a]{_seek}adelay={delay_ms}|{delay_ms},volume={volume:.2f}[{label}]"
relevant_pauses = (
[]
if event.audio_def.ignore_pauses
else [
p
for p in (plan.narration_pauses or [])
if p.output_time > event.start_time
]
)
audio_labels_to_mix.append(f"[{label}]")
if not relevant_pauses:
delay_ms = int(event.start_time * 1000)
_seek = f"atrim={_off:.3f},asetpts=PTS-STARTPTS," if _off > 0 else ""
filters.append(
f"[{audio_idx}:a]{_seek}adelay={delay_ms}|{delay_ms},volume={volume:.2f}[{label}]"
)
audio_labels_to_mix.append(f"[{label}]")
else:
# Play [seg_start, pause) of source, freeze during the pause,
# then resume — source position (src_pos) never advances across
# the gap. Final segment runs to the source's natural end.
src_pos = _off
seg_start = event.start_time
seg_count = 0
for pause in relevant_pauses:
if pause.output_time > seg_start:
seg_dur = pause.output_time - seg_start
seg_label = f"{label}_seg{seg_count}"
d_ms = int(seg_start * 1000)
filters.append(
f"[{audio_idx}:a]atrim={src_pos:.3f}:{src_pos + seg_dur:.3f},"
f"asetpts=PTS-STARTPTS,adelay={d_ms}|{d_ms},"
f"volume={volume:.2f}[{seg_label}]"
)
audio_labels_to_mix.append(f"[{seg_label}]")
src_pos += seg_dur
seg_count += 1
seg_start = pause.output_time + pause.duration
seg_label = f"{label}_seg{seg_count}"
d_ms = int(seg_start * 1000)
filters.append(
f"[{audio_idx}:a]atrim={src_pos:.3f},"
f"asetpts=PTS-STARTPTS,adelay={d_ms}|{d_ms},"
f"volume={volume:.2f}[{seg_label}]"
)
audio_labels_to_mix.append(f"[{seg_label}]")
# Extract and mix audio from triggered video events
_have_audio = video_events_with_audio or set()