Ading cleanup and small improvements
This commit is contained in:
+34
-2
@@ -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
|
||||
|
||||
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
@@ -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}]")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user