Advance WebGPU volume and bounded workflows
This commit is contained in:
@@ -67,6 +67,21 @@ export interface SequencerRuntimeCapabilityIR {
|
||||
localEncoding: "BLOCKED";
|
||||
}
|
||||
|
||||
export interface SequencerFrameStripIR {
|
||||
stripId: string;
|
||||
channel: number;
|
||||
sourceFrame: number;
|
||||
dependencyStripIds: string[];
|
||||
}
|
||||
|
||||
export interface SequencerTransitionFrameIR {
|
||||
effectStripId: string;
|
||||
effectType: "CROSS" | "GAMMA_CROSS";
|
||||
factor: number;
|
||||
from: { stripId: string; sourceFrame: number };
|
||||
to: { stripId: string; sourceFrame: number };
|
||||
}
|
||||
|
||||
export class SequencerValidationError extends Error {
|
||||
readonly code: ErrorCode;
|
||||
|
||||
@@ -236,6 +251,54 @@ export function sequencerSourceFrame(strip: SequencerStripIR, timelineFrame: num
|
||||
return Math.min(strip.sourceEnd, Math.max(strip.sourceStart, strip.sourceStart + (timelineFrame - strip.frameStart) * strip.speed));
|
||||
}
|
||||
|
||||
export function resolveSequencerFrame(value: unknown, timelineFrame: number): SequencerFrameStripIR[] {
|
||||
const timeline = parseSequencerTimeline(value);
|
||||
if (!Number.isFinite(timelineFrame) || timelineFrame < timeline.frameStart || timelineFrame > timeline.frameEnd) throw new SequencerValidationError("SEQUENCER_SCHEMA_INVALID", `Timeline frame ${timelineFrame} is outside the scene range`);
|
||||
const active = timeline.strips.filter((strip) => !strip.muted && timelineFrame >= strip.frameStart && timelineFrame < strip.frameEnd);
|
||||
const activeIds = new Set(active.map((strip) => strip.id));
|
||||
const hiddenByMeta = new Set(active.filter((strip) => strip.type === "META").flatMap((strip) => strip.childStripIds ?? []));
|
||||
const result = active.filter((strip) => !hiddenByMeta.has(strip.id)).map((strip): SequencerFrameStripIR => {
|
||||
const dependencies = [...(strip.inputStripIds ?? []), ...(strip.childStripIds ?? [])];
|
||||
if (dependencies.some((id) => !activeIds.has(id))) throw new SequencerValidationError("SEQUENCER_RESOURCE_MISSING", `${strip.id} has an inactive frame dependency`);
|
||||
return { stripId: strip.id, channel: strip.channel, sourceFrame: sequencerSourceFrame(strip, timelineFrame), dependencyStripIds: [...dependencies] };
|
||||
});
|
||||
result.sort((left, right) => left.channel - right.channel || left.stripId.localeCompare(right.stripId));
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Resolves the ordered inputs and bounded progress for the verified cross-transition subset. */
|
||||
export function resolveSequencerTransitionFrame(
|
||||
value: unknown,
|
||||
effectStripId: string,
|
||||
timelineFrame: number,
|
||||
): SequencerTransitionFrameIR {
|
||||
const timeline = parseSequencerTimeline(value);
|
||||
const effect = timeline.strips.find((strip) => strip.id === effectStripId);
|
||||
if (!effect || effect.type !== "EFFECT" ||
|
||||
(effect.effectType !== "CROSS" && effect.effectType !== "GAMMA_CROSS") ||
|
||||
effect.inputStripIds?.length !== 2) {
|
||||
throw new SequencerValidationError("SEQUENCER_SCHEMA_INVALID", `${effectStripId} is not a supported two-input cross transition`);
|
||||
}
|
||||
if (!Number.isFinite(timelineFrame) || timelineFrame < effect.frameStart || timelineFrame >= effect.frameEnd) {
|
||||
throw new SequencerValidationError("SEQUENCER_SCHEMA_INVALID", `${effectStripId} is inactive at frame ${timelineFrame}`);
|
||||
}
|
||||
const [fromId, toId] = effect.inputStripIds;
|
||||
const from = timeline.strips.find((strip) => strip.id === fromId);
|
||||
const to = timeline.strips.find((strip) => strip.id === toId);
|
||||
if (!from || !to || from.muted || to.muted || timelineFrame < from.frameStart || timelineFrame >= from.frameEnd ||
|
||||
timelineFrame < to.frameStart || timelineFrame >= to.frameEnd) {
|
||||
throw new SequencerValidationError("SEQUENCER_RESOURCE_MISSING", `${effectStripId} has an inactive transition input`);
|
||||
}
|
||||
const factor = (timelineFrame - effect.frameStart) / (effect.frameEnd - effect.frameStart);
|
||||
return {
|
||||
effectStripId,
|
||||
effectType: effect.effectType,
|
||||
factor,
|
||||
from: { stripId: from.id, sourceFrame: sequencerSourceFrame(from, timelineFrame) },
|
||||
to: { stripId: to.id, sourceFrame: sequencerSourceFrame(to, timelineFrame) },
|
||||
};
|
||||
}
|
||||
|
||||
export function sequencerRuntimeCapabilities(scope: typeof globalThis = globalThis): SequencerRuntimeCapabilityIR {
|
||||
return {
|
||||
webCodecsVideo: "VideoDecoder" in scope ? "PROBE_REQUIRED" : "UNAVAILABLE",
|
||||
|
||||
Reference in New Issue
Block a user