cursedbelt 5.112.0 → 5.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/react/activity-bar/ActivityBar.d.ts.map +1 -1
  2. package/dist/react/activity-bar/ActivityBar.js +8 -2
  3. package/dist/react/activity-bar/ActivityBar.js.map +1 -1
  4. package/dist/react/activity-bar/activityBar.css +4 -0
  5. package/dist/react/media/CutTimeline.d.ts +31 -0
  6. package/dist/react/media/CutTimeline.d.ts.map +1 -0
  7. package/dist/react/media/CutTimeline.js +197 -0
  8. package/dist/react/media/CutTimeline.js.map +1 -0
  9. package/dist/react/media/VideoCutEditor.d.ts +32 -6
  10. package/dist/react/media/VideoCutEditor.d.ts.map +1 -1
  11. package/dist/react/media/VideoCutEditor.js +97 -63
  12. package/dist/react/media/VideoCutEditor.js.map +1 -1
  13. package/dist/react/media/cutTimelineMath.d.ts +80 -0
  14. package/dist/react/media/cutTimelineMath.d.ts.map +1 -0
  15. package/dist/react/media/cutTimelineMath.js +116 -0
  16. package/dist/react/media/cutTimelineMath.js.map +1 -0
  17. package/dist/react/media/useFrameRate.d.ts +34 -0
  18. package/dist/react/media/useFrameRate.d.ts.map +1 -0
  19. package/dist/react/media/useFrameRate.js +66 -0
  20. package/dist/react/media/useFrameRate.js.map +1 -0
  21. package/dist/react/media/videoCutEditor.css +249 -63
  22. package/dist/react/media-gallery/MediaGallery.d.ts +10 -1
  23. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  24. package/dist/react/media-gallery/MediaGallery.js +29 -7
  25. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  26. package/dist/react/media-gallery/galleryDeepLink.d.ts +14 -0
  27. package/dist/react/media-gallery/galleryDeepLink.d.ts.map +1 -1
  28. package/dist/react/media-gallery/galleryDeepLink.js +44 -5
  29. package/dist/react/media-gallery/galleryDeepLink.js.map +1 -1
  30. package/dist/react/media-gallery/index.d.ts +1 -1
  31. package/dist/react/media-gallery/index.d.ts.map +1 -1
  32. package/dist/react/media-gallery/index.js +1 -1
  33. package/dist/react/media-gallery/index.js.map +1 -1
  34. package/dist/react/media-gallery/types.d.ts +5 -0
  35. package/dist/react/media-gallery/types.d.ts.map +1 -1
  36. package/dist/testing/designAudit.d.ts +8 -1
  37. package/dist/testing/designAudit.d.ts.map +1 -1
  38. package/dist/testing/designAudit.js +21 -0
  39. package/dist/testing/designAudit.js.map +1 -1
  40. package/package.json +5 -3
  41. package/scripts/demoServer.ts +1 -1
  42. package/scripts/guardrails-baseline.json +1 -1
  43. package/src/demoFixture.spec.ts +17 -5
  44. package/src/demoRun.spec.ts +10 -1
  45. package/src/react/activity-bar/ActivityBar.spec.tsx +15 -0
  46. package/src/react/activity-bar/ActivityBar.tsx +8 -2
  47. package/src/react/activity-bar/activityBar.css +4 -0
  48. package/src/react/media/CutTimeline.tsx +379 -0
  49. package/src/react/media/VideoCutEditor.spec.tsx +1 -1
  50. package/src/react/media/VideoCutEditor.tsx +277 -214
  51. package/src/react/media/cutTimelineMath.spec.ts +90 -0
  52. package/src/react/media/cutTimelineMath.ts +140 -0
  53. package/src/react/media/useFrameRate.ts +96 -0
  54. package/src/react/media/videoCutEditor.css +249 -63
  55. package/src/react/media-gallery/MediaGallery.tsx +38 -7
  56. package/src/react/media-gallery/galleryDeepLink.spec.tsx +87 -2
  57. package/src/react/media-gallery/galleryDeepLink.ts +58 -5
  58. package/src/react/media-gallery/index.ts +1 -0
  59. package/src/react/media-gallery/types.ts +5 -0
  60. package/src/testing/designAudit.ts +22 -1
@@ -2,32 +2,44 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import './videoCutEditor.css';
3
3
  import React from 'react';
4
4
  import { useEditorVideoSource } from './hlsSource.js';
5
- import { Scissors, X } from 'lucide-react';
5
+ import { ArrowLeft, Pause, Play, Scissors, StepBack, StepForward, Undo2 } from 'lucide-react';
6
6
  import { ActionBar } from '../components/ActionBar.js';
7
7
  import { Button } from '../components/Button.js';
8
+ import { Dialog, DialogContent, DialogTitle, focusPanelOnOpen } from '../components/dialog.js';
9
+ import { IconButton } from '../components/IconButton.js';
10
+ import { useDiscardGuard } from '../hooks/useDiscardGuard.js';
8
11
  import { InfoHint } from '../components/InfoHint.js';
9
12
  import { Tooltip } from '../components/Tooltip.js';
10
- import { Modal } from '../components/Modal.js';
11
13
  import { addSplit, cutSeconds, cutTimecode, EMPTY_PLAN, keptSeconds, planCuts, planProblem, planSegments, removeSplit, toggleSegment, } from 'cursedbelt-core/media/video-cuts';
12
14
  import { cn, RAW_BUTTON } from '../lib/cn.js';
13
15
  import { Kbd } from '../palette/Kbd.js';
14
- export function VideoCutEditor({ src, hlsUrl, posterUrl, resolving = false, name, durationSeconds, open = true, onClose, onSubmit, submitLabel = 'Send for processing', note, ...passThrough }) {
16
+ import { CutTimeline } from './CutTimeline.js';
17
+ import { frameAt, frameStart, frameTimecode, snapToFrame } from './cutTimelineMath.js';
18
+ import { useFrameRate } from './useFrameRate.js';
19
+ export function VideoCutEditor({ src, hlsUrl, posterUrl, resolving = false, name, durationSeconds, frameRate, open = true, onClose, onSubmit, submitLabel = 'Send for processing', note, ...passThrough }) {
15
20
  const videoRef = React.useRef(null);
16
21
  const [duration, setDuration] = React.useState(durationSeconds && Number.isFinite(durationSeconds) ? durationSeconds : 0);
17
22
  const [playhead, setPlayhead] = React.useState(0);
23
+ const [playing, setPlaying] = React.useState(false);
18
24
  const [plan, setPlan] = React.useState(EMPTY_PLAN);
19
25
  const [submitting, setSubmitting] = React.useState(false);
20
26
  const [error, setError] = React.useState(null);
27
+ // Marked cuts are minutes of scrubbing: Back, Escape and Cancel ask before dropping them.
28
+ const { guardedClose, discardOverlay, isConfirming, cancelDiscard } = useDiscardGuard({
29
+ onClose,
30
+ isDirty: plan.splits.length > 0,
31
+ open,
32
+ });
21
33
  // A source that changes is a fresh attempt: it must not inherit the previous one's failure,
22
34
  // the same rule `VideoTurner` and the gallery's own `ThumbImg` keep.
23
35
  const [failedSrc, setFailedSrc] = React.useState(null);
24
36
  /**
25
37
  * Why there is no film to mark up — `null` when there is one.
26
38
  *
27
- * 🔴 The dialog SAYS it rather than rendering the black rectangle a source-less `<video>`
39
+ * 🔴 The page SAYS it rather than rendering the black rectangle a source-less `<video>`
28
40
  * paints. Marking a cut means scrubbing to a moment, so with nothing to play there is
29
41
  * genuinely nothing to mark — and the one thing worse than that is a player that looks
30
- * broken and a Send button whose refusal ("Nothing is marked") reads as the dialog's fault.
42
+ * broken and a Send button whose refusal ("Nothing is marked") reads as the page's fault.
31
43
  */
32
44
  const cannotShow = resolving
33
45
  ? 'Finding this film…'
@@ -37,54 +49,79 @@ export function VideoCutEditor({ src, hlsUrl, posterUrl, resolving = false, name
37
49
  ? 'The film could not be loaded, so there is nothing to mark up. Nothing has been sent.'
38
50
  : null;
39
51
  const source = useEditorVideoSource(videoRef, { src, hlsUrl }, cannotShow === null);
52
+ const { rate, onLoadedData } = useFrameRate(videoRef, frameRate, source.key);
53
+ const fps = rate.fps;
40
54
  const segments = React.useMemo(() => planSegments(plan, duration), [plan, duration]);
41
55
  const cuts = React.useMemo(() => planCuts(plan, duration), [plan, duration]);
42
56
  const problem = planProblem(plan, duration);
43
- const pct = React.useCallback((seconds) => (duration > 0 ? Math.min(100, Math.max(0, (seconds / duration) * 100)) : 0), [duration]);
44
57
  const seek = React.useCallback((seconds) => {
45
58
  const video = videoRef.current;
46
- if (!video || !Number.isFinite(seconds))
59
+ if (!Number.isFinite(seconds))
47
60
  return;
48
- video.currentTime = Math.max(0, seconds);
49
- setPlayhead(Math.max(0, seconds));
61
+ const at = Math.max(0, seconds);
62
+ if (video)
63
+ video.currentTime = at;
64
+ setPlayhead(at);
50
65
  }, []);
51
- /** ✂ — divide the clip at the playhead. */
52
- const cutHere = React.useCallback(() => {
66
+ /** Where the playhead really is — the element's own clock, which a `timeupdate` trails. */
67
+ const now = () => videoRef.current?.currentTime ?? playhead;
68
+ /** `,` / `.` — one frame back or forward, pausing first, as YouTube and mpv do. */
69
+ const stepFrame = (direction) => {
70
+ const video = videoRef.current;
71
+ if (video && !video.paused)
72
+ video.pause();
73
+ const last = duration > 0 ? frameAt(duration - 1e-6, fps) : 0;
74
+ const frame = Math.min(last, Math.max(0, frameAt(now(), fps) + direction));
75
+ seek(frameStart(frame, fps));
76
+ };
77
+ const togglePlay = () => {
78
+ const video = videoRef.current;
79
+ if (!video)
80
+ return;
81
+ if (video.paused)
82
+ void video.play().catch(() => undefined);
83
+ else
84
+ video.pause();
85
+ };
86
+ /** ✂ — divide the clip at the start of the frame the playhead is on. */
87
+ const cutHere = () => {
53
88
  setError(null);
89
+ const at = snapToFrame(now(), fps);
54
90
  setPlan((current) => {
55
- const next = addSplit(current, playhead, duration);
91
+ const next = addSplit(current, at, duration);
56
92
  if (next === current)
57
93
  setError("There is already a cut there, or the playhead is too close to an end of the clip to cut.");
58
94
  return next;
59
95
  });
60
- }, [playhead, duration]);
96
+ };
61
97
  /** ✗ on a piece — mark it out, or put it back. */
62
98
  const toggle = React.useCallback((segment) => {
63
99
  setError(null);
64
100
  setPlan((current) => toggleSegment(current, segment, duration));
65
101
  }, [duration]);
66
102
  /** Undo a cut, merging the pieces either side. */
67
- const uncut = React.useCallback((at) => {
103
+ const uncut = (at) => {
68
104
  setError(null);
69
105
  setPlan((current) => removeSplit(current, at, duration));
70
- }, [duration]);
106
+ };
71
107
  /*
72
- * `C` cuts and `Space` plays — and nothing else is bound, deliberately.
108
+ * `C` cuts, `Space` plays, `,` and `.` step a frame — and nothing else is bound, deliberately.
73
109
  *
74
- * 🔴 Removing a piece has NO key. It is the only irreversible-feeling action in the dialog
75
- * and the one whose target is ambiguous from the keyboard: with the playhead between two
76
- * splits, "delete" could mean the piece behind it or the piece ahead. Clicking the piece
77
- * says which, which is the entire point of the model. Every segment is a real focusable
78
- * button, so the keyboard route is Tab-then-Enter and it names its target out loud.
110
+ * 🔴 Removing a piece has NO key. It is the only irreversible-feeling action on the page and
111
+ * the one whose target is ambiguous from the keyboard: with the playhead between two splits,
112
+ * "delete" could mean the piece behind it or the piece ahead. Clicking the piece says which,
113
+ * which is the entire point of the model. Every segment is a real focusable button, so the
114
+ * keyboard route is Tab-then-Enter and it names its target out loud.
79
115
  *
80
- * Bound on the DIALOG, not the window: the gallery has its own ⇧←/⇧→ and Space handling,
81
- * and a global listener would keep firing after this closed. `Modal` traps focus, so the
82
- * dialog really does have the keystrokes while it is open.
116
+ * Bound on the PAGE, not the window: the gallery has its own ⇧←/⇧→ and Space handling (it
117
+ * stands down while this is open), and a global listener would keep firing after this closed.
83
118
  */
84
119
  const onKeyDown = (event) => {
85
120
  const target = event.target;
86
121
  // Never steal a key from a text field — the app may render one in `note`.
87
- if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA'))
122
+ if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable))
123
+ return;
124
+ if (event.metaKey || event.ctrlKey || event.altKey || isConfirming)
88
125
  return;
89
126
  if (event.key === 'c' || event.key === 'C') {
90
127
  event.preventDefault();
@@ -92,13 +129,11 @@ export function VideoCutEditor({ src, hlsUrl, posterUrl, resolving = false, name
92
129
  }
93
130
  else if (event.key === ' ') {
94
131
  event.preventDefault();
95
- const video = videoRef.current;
96
- if (!video)
97
- return;
98
- if (video.paused)
99
- void video.play().catch(() => undefined);
100
- else
101
- video.pause();
132
+ togglePlay();
133
+ }
134
+ else if (event.key === ',' || event.key === '.') {
135
+ event.preventDefault();
136
+ stepFrame(event.key === ',' ? -1 : 1);
102
137
  }
103
138
  };
104
139
  const submit = async () => {
@@ -121,36 +156,35 @@ export function VideoCutEditor({ src, hlsUrl, posterUrl, resolving = false, name
121
156
  setSubmitting(false);
122
157
  }
123
158
  };
124
- return (_jsx(Modal, { ...passThrough, open: open, onClose: onClose, title: name ? `Trim ${name}` : 'Trim video', size: 'lg', footer: _jsx(ActionBar, { primary: _jsxs(_Fragment, { children: [_jsx(Button, { variant: 'secondary', onClick: onClose, tooltip: 'Close without sending anything', children: "Cancel" }), _jsx(Button, { onClick: () => void submit(), disabled: submitting || problem !== null, tooltip: problem ?? 'Queue the cuts — the edited copy arrives beside the original', children: submitting ? 'Sending…' : submitLabel })] }) }), children: _jsxs("fieldset", { className: 'cbvc', onKeyDown: onKeyDown, "aria-label": 'Mark sections to cut. Press [ to start a cut, ] to end it, space to play or pause.', children: [cannotShow === null ? (_jsx("video", { ref: videoRef, className: 'cbvc-video', src: source.src, poster: posterUrl, controls: true, playsInline: true, preload: 'metadata', onLoadedMetadata: (event) => {
125
- const seen = event.currentTarget.duration;
126
- // The element's own metadata wins over anything the catalog rounded.
127
- if (Number.isFinite(seen) && seen > 0)
128
- setDuration(seen);
129
- }, onTimeUpdate: (event) => setPlayhead(event.currentTarget.currentTime), onSeeked: (event) => setPlayhead(event.currentTarget.currentTime), onError: (event) => {
130
- if (source.exhausted(event.currentTarget))
131
- setFailedSrc(source.key);
132
- } }, source.key)) : (_jsx("p", { className: 'cbvc-video cbvc-nofilm', role: 'status', children: cannotShow })), _jsxs("div", { className: 'cbvc-marks', children: [_jsxs(Button, { size: 'sm', variant: 'secondary', onClick: cutHere, tooltip: 'Cut the clip here (C)', children: [_jsx(Scissors, { "aria-hidden": 'true', size: 14 }), " Cut here"] }), _jsx("span", { className: 'cbvc-playhead', children: cutTimecode(playhead) }), _jsxs(InfoHint, { title: 'Marking a video to trim', align: 'right', children: [_jsxs("p", { children: ["Press ", _jsx("strong", { children: "Cut here" }), " (or ", _jsx(Kbd, { children: "C" }), ") to divide the clip at the playhead. Every piece between two cuts is a section."] }), _jsxs("p", { children: ["Click ", _jsx("strong", { children: "\u2717" }), " on a section to remove it \u2014 click again to keep it after all. Click a section's timecode to jump there, and ", _jsx(Kbd, { children: "Space" }), " plays and pauses."] }), _jsx("p", { children: "Nothing here changes your video. Sending makes a trimmed COPY beside the original, and you choose which to keep." })] })] }), _jsxs("ul", { className: 'cbvc-strip', "aria-label": segments.length <= 1
133
- ? 'The whole clip, uncut'
134
- : `${segments.length} sections; ${segments.filter((segment) => segment.removed).length} marked for removal`, children: [segments.map((segment) => (_jsx("li", { className: 'cbvc-seg-item', style: { width: `${pct(segment.end) - pct(segment.start)}%` }, children: _jsx(Tooltip, { content: segment.removed
135
- ? `Keep ${cutTimecode(segment.start)}–${cutTimecode(segment.end)} after all`
136
- : `Remove ${cutTimecode(segment.start)}–${cutTimecode(segment.end)}`, children: _jsxs("button", {
137
- // guardrails-ignore no-raw-action-button: a segment of the timeline, sized to
138
- // its own duration — a `Button` carries padding and a min-width, which is
139
- // exactly wrong for a control whose WIDTH is the information.
140
- type: 'button', className: cn(RAW_BUTTON, `cbvc-seg${segment.removed ? ' is-removed' : ''}`), "aria-pressed": segment.removed, "aria-label": segment.removed
141
- ? `Keep ${cutTimecode(segment.start)} to ${cutTimecode(segment.end)} after all`
142
- : `Remove ${cutTimecode(segment.start)} to ${cutTimecode(segment.end)}`, onClick: () => toggle(segment), children: [_jsx(X, { className: 'cbvc-seg-x', "aria-hidden": 'true', size: 12 }), _jsx("span", { className: 'cbvc-seg-label', children: segment.removed ? 'Removed' : cutTimecode(segment.end - segment.start) })] }) }) }, segment.key))), _jsx("span", { className: 'cbvc-strip-head', style: { left: `${pct(playhead)}%` } })] }), _jsx("p", { className: 'cbvc-summary', children: duration > 0 ? (_jsxs(_Fragment, { children: [_jsx("strong", { children: cutTimecode(keptSeconds(cuts, duration)) }), " kept of", ' ', cutTimecode(duration), cuts.length > 0
143
- ? ` — removing ${cutTimecode(cutSeconds(cuts, duration))} in ${cuts.length === 1 ? '1 section' : `${cuts.length} sections`}`
144
- : segments.length > 1
145
- ? ' — nothing marked for removal yet'
146
- : ''] })) : cannotShow === null ? ('Reading the clip…') : (
147
- // 🔴 Only a mounted film is being read. With none, this said "Reading the clip…"
148
- // forever beside the reason there was nothing to read (owner, 2026-10-27).
149
- 'Its length is not known.') }), plan.splits.length > 0 ? (_jsx("ul", { className: 'cbvc-list', children: planSegments(plan, duration)
150
- .slice(1)
151
- .map((segment) => (_jsxs("li", { className: 'cbvc-row', children: [_jsxs("button", {
152
- // guardrails-ignore no-raw-action-button: a plain-CSS row inside a lazily
153
- // imported, deliberately Tailwind-free editor — see `pictureEditor.css`.
154
- type: 'button', className: cn(RAW_BUTTON, 'cbvc-row-goto'), onClick: () => seek(segment.start), children: [_jsx(Scissors, { "aria-hidden": 'true', size: 12 }), " Cut at ", cutTimecode(segment.start)] }), _jsx(Button, { size: 'sm', variant: 'ghost', tooltip: 'Undo this cut \u2014 the sections either side become one again', "aria-label": `Undo the cut at ${cutTimecode(segment.start)}`, onClick: () => uncut(segment.start), children: "Undo" })] }, `cut-${segment.key}`))) })) : null, note ? _jsx("div", { className: 'cbvc-note', children: note }) : null, error ? (_jsx("p", { className: 'cbvc-error', role: 'alert', children: error })) : null] }) }));
159
+ const transport = (_jsxs("div", { className: 'cbvc-transport', children: [_jsx(IconButton, { size: 'sm', label: 'Back one frame', tooltip: 'Back one frame (,)', onClick: () => stepFrame(-1), disabled: cannotShow !== null, children: _jsx(StepBack, { "aria-hidden": 'true' }) }), _jsx(IconButton, { size: 'sm', label: playing ? 'Pause' : 'Play', tooltip: playing ? 'Pause (Space)' : 'Play (Space)', onClick: togglePlay, disabled: cannotShow !== null, children: playing ? _jsx(Pause, { "aria-hidden": 'true' }) : _jsx(Play, { "aria-hidden": 'true' }) }), _jsx(IconButton, { size: 'sm', label: 'Forward one frame', tooltip: 'Forward one frame (.)', onClick: () => stepFrame(1), disabled: cannotShow !== null, children: _jsx(StepForward, { "aria-hidden": 'true' }) }), _jsxs("span", { className: 'cbvc-readout', "data-testid": 'trim-readout', children: [_jsx("strong", { children: frameTimecode(playhead, fps) }), _jsxs("span", { className: 'cbvc-readout-of', children: [" / ", frameTimecode(duration, fps)] })] }), _jsx(Tooltip, { content: rate.source === 'probe'
160
+ ? 'The frame rate the file was probed at'
161
+ : rate.source === 'measured'
162
+ ? 'The frame rate, measured from the clip'
163
+ : 'Assumed — the clip has not said its frame rate, so frame steps may not match its frames', children: _jsx("span", { className: 'cbvc-fps', "data-source": rate.source, tabIndex: 0, children: rate.source === 'assumed' ? `${Math.round(fps)} fps?` : `${Number(fps.toFixed(3))} fps` }) }), _jsxs(Button, { size: 'sm', variant: 'secondary', onClick: cutHere, tooltip: 'Cut the clip at this frame (C)', disabled: cannotShow !== null, children: [_jsx(Scissors, { "aria-hidden": 'true', size: 14 }), " Cut here"] })] }));
164
+ return (_jsx(Dialog, { open: open, onOpenChange: (next) => !next && guardedClose(), children: _jsxs(DialogContent, { ...passThrough, className: 'cbvc-page', overlayClassName: 'cbvc-page-scrim', "aria-describedby": undefined, onOpenAutoFocus: focusPanelOnOpen, onEscapeKeyDown: (event) => {
165
+ event.preventDefault();
166
+ if (isConfirming)
167
+ cancelDiscard();
168
+ else
169
+ guardedClose();
170
+ }, onKeyDown: onKeyDown, children: [_jsxs("header", { className: 'cbvc-bar', children: [_jsx(IconButton, { label: 'Back', tooltip: 'Back to the file, sending nothing (Esc)', onClick: guardedClose, children: _jsx(ArrowLeft, { "aria-hidden": 'true' }) }), _jsx(DialogTitle, { className: 'cbvc-title', children: name ? `Trim ${name}` : 'Trim video' }), _jsxs(InfoHint, { title: 'Marking a video to trim', align: 'right', children: [_jsxs("p", { children: ["Press ", _jsx("strong", { children: "Cut here" }), " (or ", _jsx(Kbd, { children: "C" }), ") to divide the clip at the frame the playhead is on. Every piece between two cuts is a section."] }), _jsxs("p", { children: ["Click ", _jsx("strong", { children: "\u2717" }), " on a section to remove it \u2014 click again to keep it after all.", _jsx(Kbd, { children: "," }), " and ", _jsx(Kbd, { children: "." }), " step one frame, ", _jsx(Kbd, { children: "Space" }), " plays and pauses."] }), _jsx("p", { children: "Zoom the timeline with \u2318 or Ctrl and the wheel, a pinch, or the slider; drag or scroll to move along it, and click the ruler to put the playhead there." }), _jsx("p", { children: "Nothing here changes your video. Sending makes a trimmed COPY beside the original, and you choose which to keep." })] })] }), _jsx("div", { className: cn('cbvc-stage', cannotShow !== null && 'cbvc-stage-empty'), children: cannotShow === null ? (_jsx("video", { ref: videoRef, className: 'cbvc-video', src: source.src, poster: posterUrl, playsInline: true, preload: 'auto', onClick: togglePlay, onLoadedMetadata: (event) => {
171
+ const seen = event.currentTarget.duration;
172
+ // The element's own metadata wins over anything the catalog rounded.
173
+ if (Number.isFinite(seen) && seen > 0)
174
+ setDuration(seen);
175
+ }, onLoadedData: onLoadedData, onPlay: () => setPlaying(true), onPause: () => setPlaying(false), onTimeUpdate: (event) => setPlayhead(event.currentTarget.currentTime), onSeeked: (event) => setPlayhead(event.currentTarget.currentTime), onError: (event) => {
176
+ if (source.exhausted(event.currentTarget))
177
+ setFailedSrc(source.key);
178
+ } }, source.key)) : (_jsx("p", { className: 'cbvc-video cbvc-nofilm', role: 'status', children: cannotShow })) }), _jsxs("div", { className: 'cbvc-deck', children: [duration > 0 ? (_jsx(CutTimeline, { duration: duration, fps: fps, playhead: playhead, segments: segments, onSeek: seek, onToggle: toggle, transport: transport })) : (_jsx("div", { className: 'cbvc-tools', children: transport })), plan.splits.length > 0 ? (_jsx("ul", { className: 'cbvc-list', "aria-label": 'Cuts', children: segments.slice(1).map((segment) => (_jsxs("li", { className: 'cbvc-row', children: [_jsxs("button", {
179
+ // guardrails-ignore no-raw-action-button: a plain-CSS row inside a lazily
180
+ // imported, deliberately Tailwind-free editor — see `pictureEditor.css`.
181
+ type: 'button', className: cn(RAW_BUTTON, 'cbvc-row-goto'), onClick: () => seek(segment.start), children: [_jsx(Scissors, { "aria-hidden": 'true', size: 12 }), " ", frameTimecode(segment.start, fps)] }), _jsx(IconButton, { size: 'sm', label: `Undo the cut at ${frameTimecode(segment.start, fps)}`, tooltip: 'Undo this cut \u2014 the sections either side become one again', onClick: () => uncut(segment.start), children: _jsx(Undo2, { "aria-hidden": 'true' }) })] }, `cut-${segment.key}`))) })) : null, _jsxs("div", { className: 'cbvc-note', children: [_jsx("p", { children: "Each cut lands on the frame you mark: the trimmed copy is re-encoded at every join." }), note ? _jsx("div", { children: note }) : null] }), error ? (_jsx("p", { className: 'cbvc-error', role: 'alert', children: error })) : null] }), _jsx(ActionBar, { className: 'cbvc-foot', secondary: _jsx(Button, { variant: 'secondary', onClick: guardedClose, tooltip: 'Close without sending anything', children: "Cancel" }), primary: _jsx(Button, { onClick: () => void submit(), disabled: submitting || problem !== null, tooltip: problem ?? 'Queue the cuts — the edited copy arrives beside the original', children: submitting ? 'Sending…' : submitLabel }), children: _jsx("p", { className: 'cbvc-summary', children: duration > 0 ? (_jsxs(_Fragment, { children: [_jsx("strong", { children: cutTimecode(keptSeconds(cuts, duration)) }), " kept of ", cutTimecode(duration), cuts.length > 0
182
+ ? ` — removing ${cutTimecode(cutSeconds(cuts, duration))} in ${cuts.length === 1 ? '1 section' : `${cuts.length} sections`}`
183
+ : segments.length > 1
184
+ ? ' — nothing marked for removal yet'
185
+ : ''] })) : cannotShow === null ? ('Reading the clip…') : (
186
+ // 🔴 Only a mounted film is being read. With none, this said "Reading the clip…"
187
+ // forever beside the reason there was nothing to read (owner, 2026-10-27).
188
+ 'Its length is not known.') }) }), discardOverlay] }) }));
155
189
  }
156
190
  //# sourceMappingURL=VideoCutEditor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"VideoCutEditor.js","sourceRoot":"","sources":["../../../src/react/media/VideoCutEditor.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoCutEditor.js","sourceRoot":"","sources":["../../../src/react/media/VideoCutEditor.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The arithmetic under the Trim page's timeline: frames, zoom, and the ruler (forge task 4311).
3
+ *
4
+ * Owner, 2026-11-03: *"a full Trim page with a zoomable timeline, down to frame-by-frame
5
+ * selection."* Everything here is a pure function of numbers, so `cutTimelineMath.spec.ts` proves
6
+ * it without a browser; `e2e/trim-page.spec.ts` proves the same numbers on a real clip.
7
+ *
8
+ * Times stay fractional SECONDS, the unit `cursedbelt-core/media/video-cuts` and binary-server's
9
+ * encode share. A frame is the half-open span `[k / fps, (k + 1) / fps)`, which is also how the
10
+ * encode reads a kept span (`trim=start:end` in `cutArgs`), so "the split is on frame k" and
11
+ * "the encode cuts at frame k" are the same number.
12
+ */
13
+ /** The rate to draw with until the clip says otherwise — the commonest one, and never a claim. */
14
+ export declare const ASSUMED_FPS = 30;
15
+ /** Deepest zoom: adjacent frames this far apart (the task's "one frame per ~12 px"). */
16
+ export declare const FRAME_PX_AT_MAX_ZOOM = 12;
17
+ /** A measured or probed rate, snapped to the known rate within 0.5% of it; `null` if it is not a rate. */
18
+ export declare function snapFrameRate(measured: number | null | undefined): number | null;
19
+ /** The frame that is showing at `seconds` — the one whose span contains it. */
20
+ export declare function frameAt(seconds: number, fps: number): number;
21
+ /** Where frame `k` starts. */
22
+ export declare function frameStart(frame: number, fps: number): number;
23
+ /** `seconds`, moved onto the start of the frame showing there. A split is only ever made here. */
24
+ export declare function snapToFrame(seconds: number, fps: number): number;
25
+ /**
26
+ * `hh:mm:ss:ff` — SMPTE's form, the one Premiere and Resolve print: a COLON before the frame,
27
+ * because `00:01.10` reads as 1.1 s when it means frame 10 of 30 past the second (seen on the
28
+ * page's first collections screenshot). `ff` counts from 00, and for a fractional rate it is the
29
+ * frame since the second's own start, so the readout is clock time and never drifts the way a
30
+ * non-drop-frame count would on 29.97.
31
+ */
32
+ export declare function frameTimecode(seconds: number, fps: number): string;
33
+ /** The two ends of the zoom, in px per second. `fit` shows the whole clip; `max` is one frame per {@link FRAME_PX_AT_MAX_ZOOM}. */
34
+ export declare function zoomBounds(width: number, duration: number, fps: number): {
35
+ fit: number;
36
+ max: number;
37
+ };
38
+ /** A zoom as a slider position in `[0, 1]`: logarithmic, so every notch is the same ×, from fit to frames. */
39
+ export declare function zoomToSlider(pps: number, bounds: {
40
+ fit: number;
41
+ max: number;
42
+ }): number;
43
+ export declare function sliderToZoom(position: number, bounds: {
44
+ fit: number;
45
+ max: number;
46
+ }): number;
47
+ /** The left edge of the view, kept so the clip never scrolls off either end. */
48
+ export declare function clampViewStart(start: number, pps: number, width: number, duration: number): number;
49
+ /**
50
+ * Zoom to `pps` keeping the moment under `anchorX` (px from the track's left) where it is — the
51
+ * gesture every map and every NLE uses, so the thing you pointed at does not slide away.
52
+ */
53
+ export declare function zoomAround(view: {
54
+ start: number;
55
+ pps: number;
56
+ }, pps: number, anchorX: number, width: number, duration: number, bounds: {
57
+ fit: number;
58
+ max: number;
59
+ }): {
60
+ start: number;
61
+ pps: number;
62
+ };
63
+ export interface RulerStep {
64
+ /** Seconds between labeled ticks. */
65
+ label: number;
66
+ /** True when a label falls between whole seconds, so it must print its frame. */
67
+ frames: boolean;
68
+ }
69
+ /**
70
+ * The ruler's labeled step for a zoom. Under a second it counts in FRAMES (1, 2, 5, 10, … of
71
+ * them), so at deep zoom each label names a frame and never a time between two of them.
72
+ */
73
+ export declare function rulerStep(pps: number, fps: number, minPx?: number): RulerStep;
74
+ /**
75
+ * A ruler label: `mm:ss` for whole seconds (hours only when the clip has them), and the whole
76
+ * `hh:mm:ss:ff` once the step is in frames — the readout's own form, since a trimmed `00:01:10`
77
+ * would read as one minute ten.
78
+ */
79
+ export declare function rulerLabel(seconds: number, fps: number, frames: boolean, duration: number): string;
80
+ //# sourceMappingURL=cutTimelineMath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cutTimelineMath.d.ts","sourceRoot":"","sources":["../../../src/react/media/cutTimelineMath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kGAAkG;AAClG,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B,wFAAwF;AACxF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AASvC,0GAA0G;AAC1G,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIhF;AAED,+EAA+E;AAC/E,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED,8BAA8B;AAC9B,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhE;AAID;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,mIAAmI;AACnI,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAGrG;AAED,8GAA8G;AAC9G,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAGtF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAG3F;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGlG;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACpC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAIhC;AAKD,MAAM,WAAW,SAAS;IACxB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,SAAS,CAQzE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKlG"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The arithmetic under the Trim page's timeline: frames, zoom, and the ruler (forge task 4311).
3
+ *
4
+ * Owner, 2026-11-03: *"a full Trim page with a zoomable timeline, down to frame-by-frame
5
+ * selection."* Everything here is a pure function of numbers, so `cutTimelineMath.spec.ts` proves
6
+ * it without a browser; `e2e/trim-page.spec.ts` proves the same numbers on a real clip.
7
+ *
8
+ * Times stay fractional SECONDS, the unit `cursedbelt-core/media/video-cuts` and binary-server's
9
+ * encode share. A frame is the half-open span `[k / fps, (k + 1) / fps)`, which is also how the
10
+ * encode reads a kept span (`trim=start:end` in `cutArgs`), so "the split is on frame k" and
11
+ * "the encode cuts at frame k" are the same number.
12
+ */
13
+ /** The rate to draw with until the clip says otherwise — the commonest one, and never a claim. */
14
+ export const ASSUMED_FPS = 30;
15
+ /** Deepest zoom: adjacent frames this far apart (the task's "one frame per ~12 px"). */
16
+ export const FRAME_PX_AT_MAX_ZOOM = 12;
17
+ /**
18
+ * The rates footage is actually made at. A measured `1 / Δt` lands a hair off its rate (the
19
+ * container's timebase rounds each timestamp), and 29.98 fps would put every split a fraction of
20
+ * a frame out by the end of a long clip. The NTSC rates are written as the fractions they are.
21
+ */
22
+ const KNOWN_RATES = [10, 12, 15, 20, 24000 / 1001, 24, 25, 30000 / 1001, 30, 48, 50, 60000 / 1001, 60, 90, 100, 120];
23
+ /** A measured or probed rate, snapped to the known rate within 0.5% of it; `null` if it is not a rate. */
24
+ export function snapFrameRate(measured) {
25
+ if (measured == null || !Number.isFinite(measured) || measured < 1 || measured > 1000)
26
+ return null;
27
+ const near = KNOWN_RATES.find((rate) => Math.abs(rate - measured) / rate < 0.005);
28
+ return near ?? Math.round(measured * 1000) / 1000;
29
+ }
30
+ /** The frame that is showing at `seconds` — the one whose span contains it. */
31
+ export function frameAt(seconds, fps) {
32
+ // The epsilon absorbs `k / fps * fps` landing a hair under `k` — 0.3 * 10 is 2.9999999999999996.
33
+ return Math.max(0, Math.floor(seconds * fps + 1e-6));
34
+ }
35
+ /** Where frame `k` starts. */
36
+ export function frameStart(frame, fps) {
37
+ return Math.max(0, frame) / fps;
38
+ }
39
+ /** `seconds`, moved onto the start of the frame showing there. A split is only ever made here. */
40
+ export function snapToFrame(seconds, fps) {
41
+ return frameStart(frameAt(seconds, fps), fps);
42
+ }
43
+ const pad = (n) => String(n).padStart(2, '0');
44
+ /**
45
+ * `hh:mm:ss:ff` — SMPTE's form, the one Premiere and Resolve print: a COLON before the frame,
46
+ * because `00:01.10` reads as 1.1 s when it means frame 10 of 30 past the second (seen on the
47
+ * page's first collections screenshot). `ff` counts from 00, and for a fractional rate it is the
48
+ * frame since the second's own start, so the readout is clock time and never drifts the way a
49
+ * non-drop-frame count would on 29.97.
50
+ */
51
+ export function frameTimecode(seconds, fps) {
52
+ const safe = Number.isFinite(seconds) && seconds > 0 ? seconds : 0;
53
+ const whole = Math.floor(safe + 1e-6);
54
+ const ff = Math.min(Math.ceil(fps) - 1, Math.max(0, Math.floor((safe - whole) * fps + 1e-6)));
55
+ return `${pad(Math.floor(whole / 3600))}:${pad(Math.floor(whole / 60) % 60)}:${pad(whole % 60)}:${pad(ff)}`;
56
+ }
57
+ /** The two ends of the zoom, in px per second. `fit` shows the whole clip; `max` is one frame per {@link FRAME_PX_AT_MAX_ZOOM}. */
58
+ export function zoomBounds(width, duration, fps) {
59
+ const fit = width > 0 && duration > 0 ? width / duration : 0;
60
+ return { fit, max: Math.max(fit, FRAME_PX_AT_MAX_ZOOM * fps) };
61
+ }
62
+ /** A zoom as a slider position in `[0, 1]`: logarithmic, so every notch is the same ×, from fit to frames. */
63
+ export function zoomToSlider(pps, bounds) {
64
+ if (!(bounds.max > bounds.fit) || bounds.fit <= 0)
65
+ return 0;
66
+ return Math.min(1, Math.max(0, Math.log(pps / bounds.fit) / Math.log(bounds.max / bounds.fit)));
67
+ }
68
+ export function sliderToZoom(position, bounds) {
69
+ if (!(bounds.max > bounds.fit) || bounds.fit <= 0)
70
+ return bounds.fit;
71
+ return bounds.fit * (bounds.max / bounds.fit) ** Math.min(1, Math.max(0, position));
72
+ }
73
+ /** The left edge of the view, kept so the clip never scrolls off either end. */
74
+ export function clampViewStart(start, pps, width, duration) {
75
+ const span = pps > 0 ? width / pps : duration;
76
+ return Math.min(Math.max(0, duration - span), Math.max(0, start));
77
+ }
78
+ /**
79
+ * Zoom to `pps` keeping the moment under `anchorX` (px from the track's left) where it is — the
80
+ * gesture every map and every NLE uses, so the thing you pointed at does not slide away.
81
+ */
82
+ export function zoomAround(view, pps, anchorX, width, duration, bounds) {
83
+ const next = Math.min(bounds.max, Math.max(bounds.fit, pps));
84
+ const anchored = view.start + anchorX / view.pps;
85
+ return { pps: next, start: clampViewStart(anchored - anchorX / next, next, width, duration) };
86
+ }
87
+ /** Seconds between labels: the smallest step that leaves at least `minPx` between them. */
88
+ const LABEL_STEPS = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200];
89
+ /**
90
+ * The ruler's labeled step for a zoom. Under a second it counts in FRAMES (1, 2, 5, 10, … of
91
+ * them), so at deep zoom each label names a frame and never a time between two of them.
92
+ */
93
+ export function rulerStep(pps, fps, minPx = 88) {
94
+ for (const frames of [1, 2, 5, 10, 15, 30]) {
95
+ const seconds = frames / fps;
96
+ if (seconds >= 1)
97
+ break;
98
+ if (seconds * pps >= minPx)
99
+ return { label: seconds, frames: true };
100
+ }
101
+ const step = LABEL_STEPS.find((s) => s * pps >= minPx) ?? LABEL_STEPS.at(-1);
102
+ return { label: step, frames: false };
103
+ }
104
+ /**
105
+ * A ruler label: `mm:ss` for whole seconds (hours only when the clip has them), and the whole
106
+ * `hh:mm:ss:ff` once the step is in frames — the readout's own form, since a trimmed `00:01:10`
107
+ * would read as one minute ten.
108
+ */
109
+ export function rulerLabel(seconds, fps, frames, duration) {
110
+ const full = frameTimecode(seconds, fps);
111
+ if (frames)
112
+ return full;
113
+ const clock = full.slice(0, -3);
114
+ return duration >= 3600 ? clock : clock.slice(3);
115
+ }
116
+ //# sourceMappingURL=cutTimelineMath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cutTimelineMath.js","sourceRoot":"","sources":["../../../src/react/media/cutTimelineMath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kGAAkG;AAClG,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B,wFAAwF;AACxF,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAErH,0GAA0G;AAC1G,MAAM,UAAU,aAAa,CAAC,QAAmC;IAC/D,IAAI,QAAQ,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,IAAI,QAAQ,GAAG,IAAI;QAAE,OAAO,IAAI,CAAC;IACnG,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC;IAClF,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;AACpD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,OAAO,CAAC,OAAe,EAAE,GAAW;IAClD,iGAAiG;IACjG,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,GAAW;IACnD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC;AAClC,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,GAAW;IACtD,OAAO,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,GAAW;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACtC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9F,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;AAC9G,CAAC;AAED,mIAAmI;AACnI,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,QAAgB,EAAE,GAAW;IACrE,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,oBAAoB,GAAG,GAAG,CAAC,EAAE,CAAC;AACjE,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,MAAoC;IAC5E,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,MAAoC;IACjF,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IACrE,OAAO,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,GAAW,EAAE,KAAa,EAAE,QAAgB;IACxF,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC9C,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,IAAoC,EACpC,GAAW,EACX,OAAe,EACf,KAAa,EACb,QAAgB,EAChB,MAAoC;IAEpC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC;IACjD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,QAAQ,GAAG,OAAO,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;AAChG,CAAC;AAED,2FAA2F;AAC3F,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AASpF;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,GAAW,EAAE,KAAK,GAAG,EAAE;IAC5D,KAAK,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC;QAC7B,IAAI,OAAO,IAAI,CAAC;YAAE,MAAM;QACxB,IAAI,OAAO,GAAG,GAAG,IAAI,KAAK;YAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACtE,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,IAAI,KAAK,CAAC,IAAK,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAY,CAAC;IACzF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,GAAW,EAAE,MAAe,EAAE,QAAgB;IACxF,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAChC,OAAO,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ /**
3
+ * The clip's frame rate, for stepping and snapping on the Trim page (forge task 4311).
4
+ *
5
+ * `probed` (the catalog's ffprobe figure) wins whenever there is one. Without it the rate is
6
+ * MEASURED from the clip itself, by two seeks the moment it can show a frame:
7
+ *
8
+ * 1. seek a little in; `requestVideoFrameCallback` reports the `mediaTime` of the frame it
9
+ * presented — the START of the frame containing that moment;
10
+ * 2. seek a hair before that start; the frame presented now is the one before it.
11
+ *
12
+ * The difference is one frame's duration, exactly — a container's timestamps, not a stopwatch
13
+ * over playback, so it needs no play, no sound and no luck with the scheduler. Then the clip
14
+ * goes back to where it was.
15
+ *
16
+ * 🔴 Until it knows, it says so (`measured: false`) and draws at {@link ASSUMED_FPS}: a
17
+ * readout that LOOKS frame-exact on a guessed rate is the very dishonesty the editor's header
18
+ * forbids. A browser with no `requestVideoFrameCallback`, or a clip that never presents a frame
19
+ * to it, stays on the assumption and stays labeled as one.
20
+ */
21
+ export interface FrameRate {
22
+ fps: number;
23
+ /** `probe` from the catalog, `measured` from the clip, `assumed` when neither answered. */
24
+ source: 'probe' | 'measured' | 'assumed';
25
+ }
26
+ /** One frame's duration, by the two-seek reading above; null when the clip would not say. */
27
+ export declare function measureFrameRate(video: HTMLVideoElement): Promise<number | null>;
28
+ export declare function useFrameRate(videoRef: React.RefObject<HTMLVideoElement | null>, probed: number | null | undefined,
29
+ /** Changes when a new clip loads — each source is measured once. */
30
+ sourceKey: string): {
31
+ rate: FrameRate;
32
+ onLoadedData: () => void;
33
+ };
34
+ //# sourceMappingURL=useFrameRate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFrameRate.d.ts","sourceRoot":"","sources":["../../../src/react/media/useFrameRate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,2FAA2F;IAC3F,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;CAC1C;AA4BD,6FAA6F;AAC7F,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAetF;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAClD,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;AACjC,oEAAoE;AACpE,SAAS,EAAE,MAAM,GAChB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,MAAM,IAAI,CAAA;CAAE,CAkB/C"}