@pixerate/editor-react 0.57.0 → 0.59.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.
@@ -485,6 +485,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
485
485
  }
486
486
 
487
487
  // src/video-editor/ClipInspector.tsx
488
+ import { useState as useState3 } from "react";
488
489
  import {
489
490
  findVideoClip,
490
491
  formatVideoSeconds as formatVideoSeconds2,
@@ -493,7 +494,8 @@ import {
493
494
  normalizeVideoGrade,
494
495
  VIDEO_GRADE_CONTROLS,
495
496
  VIDEO_GRADE_PRESET_NAMES,
496
- videoGradePreset,
497
+ videoGradeLook,
498
+ videoLutEdits,
497
499
  VIDEO_BLEND_MODE_LABELS,
498
500
  VIDEO_BLEND_MODES,
499
501
  VIDEO_FADE_CURVE_LABELS,
@@ -538,6 +540,59 @@ function Commit({
538
540
  );
539
541
  return /* @__PURE__ */ jsx5("input", { className: field, ...rest, defaultValue: value, onKeyDown: (e) => e.key === "Enter" && e.currentTarget.blur(), onBlur: (e) => commit(e.currentTarget.value) }, value);
540
542
  }
543
+ function LutControls({ controller, clip, disabled }) {
544
+ const [message, setMessage] = useState3(null);
545
+ const lut = clip.grade?.lut;
546
+ const apply = (setting, label) => {
547
+ const r = controller.apply(videoLutEdits(controller.getProject(), [clip.id], setting), label);
548
+ setMessage(r.ok ? null : r.error);
549
+ };
550
+ const choose = async (src) => {
551
+ if (!src) return lut && apply(null, "Remove LUT");
552
+ try {
553
+ const table = await controller.luts.load(src);
554
+ apply({ src, ...lut?.strength !== void 0 ? { strength: lut.strength } : {}, ...table.title ? { name: table.title } : {} }, "Apply LUT");
555
+ } catch (e) {
556
+ setMessage(e instanceof Error ? e.message : String(e));
557
+ }
558
+ };
559
+ const readFile = async (file) => {
560
+ if (!file) return;
561
+ try {
562
+ const table = controller.luts.set(file.name, await file.text());
563
+ apply({ src: file.name, ...lut?.strength !== void 0 ? { strength: lut.strength } : {}, ...table.title ? { name: table.title } : {} }, "Apply LUT");
564
+ } catch (e) {
565
+ setMessage(`${file.name}: ${e instanceof Error ? e.message : String(e)}`);
566
+ }
567
+ };
568
+ return /* @__PURE__ */ jsxs4(Fragment3, { children: [
569
+ /* @__PURE__ */ jsx5(Row, { label: "LUT", name: "grade.lut", children: /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 gap-1", children: [
570
+ /* @__PURE__ */ jsx5(Commit, { value: lut?.src ?? "", placeholder: "look.cube", "aria-label": "LUT file", disabled, onCommit: (raw) => void choose(raw.trim()) }),
571
+ /* @__PURE__ */ jsxs4("label", { className: `${btn2} cursor-pointer whitespace-nowrap`, title: "Load a .cube file", children: [
572
+ "Load",
573
+ /* @__PURE__ */ jsx5("input", { type: "file", accept: ".cube", className: "sr-only", disabled, "aria-label": "Load a .cube LUT", onChange: (e) => void readFile(e.currentTarget.files?.[0]) })
574
+ ] }),
575
+ lut && /* @__PURE__ */ jsx5("button", { type: "button", className: btn2, disabled, "aria-label": "Remove LUT", onClick: () => apply(null, "Remove LUT"), children: "\u2715" })
576
+ ] }) }),
577
+ lut && /* @__PURE__ */ jsx5(Row, { label: "LUT strength", name: "grade.lut.strength", children: /* @__PURE__ */ jsx5(
578
+ Commit,
579
+ {
580
+ type: "number",
581
+ min: 0,
582
+ max: 1,
583
+ step: 0.05,
584
+ value: String(lut.strength ?? 1),
585
+ "aria-label": "LUT strength",
586
+ disabled,
587
+ onCommit: (raw) => {
588
+ const n = Number(raw);
589
+ if (raw.trim() && Number.isFinite(n)) apply(n > 0 ? { ...lut, strength: Math.min(1, n) } : null, n > 0 ? "Change LUT strength" : "Remove LUT");
590
+ }
591
+ }
592
+ ) }),
593
+ message && /* @__PURE__ */ jsx5("p", { className: "text-xs text-red-500", role: "alert", "data-lut-error": "", children: message })
594
+ ] });
595
+ }
541
596
  function Row({ label, name, children }) {
542
597
  return /* @__PURE__ */ jsxs4("label", { className: "grid grid-cols-[5.5rem_minmax(0,1fr)] items-center gap-2 text-xs", "data-field": name, children: [
543
598
  /* @__PURE__ */ jsx5("span", { className: "text-muted-foreground", children: label }),
@@ -630,7 +685,7 @@ function ClipInspector({ controller, className = "" }) {
630
685
  disabled,
631
686
  "aria-label": "Apply a look",
632
687
  onChange: (e) => {
633
- const grade = videoGradePreset(e.target.value);
688
+ const grade = videoGradeLook(clip.grade, e.target.value);
634
689
  if (grade) controller.apply({ op: "update_clip", clip: clip.id, set: { grade } }, `Apply ${e.target.value} look`);
635
690
  },
636
691
  children: [
@@ -657,7 +712,8 @@ function ClipInspector({ controller, className = "" }) {
657
712
  set("grade", grade, `Change ${c.label.toLowerCase()}`);
658
713
  }
659
714
  }
660
- ) }, c.key))
715
+ ) }, c.key)),
716
+ /* @__PURE__ */ jsx5(LutControls, { controller, clip, disabled })
661
717
  ] }),
662
718
  at.track.kind === "video" && ["animateIn", "animateOut"].map((key) => /* @__PURE__ */ jsx5(Row, { label: key === "animateIn" ? "Animate in" : "Animate out", name: key, children: /* @__PURE__ */ jsxs4(
663
719
  "select",
@@ -788,7 +844,7 @@ function ClipInspector({ controller, className = "" }) {
788
844
  }
789
845
 
790
846
  // src/video-editor/MediaBin.tsx
791
- import { useState as useState3 } from "react";
847
+ import { useState as useState4 } from "react";
792
848
  import {
793
849
  formatVideoSeconds as formatVideoSeconds3,
794
850
  probeVideoMedia,
@@ -800,11 +856,11 @@ var btn3 = "rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40
800
856
  function MediaBin({ controller, editable = true, fileToUrl, probe = probeVideoMedia, className = "" }) {
801
857
  const { project, readOnly } = useVideoEditorState(controller);
802
858
  const canEdit = editable && !readOnly;
803
- const [url, setUrl] = useState3("");
804
- const [urlKind, setUrlKind] = useState3("");
805
- const [busy, setBusy] = useState3(0);
806
- const [problem, setProblem] = useState3(null);
807
- const [over, setOver] = useState3(false);
859
+ const [url, setUrl] = useState4("");
860
+ const [urlKind, setUrlKind] = useState4("");
861
+ const [busy, setBusy] = useState4(0);
862
+ const [problem, setProblem] = useState4(null);
863
+ const [over, setOver] = useState4(false);
808
864
  const addSource = async (src, kind, name) => {
809
865
  setBusy((n) => n + 1);
810
866
  try {
@@ -939,7 +995,7 @@ function MediaBin({ controller, editable = true, fileToUrl, probe = probeVideoMe
939
995
  }
940
996
 
941
997
  // src/video-editor/TranscriptPanel.tsx
942
- import React5, { useMemo as useMemo3, useState as useState4 } from "react";
998
+ import React6, { useMemo as useMemo3, useState as useState5 } from "react";
943
999
  import {
944
1000
  findVideoFillers,
945
1001
  findVideoTranscriptPauses,
@@ -955,9 +1011,9 @@ function TranscriptPanel({ controller, track, editable = true, className = "" })
955
1011
  const state = useVideoEditorState(controller);
956
1012
  const { project, time, readOnly } = state;
957
1013
  const canEdit = editable && !readOnly;
958
- const [anchor, setAnchor] = useState4(null);
959
- const [focus, setFocus] = useState4(null);
960
- const [message, setMessage] = useState4(null);
1014
+ const [anchor, setAnchor] = useState5(null);
1015
+ const [focus, setFocus] = useState5(null);
1016
+ const [message, setMessage] = useState5(null);
961
1017
  const words = useMemo3(() => {
962
1018
  try {
963
1019
  return videoTimelineTranscript(project, { track });
@@ -1002,7 +1058,7 @@ function TranscriptPanel({ controller, track, editable = true, className = "" })
1002
1058
  },
1003
1059
  children: words.map((w, i) => {
1004
1060
  const inSelection = selected !== null && i >= selected[0] && i <= selected[1];
1005
- return /* @__PURE__ */ jsxs6(React5.Fragment, { children: [
1061
+ return /* @__PURE__ */ jsxs6(React6.Fragment, { children: [
1006
1062
  /* @__PURE__ */ jsx7(
1007
1063
  "span",
1008
1064
  {