import { describe, expect, it } from "vitest"; import { computeEditRange, cutSpan, frameRegion, isHandleFrame } from "./handles"; describe("computeEditRange", () => { it("is null when no handles are marked", () => { expect(computeEditRange(null, 100)).toBeNull(); expect(computeEditRange({}, 100)).toBeNull(); expect(computeEditRange({ handleHead: 0, handleTail: 0 }, 100)).toBeNull(); }); it("is null until the media length is known", () => { expect(computeEditRange({ handleHead: 8, handleTail: 8 }, 0)).toBeNull(); }); it("marks 8+8 on media with no slate", () => { expect(computeEditRange({ handleHead: 8, handleTail: 8, frameCount: 100 }, 100)).toEqual({ pictureStart: 0, cutIn: 8, tailStart: 92, pictureEnd: 100, }); }); it("infers a 1-frame head slate from the media being one frame longer than the EXRs", () => { // Pipeline preview: 100 EXR frames + 1 slate frame = 101 in the MP4 expect(computeEditRange({ handleHead: 8, handleTail: 8, frameCount: 100 }, 101)).toEqual({ pictureStart: 1, cutIn: 9, tailStart: 93, pictureEnd: 101, }); }); it("uses a recorded picture start over inference", () => { const r = computeEditRange( { handleHead: 8, handleTail: 8, frameCount: 100, pictureStartFrame: 2 }, 102 ); expect(r).toEqual({ pictureStart: 2, cutIn: 10, tailStart: 94, pictureEnd: 102 }); }); it("anchors the tail on the picture when the container rounds a frame short", () => { // Recorded 1-frame slate, 100 EXRs, but the MP4 duration floors to 100 const r = computeEditRange( { handleHead: 8, handleTail: 8, frameCount: 100, pictureStartFrame: 1 }, 100 ); expect(r).toEqual({ pictureStart: 1, cutIn: 9, tailStart: 92, pictureEnd: 100 }); }); it("doesn't mistake a large unexplained length difference for a slate", () => { const r = computeEditRange({ handleHead: 8, handleTail: 8, frameCount: 50 }, 100); expect(r?.pictureStart).toBe(0); }); it("supports unequal and one-sided handles", () => { expect(computeEditRange({ handleHead: 12, handleTail: 0, frameCount: 100 }, 100)).toEqual({ pictureStart: 0, cutIn: 12, tailStart: 100, pictureEnd: 100, }); }); it("never lets the handles overlap on a very short shot", () => { const r = computeEditRange({ handleHead: 8, handleTail: 8, frameCount: 10 }, 10)!; expect(r.cutIn).toBe(8); expect(r.tailStart).toBe(8); }); }); describe("frameRegion", () => { const range = computeEditRange({ handleHead: 8, handleTail: 8, frameCount: 100 }, 101); it("classifies slate, handles and edit", () => { expect(frameRegion(range, 0)).toBe("slate"); expect(frameRegion(range, 1)).toBe("head"); expect(frameRegion(range, 8)).toBe("head"); expect(frameRegion(range, 9)).toBe("edit"); expect(frameRegion(range, 92)).toBe("edit"); expect(frameRegion(range, 93)).toBe("tail"); expect(frameRegion(range, 100)).toBe("tail"); }); it("treats everything as edit when handles are unknown", () => { expect(frameRegion(null, 0)).toBe("edit"); expect(isHandleFrame(null, 0)).toBe(false); }); it("does not count the slate as a handle", () => { expect(isHandleFrame(range, 0)).toBe(false); expect(isHandleFrame(range, 1)).toBe(true); }); }); describe("cutSpan", () => { it("plays the cut when handles are marked, skipping slate and handles", () => { // 1 slate + 8 head + 84 cut + 8 tail = 101 MP4 frames expect(cutSpan({ handleHead: 8, handleTail: 8, frameCount: 100 }, 101)).toEqual({ start: 9, end: 93, }); }); it("plays the whole picture minus the slate when no handles are marked", () => { expect(cutSpan({ frameCount: 100 }, 101)).toEqual({ start: 1, end: 101 }); expect(cutSpan({ pictureStartFrame: 2 }, 50)).toEqual({ start: 2, end: 50 }); }); it("plays the whole media when nothing is known about it", () => { expect(cutSpan(null, 48)).toEqual({ start: 0, end: 48 }); expect(cutSpan({}, 48)).toEqual({ start: 0, end: 48 }); }); it("is null until the media length is known", () => { expect(cutSpan({ handleHead: 8, handleTail: 8 }, 0)).toBeNull(); }); it("never returns an empty span", () => { // Handles longer than the picture expect(cutSpan({ handleHead: 30, handleTail: 30, frameCount: 40 }, 40)).toEqual({ start: 0, end: 40, }); }); });