2e9fa45580
Deploy / deploy (push) Successful in 3m31s
Renders run 8 frames past the cut at each end. Directors were flagging glitches in unfixed handles as shot problems, so the player now marks them — without trimming or burning anything into the EXRs/MOV/MP4. - Version.handleHead/handleTail/pictureStartFrame, Export.handleHead/ handleTail; migration backfills pipeline exports and their preview versions from the manifest's handleFrames (else 8) - AE panel: "Includes handles [8] frames each end" -> manifest.review; older manifests fall back to source.handleFrames - finalize copies handles onto the Version; worker 1.6.4 reports the head slate length (pictureStartSeconds), else the player infers it as MP4 frames - EXR frameCount - Player: hatched HANDLE / SLATE regions and cut marks on the timeline, "HEAD/TAIL HANDLE · NOT IN EDIT" overlay on handle frames, HANDLE badge on notes, "Edit range only" toggle (default on in the client portal) - Staff Handles editor on the review page for manual uploads/corrections Also fixes two player bugs found while testing: timeToFrame floored float error (every seek landed a frame early), and J/reverse never ran (the keyboard effect's cleanup stopped it on the next render). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
3.4 KiB
TypeScript
105 lines
3.4 KiB
TypeScript
import { create } from "zustand";
|
|
import type { AnnotationTool } from "@/types";
|
|
import { ASPECT_OFF, type BlankingMode } from "@/lib/aspect-overlays";
|
|
import type { EditRange } from "@/lib/handles";
|
|
|
|
interface ReviewPlayerStore {
|
|
// Playback
|
|
isPlaying: boolean;
|
|
currentFrame: number;
|
|
currentTime: number;
|
|
duration: number;
|
|
playbackRate: number;
|
|
volume: number;
|
|
isMuted: boolean;
|
|
isFullscreen: boolean;
|
|
|
|
// Annotation
|
|
isAnnotating: boolean;
|
|
selectedTool: AnnotationTool;
|
|
selectedColor: string;
|
|
strokeWidth: number;
|
|
showAnnotations: boolean;
|
|
|
|
// Delivery blanking — a viewing aid only, never part of the saved review
|
|
aspectPresetId: string;
|
|
blankingMode: BlankingMode;
|
|
|
|
// Config (set from version data)
|
|
fps: number;
|
|
totalFrames: number;
|
|
|
|
// Handles — where the cut sits in the media (null: no handles marked), and
|
|
// whether playback keeps to it. Viewing aids only; the media is untouched.
|
|
editRange: EditRange | null;
|
|
editRangeOnly: boolean;
|
|
|
|
// Actions
|
|
setPlaying: (playing: boolean) => void;
|
|
setCurrentFrame: (frame: number) => void;
|
|
setCurrentTime: (time: number) => void;
|
|
setDuration: (duration: number) => void;
|
|
setPlaybackRate: (rate: number) => void;
|
|
setVolume: (volume: number) => void;
|
|
setMuted: (muted: boolean) => void;
|
|
setFullscreen: (fullscreen: boolean) => void;
|
|
setAnnotating: (annotating: boolean) => void;
|
|
setSelectedTool: (tool: AnnotationTool) => void;
|
|
setSelectedColor: (color: string) => void;
|
|
setStrokeWidth: (width: number) => void;
|
|
setShowAnnotations: (show: boolean) => void;
|
|
setAspectPresetId: (id: string) => void;
|
|
setBlankingMode: (mode: BlankingMode) => void;
|
|
setFps: (fps: number) => void;
|
|
setTotalFrames: (frames: number) => void;
|
|
setEditRange: (range: EditRange | null) => void;
|
|
setEditRangeOnly: (on: boolean) => void;
|
|
reset: () => void;
|
|
}
|
|
|
|
const initialState = {
|
|
isPlaying: false,
|
|
currentFrame: 0,
|
|
currentTime: 0,
|
|
duration: 0,
|
|
playbackRate: 1,
|
|
volume: 1,
|
|
isMuted: false,
|
|
isFullscreen: false,
|
|
isAnnotating: false,
|
|
selectedTool: "freehand" as AnnotationTool,
|
|
selectedColor: "#ef4444",
|
|
strokeWidth: 2,
|
|
showAnnotations: true,
|
|
aspectPresetId: ASPECT_OFF,
|
|
blankingMode: "solid" as BlankingMode,
|
|
fps: 24,
|
|
totalFrames: 0,
|
|
editRange: null as EditRange | null,
|
|
editRangeOnly: false,
|
|
};
|
|
|
|
export const useReviewStore = create<ReviewPlayerStore>((set) => ({
|
|
...initialState,
|
|
setPlaying: (isPlaying) => set({ isPlaying }),
|
|
setCurrentFrame: (currentFrame) => set({ currentFrame }),
|
|
setCurrentTime: (currentTime) => set({ currentTime }),
|
|
setDuration: (duration) => set({ duration }),
|
|
setPlaybackRate: (playbackRate) => set({ playbackRate }),
|
|
setVolume: (volume) => set({ volume }),
|
|
setMuted: (isMuted) => set({ isMuted }),
|
|
setFullscreen: (isFullscreen) => set({ isFullscreen }),
|
|
setAnnotating: (isAnnotating) => set({ isAnnotating }),
|
|
setSelectedTool: (selectedTool) => set({ selectedTool }),
|
|
setSelectedColor: (selectedColor) => set({ selectedColor }),
|
|
setStrokeWidth: (strokeWidth) => set({ strokeWidth }),
|
|
setShowAnnotations: (showAnnotations) => set({ showAnnotations }),
|
|
setAspectPresetId: (aspectPresetId) => set({ aspectPresetId }),
|
|
setBlankingMode: (blankingMode) => set({ blankingMode }),
|
|
setFps: (fps) => set({ fps }),
|
|
setTotalFrames: (totalFrames) => set({ totalFrames }),
|
|
setEditRange: (editRange) => set({ editRange }),
|
|
setEditRangeOnly: (editRangeOnly) => set({ editRangeOnly }),
|
|
reset: () => set(initialState),
|
|
}));
|