@pixerate/editor-react 0.60.0 → 0.62.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.
|
@@ -77,6 +77,7 @@ interface VideoTransportProps {
|
|
|
77
77
|
/**
|
|
78
78
|
* Play / pause, a frame back and forward, the playhead and total length as timecode, and the
|
|
79
79
|
* edits that work on the playhead and the selection: split, delete, ripple delete, undo and redo.
|
|
80
|
+
* When any asset has proxies, a preview-size control picks what the preview plays (a view setting).
|
|
80
81
|
*/
|
|
81
82
|
declare function VideoTransport({ controller, editable, className }: VideoTransportProps): React__default.JSX.Element;
|
|
82
83
|
|
|
@@ -92,17 +93,6 @@ interface VideoTimelineProps {
|
|
|
92
93
|
waveforms?: VideoWaveformMode;
|
|
93
94
|
className?: string;
|
|
94
95
|
}
|
|
95
|
-
/**
|
|
96
|
-
* The tracks of a `VideoEditorController` laid out in time: a ruler with markers (click or drag it
|
|
97
|
-
* to scrub), a playhead, and one row per track with its toggles (mute, hide, lock) and its clips.
|
|
98
|
-
* Click a clip to select it (Shift / Cmd to add or remove it), drag it to move it (onto another
|
|
99
|
-
* track of the same kind too; hold Alt to insert, pushing later material along on every sync-locked
|
|
100
|
-
* track, or Shift to overwrite what is underneath), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,
|
|
101
|
-
* markers, the playhead and frames. Every edit is one undo step; refused edits show why.
|
|
102
|
-
*
|
|
103
|
-
* Keyboard (when focused): Space plays, ←/→ step a frame, S splits at the playhead, Delete removes
|
|
104
|
-
* the selection (Shift to ripple), Cmd/Ctrl+Z undoes, Shift+Cmd/Ctrl+Z redoes.
|
|
105
|
-
*/
|
|
106
96
|
declare function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable, waveforms, className }: VideoTimelineProps): React__default.JSX.Element;
|
|
107
97
|
|
|
108
98
|
interface ClipInspectorProps {
|
|
@@ -145,7 +145,7 @@ function VideoScopes({ controller, scopes = VIDEO_SCOPE_KINDS, width = 256, heig
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// src/video-editor/VideoTransport.tsx
|
|
148
|
-
import { formatVideoTimecode } from "@pixerate/editor/video-editor";
|
|
148
|
+
import { formatVideoTimecode, parseVideoPreviewScale, VIDEO_PREVIEW_SCALE_OPTIONS, videoProjectHasProxies } from "@pixerate/editor/video-editor";
|
|
149
149
|
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
150
150
|
var btn = "rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40";
|
|
151
151
|
function VideoTransport({ controller, editable = true, className = "" }) {
|
|
@@ -161,6 +161,18 @@ function VideoTransport({ controller, editable = true, className = "" }) {
|
|
|
161
161
|
" / ",
|
|
162
162
|
formatVideoTimecode(duration, project.fps)
|
|
163
163
|
] }),
|
|
164
|
+
videoProjectHasProxies(project) && /* @__PURE__ */ jsx3(
|
|
165
|
+
"select",
|
|
166
|
+
{
|
|
167
|
+
className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs",
|
|
168
|
+
value: String(state.previewScale),
|
|
169
|
+
onChange: (e) => controller.setPreviewScale(parseVideoPreviewScale(e.target.value)),
|
|
170
|
+
"aria-label": "Preview size",
|
|
171
|
+
title: "Play proxies in the preview (the export always uses the originals)",
|
|
172
|
+
"data-preview-scale": "",
|
|
173
|
+
children: VIDEO_PREVIEW_SCALE_OPTIONS.map((o) => /* @__PURE__ */ jsx3("option", { value: o.value, children: o.label }, o.value))
|
|
174
|
+
}
|
|
175
|
+
),
|
|
164
176
|
editable && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
165
177
|
/* @__PURE__ */ jsx3("span", { className: "mx-1 h-4 w-px bg-border" }),
|
|
166
178
|
/* @__PURE__ */ jsx3("button", { type: "button", className: btn, onClick: () => controller.splitAtPlayhead(), disabled: !canEdit, title: "Split at the playhead (S)", "data-split": "", children: "Split" }),
|
|
@@ -189,6 +201,7 @@ import {
|
|
|
189
201
|
planVideoClipDrag,
|
|
190
202
|
videoDragLabel,
|
|
191
203
|
videoAssetMap,
|
|
204
|
+
videoSequenceMap,
|
|
192
205
|
videoClipLabel,
|
|
193
206
|
videoClipShowsWaveform,
|
|
194
207
|
videoClipWaveformPath,
|
|
@@ -220,6 +233,27 @@ function ClipWaveform({ controller, clip, asset, width, height }) {
|
|
|
220
233
|
return /* @__PURE__ */ jsx4("svg", { className: "pointer-events-none absolute inset-0", width, height, "aria-hidden": "true", "data-waveform": clip.id, children: /* @__PURE__ */ jsx4("path", { d, fill: "currentColor", fillOpacity: 0.45 }) });
|
|
221
234
|
}
|
|
222
235
|
var isField = (el) => el instanceof HTMLElement && /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName);
|
|
236
|
+
function useCachedRanges(controller, project, px) {
|
|
237
|
+
const [ranges, setRanges] = useState2([]);
|
|
238
|
+
useEffect4(() => {
|
|
239
|
+
const cache = controller.frameCache;
|
|
240
|
+
if (!cache) return setRanges([]);
|
|
241
|
+
let timer = null;
|
|
242
|
+
const read = () => {
|
|
243
|
+
timer = null;
|
|
244
|
+
setRanges(cache.cachedRanges(project, { step: 2e3 / px }));
|
|
245
|
+
};
|
|
246
|
+
read();
|
|
247
|
+
const off = cache.subscribe(() => {
|
|
248
|
+
timer ??= setTimeout(read, 250);
|
|
249
|
+
});
|
|
250
|
+
return () => {
|
|
251
|
+
off();
|
|
252
|
+
if (timer) clearTimeout(timer);
|
|
253
|
+
};
|
|
254
|
+
}, [controller, project, px]);
|
|
255
|
+
return ranges;
|
|
256
|
+
}
|
|
223
257
|
function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable = true, waveforms = "audio", className = "" }) {
|
|
224
258
|
const state = useVideoEditorState(controller);
|
|
225
259
|
const { project, duration, time, selection, readOnly, error } = state;
|
|
@@ -231,10 +265,12 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
|
|
|
231
265
|
const rows = useRef4(/* @__PURE__ */ new Map());
|
|
232
266
|
const ruler = useRef4(null);
|
|
233
267
|
const assets = videoAssetMap(project);
|
|
268
|
+
const sequences = videoSequenceMap(project);
|
|
234
269
|
const msToPx = (ms) => ms * px / 1e3;
|
|
235
270
|
const span = Math.max(duration + 5e3, 1e4);
|
|
236
271
|
const width = msToPx(span);
|
|
237
272
|
const ticks = videoRulerTicks(span, px, project.fps);
|
|
273
|
+
const cached = useCachedRanges(controller, project, px);
|
|
238
274
|
const seekAt = (clientX) => {
|
|
239
275
|
const box = ruler.current.getBoundingClientRect();
|
|
240
276
|
controller.seek(Math.max(0, (clientX - box.left) * 1e3 / px));
|
|
@@ -396,6 +432,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
|
|
|
396
432
|
},
|
|
397
433
|
children: [
|
|
398
434
|
ticks.map((t) => /* @__PURE__ */ jsx4("div", { className: `absolute bottom-0 w-px ${t.major ? "h-2.5 bg-muted-foreground/70" : "h-1 bg-muted-foreground/40"}`, style: { left: t.x }, "data-tick": t.time, children: t.label && /* @__PURE__ */ jsx4("span", { className: "absolute -top-3.5 left-1 whitespace-nowrap tabular-nums", children: t.label }) }, t.time)),
|
|
435
|
+
cached.map((r) => /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute bottom-0 h-[3px] bg-emerald-500/70", style: { left: msToPx(r.start), width: Math.max(1, msToPx(r.end - r.start)) }, title: "Frames cached: plays and scrubs without waiting", "data-cached-range": "" }, r.start)),
|
|
399
436
|
(project.markers ?? []).map((m) => /* @__PURE__ */ jsx4(
|
|
400
437
|
"div",
|
|
401
438
|
{
|
|
@@ -438,7 +475,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
|
|
|
438
475
|
children: clips.map(({ clip, home, start, duration: length, dragging }) => {
|
|
439
476
|
const asset = clip.type === "media" ? assets.get(clip.asset) : void 0;
|
|
440
477
|
const selected = selection.includes(clip.id);
|
|
441
|
-
const label = videoClipLabel(clip, asset);
|
|
478
|
+
const label = videoClipLabel(clip, asset, clip.type === "sequence" ? sequences.get(clip.sequence) : void 0);
|
|
442
479
|
return /* @__PURE__ */ jsxs3(
|
|
443
480
|
"div",
|
|
444
481
|
{
|
|
@@ -453,6 +490,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
|
|
|
453
490
|
},
|
|
454
491
|
title: `${label} \xB7 ${formatVideoSeconds(start)}\u2013${formatVideoSeconds(start + length)}`,
|
|
455
492
|
"data-clip": clip.id,
|
|
493
|
+
"data-compound": clip.type === "sequence" ? clip.sequence : void 0,
|
|
456
494
|
"data-selected": selected ? "" : void 0,
|
|
457
495
|
"data-dragging": dragging ? "" : void 0,
|
|
458
496
|
"aria-selected": selected,
|
|
@@ -629,7 +667,7 @@ function ClipInspector({ controller, className = "" }) {
|
|
|
629
667
|
const transition = clip.transition;
|
|
630
668
|
return /* @__PURE__ */ jsxs4("div", { className: `flex flex-col gap-1.5 ${className}`, "data-clip-inspector": "", "data-clip-id": clip.id, children: [
|
|
631
669
|
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
632
|
-
/* @__PURE__ */ jsx5("span", { className: "font-medium", children: clip.type === "media" ? "Media clip" : clip.type === "text" ? "Title" : "Colour clip" }),
|
|
670
|
+
/* @__PURE__ */ jsx5("span", { className: "font-medium", children: clip.type === "media" ? "Media clip" : clip.type === "text" ? "Title" : clip.type === "sequence" ? "Compound clip" : "Colour clip" }),
|
|
633
671
|
/* @__PURE__ */ jsx5("code", { className: "text-muted-foreground", children: clip.id }),
|
|
634
672
|
at.track.locked && /* @__PURE__ */ jsx5("span", { className: "text-amber-500", children: "locked track" })
|
|
635
673
|
] }),
|
|
@@ -777,6 +815,14 @@ function ClipInspector({ controller, className = "" }) {
|
|
|
777
815
|
/* @__PURE__ */ jsx5(Row, { label: "Font size", name: "fontSize", children: number("fontSize", "Font size", { min: 1, step: 2 }) }),
|
|
778
816
|
/* @__PURE__ */ jsx5(Row, { label: "Background", name: "background", children: text("background", "Background") })
|
|
779
817
|
] }),
|
|
818
|
+
clip.type === "sequence" && /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
819
|
+
/* @__PURE__ */ jsx5(Row, { label: "Sequence", name: "sequence", children: /* @__PURE__ */ jsxs4("span", { className: "truncate", title: "A compound clip plays its sequence as one picture and sound; its clips cannot be edited here yet", children: [
|
|
820
|
+
project.sequences?.find((q) => q.id === clip.sequence)?.name ?? clip.sequence,
|
|
821
|
+
" \xB7 from ",
|
|
822
|
+
formatVideoSeconds2(clip.in ?? 0)
|
|
823
|
+
] }) }),
|
|
824
|
+
/* @__PURE__ */ jsx5(Row, { label: "Muted", name: "muted", children: /* @__PURE__ */ jsx5("input", { type: "checkbox", className: "justify-self-start", checked: !!clip.muted, disabled, "aria-label": "Muted", onChange: (e) => set("muted", e.target.checked || void 0, e.target.checked ? "Mute clip" : "Unmute clip") }) })
|
|
825
|
+
] }),
|
|
780
826
|
clip.type === "color" && /* @__PURE__ */ jsx5(Row, { label: "Colour", name: "color", children: /* @__PURE__ */ jsx5(Commit, { value: clip.color, "aria-label": "Colour", disabled, onCommit: (raw) => raw.trim() && set("color", raw.trim(), "Change colour") }) }),
|
|
781
827
|
/* @__PURE__ */ jsx5(Row, { label: "Transition in", name: "transition", children: /* @__PURE__ */ jsxs4(
|
|
782
828
|
"select",
|
|
@@ -930,6 +976,7 @@ function MediaBin({ controller, editable = true, fileToUrl, probe = probeVideoMe
|
|
|
930
976
|
project.assets.length ? /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-1", "aria-label": "Media assets", children: project.assets.map((asset) => /* @__PURE__ */ jsxs5("li", { className: "flex items-center gap-2 text-xs", "data-asset": asset.id, children: [
|
|
931
977
|
/* @__PURE__ */ jsx6("span", { className: "rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground", "data-asset-kind": "", children: asset.kind }),
|
|
932
978
|
/* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate", title: asset.src, children: asset.name ?? asset.id }),
|
|
979
|
+
asset.proxies?.length ? /* @__PURE__ */ jsx6("span", { className: "rounded bg-muted px-1 text-[10px] text-muted-foreground", title: `Proxies: ${asset.proxies.map((p) => `${p.scale * 100}%`).join(", ")} (the preview can play these; exports use the original)`, "data-asset-proxies": "", children: "proxy" }) : null,
|
|
933
980
|
asset.duration !== void 0 && /* @__PURE__ */ jsx6("span", { className: "tabular-nums text-muted-foreground", children: formatVideoSeconds3(asset.duration) }),
|
|
934
981
|
editable && /* @__PURE__ */ jsx6("button", { type: "button", className: btn3, disabled: !canEdit, onClick: () => addToTimeline(asset), "data-add-to-timeline": "", children: "Add to timeline" })
|
|
935
982
|
] }, asset.id)) }) : /* @__PURE__ */ jsxs5("p", { className: "text-xs text-muted-foreground", children: [
|