feat: rewrite export to use per-keyframe crop modes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2793,35 +2793,34 @@ class MainWindow(QMainWindow):
|
|||||||
out = build_export_path(folder, name, self._export_counter, sub=sub)
|
out = build_export_path(folder, name, self._export_counter, sub=sub)
|
||||||
jobs.append((start, out, base_ratio, base_center))
|
jobs.append((start, out, base_ratio, base_center))
|
||||||
|
|
||||||
# Apply crop keyframes: each sub-clip uses the latest keyframe
|
# Apply crop keyframes (or fall back to base state).
|
||||||
# at or before its start time (keyframes set in lock mode).
|
|
||||||
if self._crop_keyframes:
|
|
||||||
for i, (s, o, r, c) in enumerate(jobs):
|
|
||||||
center = base_center
|
|
||||||
for kt, kc in self._crop_keyframes:
|
|
||||||
if kt <= s + 0.05:
|
|
||||||
center = kc
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
jobs[i] = (s, o, r, center)
|
|
||||||
|
|
||||||
# Random crop: ~1 per 3 clips gets a random crop + random position.
|
|
||||||
# When both portrait and square are on, they share the quota.
|
|
||||||
rand_portrait = self._chk_rand_portrait.isChecked()
|
rand_portrait = self._chk_rand_portrait.isChecked()
|
||||||
rand_square = self._chk_rand_square.isChecked()
|
rand_square = self._chk_rand_square.isChecked()
|
||||||
if (rand_portrait or rand_square) and n_clips > 1:
|
widened = apply_keyframes_to_jobs(
|
||||||
n_random = max(1, n_clips // 3)
|
jobs, self._crop_keyframes,
|
||||||
indices = random.sample(range(n_clips), n_random)
|
base_center=base_center, base_ratio=base_ratio,
|
||||||
# Build pool of ratios to assign
|
base_rand_p=rand_portrait, base_rand_s=rand_square,
|
||||||
if rand_portrait and rand_square:
|
)
|
||||||
ratios = ["9:16", "1:1"]
|
|
||||||
elif rand_portrait:
|
# Random crop: eligible clips (per their keyframe flags) have
|
||||||
ratios = ["9:16"]
|
# ~1 in 3 chance of getting a random ratio applied.
|
||||||
else:
|
portrait_eligible = [i for i, w in enumerate(widened) if w[4]]
|
||||||
ratios = ["1:1"]
|
square_eligible = [i for i, w in enumerate(widened) if w[5]]
|
||||||
for idx in indices:
|
rand_indices: dict[int, list[str]] = {}
|
||||||
s, o, _, c = jobs[idx]
|
if portrait_eligible and n_clips > 1:
|
||||||
jobs[idx] = (s, o, random.choice(ratios), c)
|
n = max(1, len(portrait_eligible) // 3)
|
||||||
|
for i in random.sample(portrait_eligible, min(n, len(portrait_eligible))):
|
||||||
|
rand_indices.setdefault(i, []).append("9:16")
|
||||||
|
if square_eligible and n_clips > 1:
|
||||||
|
n = max(1, len(square_eligible) // 3)
|
||||||
|
for i in random.sample(square_eligible, min(n, len(square_eligible))):
|
||||||
|
rand_indices.setdefault(i, []).append("1:1")
|
||||||
|
|
||||||
|
jobs = []
|
||||||
|
for i, (s, o, ratio, center, _rp, _rs) in enumerate(widened):
|
||||||
|
if i in rand_indices:
|
||||||
|
ratio = random.choice(rand_indices[i])
|
||||||
|
jobs.append((s, o, ratio, center))
|
||||||
|
|
||||||
# Subject tracking: re-detect crop center per sub-clip.
|
# Subject tracking: re-detect crop center per sub-clip.
|
||||||
if self._chk_track.isChecked() and any(j[2] for j in jobs):
|
if self._chk_track.isChecked() and any(j[2] for j in jobs):
|
||||||
|
|||||||
Reference in New Issue
Block a user