Ading cleanup and small improvements
This commit is contained in:
+38
-6
@@ -2185,22 +2185,54 @@ def _prune_manifest(
|
|||||||
if (key.lower() if lowercase_keys else key) not in keep_ids
|
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:
|
if verbose:
|
||||||
print(f" {path.name}: all {len(data)} {label} entries in use.")
|
print(f" {path.name}: all {len(data)} {label} entries in use.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
print(f" {path.name}: removing {len(to_remove)} unused {label} entr"
|
if to_remove:
|
||||||
f"{'y' if len(to_remove) == 1 else 'ies'}:")
|
print(f" {path.name}: removing {len(to_remove)} unused {label} entr"
|
||||||
for key in to_remove:
|
f"{'y' if len(to_remove) == 1 else 'ies'}:")
|
||||||
print(f" - {key}")
|
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:
|
if not dry_run:
|
||||||
for key in to_remove:
|
for key in to_remove:
|
||||||
del data[key]
|
del data[key]
|
||||||
|
for key, _ in dup_remove:
|
||||||
|
del data[key]
|
||||||
_write_json_preserve(path, data)
|
_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:
|
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_width = int(cut_width * zoom)
|
||||||
zoomed_height = int(cut_height * 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}"
|
video_label = f"av{i}"
|
||||||
filters.append(
|
filters.append(
|
||||||
f"{narr_src}fps={plan.config.fps},setpts=PTS-STARTPTS,"
|
f"{narr_src}fps={plan.config.fps},setpts=PTS-STARTPTS,"
|
||||||
@@ -1035,9 +1035,19 @@ def build_filter_complex(
|
|||||||
)
|
)
|
||||||
current_label = next_label
|
current_label = next_label
|
||||||
else:
|
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(
|
segments = _build_narration_segments(
|
||||||
plan.narration_pauses, plan.total_duration
|
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(
|
for seg_idx, (src_start, src_end, out_start, out_end) in enumerate(
|
||||||
segments
|
segments
|
||||||
@@ -1045,7 +1055,7 @@ def build_filter_complex(
|
|||||||
seg_label = f"av{i}_seg{seg_idx}"
|
seg_label = f"av{i}_seg{seg_idx}"
|
||||||
pts_offset = out_start
|
pts_offset = out_start
|
||||||
filters.append(
|
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"setpts=PTS-STARTPTS+{pts_offset:.3f}/TB,"
|
||||||
f"format=yuva444p10le,"
|
f"format=yuva444p10le,"
|
||||||
f"scale={zoomed_width}:{zoomed_height}:force_original_aspect_ratio=increase,"
|
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
|
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)
|
# Simple case: trim main audio to end before outro (with optional channel and volume filters)
|
||||||
filter_parts = []
|
filter_parts = []
|
||||||
if channel_filter:
|
if channel_filter:
|
||||||
@@ -1299,8 +1309,17 @@ def build_filter_complex(
|
|||||||
else:
|
else:
|
||||||
audio_labels_to_mix.append(f"{_main_aud}")
|
audio_labels_to_mix.append(f"{_main_aud}")
|
||||||
else:
|
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)
|
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(
|
for seg_idx, (src_start, src_end, out_start, out_end) in enumerate(
|
||||||
segments
|
segments
|
||||||
):
|
):
|
||||||
@@ -1317,7 +1336,7 @@ def build_filter_complex(
|
|||||||
if volume_filter:
|
if volume_filter:
|
||||||
filter_parts.append(volume_filter)
|
filter_parts.append(volume_filter)
|
||||||
filters.append(
|
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}]")
|
audio_labels_to_mix.append(f"[{seg_label}]")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user