Ading cleanup and small improvements

This commit is contained in:
2026-07-16 16:00:19 +02:00
parent 24b04fd55e
commit fa8512ff3d
2 changed files with 62 additions and 11 deletions
+38 -6
View File
@@ -2185,22 +2185,54 @@ def _prune_manifest(
if (key.lower() if lowercase_keys else key) not in keep_ids
]
if not to_remove:
# Case-collision duplicates: when keys are case-insensitive (videos), several
# keys can share the same lowercase form (e.g. "KnightRotating" and
# "knightrotating"). Markers resolve by lowercased id, so only the exact
# lowercase key is ever reachable — the other case-variants are dead weight.
# Collapse each surviving collision group to its lowercase canonical.
dup_remove: list[tuple[str, str]] = [] # (removed_key, kept_canonical_key)
if lowercase_keys:
remove_set = set(to_remove)
groups: dict[str, list[str]] = {}
for key in data:
if key in remove_set:
continue
groups.setdefault(key.lower(), []).append(key)
for low, keys in groups.items():
if len(keys) < 2:
continue
# Keep the already-lowercase key (what the renderer resolves); else
# the first-seen, so at least one entry survives.
canonical = next((k for k in keys if k == low), keys[0])
for key in keys:
if key != canonical:
dup_remove.append((key, canonical))
if not to_remove and not dup_remove:
if verbose:
print(f" {path.name}: all {len(data)} {label} entries in use.")
return 0
print(f" {path.name}: removing {len(to_remove)} unused {label} entr"
f"{'y' if len(to_remove) == 1 else 'ies'}:")
for key in to_remove:
print(f" - {key}")
if to_remove:
print(f" {path.name}: removing {len(to_remove)} unused {label} entr"
f"{'y' if len(to_remove) == 1 else 'ies'}:")
for key in to_remove:
print(f" - {key}")
if dup_remove:
print(f" {path.name}: removing {len(dup_remove)} case-duplicate {label} entr"
f"{'y' if len(dup_remove) == 1 else 'ies'}:")
for key, canonical in dup_remove:
print(f" - {key} (duplicate of '{canonical}')")
if not dry_run:
for key in to_remove:
del data[key]
for key, _ in dup_remove:
del data[key]
_write_json_preserve(path, data)
return len(to_remove)
return len(to_remove) + len(dup_remove)
def _prune_narration(project_path: Path, verbose: bool, dry_run: bool) -> int:
+24 -5
View File
@@ -1019,7 +1019,7 @@ def build_filter_complex(
zoomed_width = int(cut_width * zoom)
zoomed_height = int(cut_height * zoom)
if not plan.narration_pauses or narration_concat:
if not plan.narration_pauses:
video_label = f"av{i}"
filters.append(
f"{narr_src}fps={plan.config.fps},setpts=PTS-STARTPTS,"
@@ -1035,9 +1035,19 @@ def build_filter_complex(
)
current_label = next_label
else:
# Freeze-splice: cut the narration video at each pause and re-time the
# remainder so it resumes exactly where it stopped (the cutscene owns
# the frame during the gap). narr_src may be the concat filter output
# [narrsrc_v], which is single-consumer, so split it into one branch
# per segment before trimming.
segments = _build_narration_segments(
plan.narration_pauses, plan.total_duration
)
split_labels = [f"nvsplit{i}_{k}" for k in range(len(segments))]
filters.append(
f"{narr_src}split={len(segments)}"
+ "".join(f"[{lbl}]" for lbl in split_labels)
)
for seg_idx, (src_start, src_end, out_start, out_end) in enumerate(
segments
@@ -1045,7 +1055,7 @@ def build_filter_complex(
seg_label = f"av{i}_seg{seg_idx}"
pts_offset = out_start
filters.append(
f"{narr_src}trim={src_start:.3f}:{src_end:.3f},"
f"[{split_labels[seg_idx]}]trim={src_start:.3f}:{src_end:.3f},"
f"setpts=PTS-STARTPTS+{pts_offset:.3f}/TB,"
f"format=yuva444p10le,"
f"scale={zoomed_width}:{zoomed_height}:force_original_aspect_ratio=increase,"
@@ -1275,7 +1285,7 @@ def build_filter_complex(
plan.narration_end_time if plan.outro_events else plan.total_duration
)
if not plan.narration_pauses or narration_concat:
if not plan.narration_pauses:
# Simple case: trim main audio to end before outro (with optional channel and volume filters)
filter_parts = []
if channel_filter:
@@ -1299,8 +1309,17 @@ def build_filter_complex(
else:
audio_labels_to_mix.append(f"{_main_aud}")
else:
# Complex case: segment the narration audio for pauses
# Freeze-splice: cut the narration audio at each pause and delay the
# remainder so it resumes on the exact sample it stopped on (the
# cutscene's own audio fills the gap). _main_aud may be the concat
# output [narrsrc_a], which is single-consumer, so asplit it into one
# branch per segment first.
segments = _build_narration_segments(plan.narration_pauses, audio_end_time)
asplit_labels = [f"narr_aud_split{k}" for k in range(len(segments))]
filters.append(
f"{_main_aud}asplit={len(segments)}"
+ "".join(f"[{lbl}]" for lbl in asplit_labels)
)
for seg_idx, (src_start, src_end, out_start, out_end) in enumerate(
segments
):
@@ -1317,7 +1336,7 @@ def build_filter_complex(
if volume_filter:
filter_parts.append(volume_filter)
filters.append(
f"{_main_aud}{','.join(filter_parts)}[{seg_label}]"
f"[{asplit_labels[seg_idx]}]{','.join(filter_parts)}[{seg_label}]"
)
audio_labels_to_mix.append(f"[{seg_label}]")