@pixerate/editor-react 0.54.0 → 0.55.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.
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/video-editor/index.ts
@@ -22,6 +32,7 @@ var video_editor_exports = {};
22
32
  __export(video_editor_exports, {
23
33
  ClipInspector: () => ClipInspector,
24
34
  MediaBin: () => MediaBin,
35
+ TranscriptPanel: () => TranscriptPanel,
25
36
  VideoEditor: () => VideoEditor,
26
37
  VideoPreview: () => VideoPreview,
27
38
  VideoTimeline: () => VideoTimeline,
@@ -844,29 +855,134 @@ function MediaBin({ controller, editable = true, fileToUrl, probe = import_video
844
855
  );
845
856
  }
846
857
 
847
- // src/video-editor/VideoEditor.tsx
848
- var import_react5 = require("react");
858
+ // src/video-editor/TranscriptPanel.tsx
859
+ var import_react5 = __toESM(require("react"), 1);
860
+ var import_video_editor7 = require("@pixerate/editor/video-editor");
849
861
  var import_jsx_runtime6 = require("react/jsx-runtime");
850
- function VideoEditor({ controller: given, options, editable = true, pxPerSecond, onPxPerSecondChange, resolution, showIssues = true, className = "" }) {
862
+ var btn3 = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
863
+ function TranscriptPanel({ controller, track, editable = true, className = "" }) {
864
+ const state = useVideoEditorState(controller);
865
+ const { project, time, readOnly } = state;
866
+ const canEdit = editable && !readOnly;
867
+ const [anchor, setAnchor] = (0, import_react5.useState)(null);
868
+ const [focus, setFocus] = (0, import_react5.useState)(null);
869
+ const [message, setMessage] = (0, import_react5.useState)(null);
870
+ const words = (0, import_react5.useMemo)(() => {
871
+ try {
872
+ return (0, import_video_editor7.videoTimelineTranscript)(project, { track });
873
+ } catch {
874
+ return [];
875
+ }
876
+ }, [project, track]);
877
+ const current = (0, import_video_editor7.videoWordAt)(words, time);
878
+ const selected = anchor !== null && focus !== null && anchor < words.length && focus < words.length ? [Math.min(anchor, focus), Math.max(anchor, focus)] : null;
879
+ const sound = project.assets.filter((a) => a.kind !== "image");
880
+ const cut = (ranges, label, none) => {
881
+ if (!ranges.length) return setMessage(none);
882
+ const r = controller.apply((0, import_video_editor7.videoRemoveRangesEdits)(ranges), label);
883
+ setMessage(r.ok ? null : r.error);
884
+ setAnchor(null);
885
+ setFocus(null);
886
+ };
887
+ const load = async (assetId, file) => {
888
+ if (!file) return;
889
+ try {
890
+ const r = controller.apply((0, import_video_editor7.videoTranscriptEdit)(assetId, await file.text()), "Attach transcript");
891
+ setMessage(r.ok ? null : r.error);
892
+ } catch (e) {
893
+ setMessage(e instanceof Error ? e.message : String(e));
894
+ }
895
+ };
896
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-2 text-xs ${className}`, "data-transcript-panel": "", children: [
897
+ words.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
898
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
899
+ "div",
900
+ {
901
+ className: "max-h-48 overflow-y-auto leading-6",
902
+ role: "listbox",
903
+ "aria-label": "Transcript",
904
+ "aria-multiselectable": "true",
905
+ tabIndex: 0,
906
+ onKeyDown: (e) => {
907
+ if (canEdit && selected && (e.key === "Delete" || e.key === "Backspace")) {
908
+ e.preventDefault();
909
+ cut([(0, import_video_editor7.videoWordsRange)(words, selected[0], selected[1])], "Cut words", "Select words to cut.");
910
+ }
911
+ },
912
+ children: words.map((w, i) => {
913
+ const inSelection = selected !== null && i >= selected[0] && i <= selected[1];
914
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react5.default.Fragment, { children: [
915
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
916
+ "span",
917
+ {
918
+ role: "option",
919
+ "aria-selected": inSelection,
920
+ tabIndex: -1,
921
+ className: `cursor-pointer rounded px-0.5 ${inSelection ? "bg-rose-500/30" : i === current ? "bg-amber-400/40" : "hover:bg-muted"}`,
922
+ "data-word": i,
923
+ "data-current": i === current ? "" : void 0,
924
+ "data-selected": inSelection ? "" : void 0,
925
+ onClick: (e) => {
926
+ if (e.shiftKey && anchor !== null) setFocus(i);
927
+ else {
928
+ setAnchor(i);
929
+ setFocus(i);
930
+ controller.seek(w.start);
931
+ }
932
+ },
933
+ children: w.word
934
+ }
935
+ ),
936
+ " "
937
+ ] }, `${w.clip}:${w.index}`);
938
+ })
939
+ }
940
+ ),
941
+ canEdit && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
942
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: btn3, disabled: !selected, onClick: () => selected && cut([(0, import_video_editor7.videoWordsRange)(words, selected[0], selected[1])], "Cut words", "Select words to cut."), children: "Delete selected words" }),
943
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: btn3, onClick: () => cut((0, import_video_editor7.findVideoFillers)(words), "Remove fillers", "No filler words found."), children: "Remove fillers" }),
944
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: btn3, onClick: () => cut((0, import_video_editor7.findVideoTranscriptPauses)(words, { fps: project.fps }), "Tighten pauses", "No long pauses found."), children: "Tighten pauses" })
945
+ ] })
946
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-muted-foreground", "data-transcript-empty": "", children: "No transcribed media on the timeline. Load a transcript (Whisper JSON, .srt or .vtt) for a video or audio asset to edit by text." }),
947
+ canEdit && sound.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("details", { children: [
948
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("summary", { className: "cursor-pointer text-muted-foreground", children: "Transcripts" }),
949
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "mt-1 space-y-1", children: sound.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { className: "flex items-center gap-2", children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "min-w-0 flex-1 truncate", children: [
951
+ a.name ?? a.id,
952
+ " ",
953
+ a.transcript ? `\xB7 ${a.transcript.length} words` : ""
954
+ ] }),
955
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "file", accept: ".json,.srt,.vtt,application/json,text/vtt", "aria-label": `Load a transcript for ${a.name ?? a.id}`, className: "w-40 text-[10px]", onChange: (e) => void load(a.id, e.target.files?.[0]) })
956
+ ] }, a.id)) })
957
+ ] }),
958
+ message && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-red-500", role: "alert", "data-transcript-error": "", children: message })
959
+ ] });
960
+ }
961
+
962
+ // src/video-editor/VideoEditor.tsx
963
+ var import_react6 = require("react");
964
+ var import_jsx_runtime7 = require("react/jsx-runtime");
965
+ function VideoEditor({ controller: given, options, editable = true, pxPerSecond, onPxPerSecondChange, resolution, showIssues = true, showTranscript = true, className = "" }) {
851
966
  const { controller, state } = useVideoEditor(given ?? options ?? {});
852
- (0, import_react5.useEffect)(() => {
967
+ (0, import_react6.useEffect)(() => {
853
968
  if (editable) return;
854
969
  const was = controller.getState().readOnly;
855
970
  controller.setReadOnly(true);
856
971
  return () => controller.setReadOnly(was);
857
972
  }, [controller, editable]);
858
973
  const { issues } = state;
859
- const issueList = showIssues && issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "space-y-0.5 text-xs", "aria-label": "Video issues", "data-video-issues": "", children: issues.map((issue, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { className: issue.severity === "error" ? "text-red-500" : issue.severity === "warning" ? "text-amber-500" : "text-muted-foreground", "data-issue": issue.code, children: issue.message }, i)) });
860
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-3 ${className}`, "data-video-editor": "", children: [
861
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `grid gap-3 ${editable ? "lg:grid-cols-[16rem_minmax(0,1fr)_18rem]" : ""}`, children: [
862
- editable && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MediaBin, { controller, className: "min-w-0" }),
863
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex min-w-0 flex-col gap-2", children: [
864
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VideoPreview, { controller, resolution, className: "rounded-md border border-border" }),
865
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VideoTransport, { controller, editable })
974
+ const issueList = showIssues && issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: "space-y-0.5 text-xs", "aria-label": "Video issues", "data-video-issues": "", children: issues.map((issue, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { className: issue.severity === "error" ? "text-red-500" : issue.severity === "warning" ? "text-amber-500" : "text-muted-foreground", "data-issue": issue.code, children: issue.message }, i)) });
975
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `flex flex-col gap-3 ${className}`, "data-video-editor": "", children: [
976
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `grid gap-3 ${editable ? "lg:grid-cols-[16rem_minmax(0,1fr)_18rem]" : ""}`, children: [
977
+ editable && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MediaBin, { controller, className: "min-w-0" }),
978
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 flex-col gap-2", children: [
979
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VideoPreview, { controller, resolution, className: "rounded-md border border-border" }),
980
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VideoTransport, { controller, editable })
866
981
  ] }),
867
- editable && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ClipInspector, { controller, className: "min-w-0" })
982
+ editable && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ClipInspector, { controller, className: "min-w-0" })
868
983
  ] }),
869
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VideoTimeline, { controller, editable, pxPerSecond, onPxPerSecondChange }),
984
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VideoTimeline, { controller, editable, pxPerSecond, onPxPerSecondChange }),
985
+ showTranscript && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TranscriptPanel, { controller, editable }),
870
986
  issueList
871
987
  ] });
872
988
  }
@@ -874,6 +990,7 @@ function VideoEditor({ controller: given, options, editable = true, pxPerSecond,
874
990
  0 && (module.exports = {
875
991
  ClipInspector,
876
992
  MediaBin,
993
+ TranscriptPanel,
877
994
  VideoEditor,
878
995
  VideoPreview,
879
996
  VideoTimeline,