summaryrefslogtreecommitdiff
path: root/apps/web/src/timeline/group-resize/compute-resize.ts
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 19:41:14 +0200
committercashmere <cashmere@autistici.org>2026-07-28 19:41:14 +0200
commite837e7504379e100ff3b56282577ec67bb88bec9 (patch)
treeeec7e6f964725a783a9e907e931862591bb46b55 /apps/web/src/timeline/group-resize/compute-resize.ts
parentda99dec8a2de1f3849aebbf5695c1e812b2dbdf9 (diff)
downloadopencut-e837e7504379e100ff3b56282577ec67bb88bec9.tar.gz
feat: ripple-extend resize, timeline filmstrip, export hardening
Yesterday's verified web session (gates green: 316 tests, tsc, build): - ripple-extend pushes right-hand chain on static-element resize - per-position video filmstrip thumbnails with zoom-adaptive density - fps-aware export bitrate, 2s keyframes, even-dim export canvas - freeze frame uses fixed 3s duration, prompt removed
Diffstat (limited to 'apps/web/src/timeline/group-resize/compute-resize.ts')
-rw-r--r--apps/web/src/timeline/group-resize/compute-resize.ts73
1 files changed, 65 insertions, 8 deletions
diff --git a/apps/web/src/timeline/group-resize/compute-resize.ts b/apps/web/src/timeline/group-resize/compute-resize.ts
index ed335997..2674aa8d 100644
--- a/apps/web/src/timeline/group-resize/compute-resize.ts
+++ b/apps/web/src/timeline/group-resize/compute-resize.ts
@@ -18,6 +18,7 @@ import {
import type {
ComputeGroupResizeArgs,
GroupResizeMember,
+ GroupResizePushTarget,
GroupResizeResult,
GroupResizeUpdate,
ResizeSide,
@@ -103,16 +104,72 @@ export function computeGroupResize({
return {
deltaTime: Object.is(finalDeltaTime, -0) ? ZERO_MEDIA_TIME : finalDeltaTime,
- updates: members.map((member) =>
- buildResizeUpdate({
- member,
- side,
- deltaTime: finalDeltaTime,
- }),
- ),
+ updates: [
+ ...members.map((member) =>
+ buildResizeUpdate({
+ member,
+ side,
+ deltaTime: finalDeltaTime,
+ }),
+ ),
+ ...buildRipplePushUpdates({ members, side, deltaTime: finalDeltaTime }),
+ ],
};
}
+function buildRipplePushUpdates({
+ members,
+ side,
+ deltaTime,
+}: {
+ members: GroupResizeMember[];
+ side: ResizeSide;
+ deltaTime: MediaTime;
+}): GroupResizeUpdate[] {
+ if (side !== "right" || deltaTime <= 0) {
+ return [];
+ }
+
+ const overflowByTarget = new Map<
+ string,
+ { target: GroupResizePushTarget; overflow: MediaTime }
+ >();
+ for (const member of members) {
+ if (
+ member.sourceDuration != null ||
+ member.rightPushChain === undefined ||
+ member.rightNeighborBound === null
+ ) {
+ continue;
+ }
+ const neighborCeiling = subMediaTime({
+ a: member.rightNeighborBound,
+ b: addMediaTime({ a: member.startTime, b: member.duration }),
+ });
+ const overflow = subMediaTime({ a: deltaTime, b: neighborCeiling });
+ if (overflow <= 0) {
+ continue;
+ }
+ for (const target of member.rightPushChain) {
+ const existing = overflowByTarget.get(target.elementId);
+ if (existing === undefined || overflow > existing.overflow) {
+ overflowByTarget.set(target.elementId, { target, overflow });
+ }
+ }
+ }
+
+ return [...overflowByTarget.values()].map(({ target, overflow }) => ({
+ trackId: target.trackId,
+ elementId: target.elementId,
+ patch: {
+ trimStart: target.trimStart,
+ trimEnd: target.trimEnd,
+ startTime: addMediaTime({ a: target.startTime, b: overflow }),
+ duration: target.duration,
+ },
+ }));
+}
+
function buildResizeUpdate({
member,
side,
@@ -219,7 +276,7 @@ function getMaximumAllowedDeltaTime({
b: addMediaTime({ a: member.startTime, b: member.duration }),
});
if (member.sourceDuration == null) {
- return rightNeighborCeiling;
+ return member.rightPushChain === undefined ? rightNeighborCeiling : null;
}
const maximumVisibleSourceSpan = subMediaTime({