@cut-workbench/workbench 0.2.0 → 0.3.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.
package/dist/lab.cjs CHANGED
@@ -1,17 +1,16 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_format = require("./format--DhqHMgX.cjs");
2
+ const require_notify = require("./notify-CMNwDFGw.cjs");
3
3
  let react = require("react");
4
4
  let _cut_workbench_core = require("@cut-workbench/core");
5
5
  let react_jsx_runtime = require("react/jsx-runtime");
6
6
  let lucide_react = require("lucide-react");
7
- let sonner = require("sonner");
8
7
  let _cut_workbench_core_lab = require("@cut-workbench/core/lab");
9
8
  // src/features/lab/components/LabCard.tsx
10
9
  /** Lab 卡片外壳:标题 + 用到的 API + (不可用时的原因) */
11
10
  function LabCard({ title, description, apis, children, unavailable, className }) {
12
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_format.Card, {
13
- className: require_format.cn("flex flex-col", className),
14
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_format.CardHeader, {
11
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_notify.Card, {
12
+ className: require_notify.cn("flex flex-col", className),
13
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_notify.CardHeader, {
15
14
  className: "gap-1",
16
15
  children: [
17
16
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -19,25 +18,25 @@ function LabCard({ title, description, apis, children, unavailable, className })
19
18
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20
19
  className: "flex size-6 items-center justify-center rounded bg-primary/10 text-primary",
21
20
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FlaskConical, { className: "size-3.5" })
22
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_format.CardTitle, {
21
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_notify.CardTitle, {
23
22
  className: "text-sm",
24
23
  children: title
25
24
  })]
26
25
  }),
27
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_format.CardDescription, {
26
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_notify.CardDescription, {
28
27
  className: "text-xs",
29
28
  children: description
30
29
  }),
31
30
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32
31
  className: "flex flex-wrap gap-1 pt-1",
33
- children: apis.map((api) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_format.Badge, {
32
+ children: apis.map((api) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_notify.Badge, {
34
33
  variant: "outline",
35
34
  className: "font-mono text-[10px] font-normal text-muted-foreground",
36
35
  children: api
37
36
  }, api))
38
37
  })
39
38
  ]
40
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_format.CardContent, {
39
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_notify.CardContent, {
41
40
  className: "flex flex-1 flex-col gap-2.5",
42
41
  children: unavailable != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
43
42
  className: "rounded-md border border-dashed border-warning/40 bg-warning/5 p-3 text-xs text-warning",
@@ -64,6 +63,7 @@ function useDownload() {
64
63
  }
65
64
  /** 带 busy 状态的异步执行器:统一错误 toast,禁止静默吞异常 */
66
65
  function useLabAction() {
66
+ const { bridge } = require_notify.useWorkbench();
67
67
  const [busy, setBusy] = (0, react.useState)(null);
68
68
  return {
69
69
  busy,
@@ -74,12 +74,16 @@ function useLabAction() {
74
74
  } catch (err) {
75
75
  const message = err instanceof Error ? err.message : String(err);
76
76
  console.error(err);
77
- sonner.toast.error("操作失败", { description: message });
77
+ require_notify.notify(bridge, {
78
+ level: "error",
79
+ title: "操作失败",
80
+ description: message
81
+ });
78
82
  return null;
79
83
  } finally {
80
84
  setBusy(null);
81
85
  }
82
- }, [])
86
+ }, [bridge])
83
87
  };
84
88
  }
85
89
  // src/features/lab/components/shared.tsx
@@ -223,7 +227,7 @@ function ConcatenateLab() {
223
227
  children: file.name
224
228
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
225
229
  className: "shrink-0 font-mono",
226
- children: info != null ? `${require_format.formatResolution(info.width, info.height)} · ${(0, _cut_workbench_core.formatDuration)(info.durationUs)} · ${info.codec}` : "检测中…"
230
+ children: info != null ? `${require_notify.formatResolution(info.width, info.height)} · ${(0, _cut_workbench_core.formatDuration)(info.durationUs)} · ${info.codec}` : "检测中…"
227
231
  })]
228
232
  }, `${file.name}-${index}`);
229
233
  })
@@ -345,6 +349,7 @@ function readStageBackground() {
345
349
  return value.length > 0 ? value : "#101018";
346
350
  }
347
351
  function ChromakeyLab() {
352
+ const { bridge } = require_notify.useWorkbench();
348
353
  const [webgl2] = (0, react.useState)(_cut_workbench_core_lab.isWebGL2Available);
349
354
  const [file, setFile] = (0, react.useState)(null);
350
355
  const [similarity, setSimilarity] = (0, react.useState)(.4);
@@ -393,7 +398,10 @@ function ChromakeyLab() {
393
398
  setFile(picked);
394
399
  setDurationUs(result.meta.duration);
395
400
  setTimeUs(Math.min(result.meta.duration / 2, 1e6));
396
- sonner.toast.success("已载入视频,拖动参数查看抠像效果");
401
+ require_notify.notify(bridge, {
402
+ level: "success",
403
+ title: "已载入视频,拖动参数查看抠像效果"
404
+ });
397
405
  };
398
406
  (0, react.useEffect)(() => {
399
407
  if (clipRef.current == null || busy != null) return;
@@ -796,6 +804,7 @@ function ThumbnailLab() {
796
804
  * - 摄像头(getUserMedia,需要用户授权)。
797
805
  */
798
806
  function RecorderLab() {
807
+ const { bridge } = require_notify.useWorkbench();
799
808
  const [mode, setMode] = (0, react.useState)("demo");
800
809
  const [recording, setRecording] = (0, react.useState)(false);
801
810
  const [paused, setPaused] = (0, react.useState)(false);
@@ -845,7 +854,11 @@ function RecorderLab() {
845
854
  return stream;
846
855
  } catch (err) {
847
856
  const message = err instanceof Error ? err.message : String(err);
848
- sonner.toast.error("无法访问摄像头", { description: message });
857
+ require_notify.notify(bridge, {
858
+ level: "error",
859
+ title: "无法访问摄像头",
860
+ description: message
861
+ });
849
862
  return null;
850
863
  }
851
864
  };
@@ -886,7 +899,10 @@ function RecorderLab() {
886
899
  recorderRef.current = null;
887
900
  if (output != null) {
888
901
  setResult(output);
889
- sonner.toast.success(`录制完成 · ${(output.size / 1024 / 1024).toFixed(2)} MB`);
902
+ require_notify.notify(bridge, {
903
+ level: "success",
904
+ title: `录制完成 · ${(output.size / 1024 / 1024).toFixed(2)} MB`
905
+ });
890
906
  }
891
907
  };
892
908
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LabCard, {
package/dist/lab.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lab.cjs","names":["Card","cn","CardHeader","FlaskConical","CardTitle","CardDescription","Badge","CardContent","useRef","useCallback","useState","useRef","_Fragment","useState","Loader2","useState","readMp4Info","concatMp4","formatResolution","formatDuration","AlertTriangle","useState","mixAudioIntoMp4","useState","isWebGL2Available","useRef","useCallback","renderChromakeyFrame","createMp4Clip","useState","useRef","parseSrt","createSubtitlePreviewClip","useCallback","renderSubtitleFrame","burnSubtitles","Square","Play","useState","useRef","splitMp4Tracks","Split","formatDuration","useState","extractKeyframes","_Fragment","formatTimecode","useState","useRef","useCallback","createDemoCanvasStream","fixRecordedDuration","Circle","_Fragment","Play","Pause","Square","formatRecorderState","AVRecorder","useState","useRef","FrameCounterClip","useCallback","renderClipFrameToCanvas","exportCustomClip","Square","Play","formatTimecode","FlaskConical"],"sources":["../src/features/lab/components/LabCard.tsx","../src/features/lab/hooks/use-lab-action.ts","../src/features/lab/components/shared.tsx","../src/features/lab/components/ConcatenateLab.tsx","../src/features/lab/components/RemuxLab.tsx","../src/features/lab/components/ChromakeyLab.tsx","../src/features/lab/components/SubtitleLab.tsx","../src/features/lab/components/TrackSplitLab.tsx","../src/features/lab/components/ThumbnailLab.tsx","../src/features/lab/components/RecorderLab.tsx","../src/features/lab/components/CustomClipLab.tsx","../src/features/lab/components/LabPage.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { FlaskConical } from 'lucide-react'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { cn } from '@/lib/cn'\n\ninterface LabCardProps {\n title: string\n description: string\n apis: string[]\n children: ReactNode\n unavailable?: string | null\n className?: string\n}\n\n/** Lab 卡片外壳:标题 + 用到的 API + (不可用时的原因) */\nexport function LabCard({\n title,\n description,\n apis,\n children,\n unavailable,\n className,\n}: LabCardProps) {\n return (\n <Card className={cn('flex flex-col', className)}>\n <CardHeader className=\"gap-1\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex size-6 items-center justify-center rounded bg-primary/10 text-primary\">\n <FlaskConical className=\"size-3.5\" />\n </div>\n <CardTitle className=\"text-sm\">{title}</CardTitle>\n </div>\n <CardDescription className=\"text-xs\">{description}</CardDescription>\n <div className=\"flex flex-wrap gap-1 pt-1\">\n {apis.map((api) => (\n <Badge\n key={api}\n variant=\"outline\"\n className=\"font-mono text-[10px] font-normal text-muted-foreground\"\n >\n {api}\n </Badge>\n ))}\n </div>\n </CardHeader>\n <CardContent className=\"flex flex-1 flex-col gap-2.5\">\n {unavailable != null ? (\n <div className=\"rounded-md border border-dashed border-warning/40 bg-warning/5 p-3 text-xs text-warning\">\n {unavailable}\n </div>\n ) : (\n children\n )}\n </CardContent>\n </Card>\n )\n}\n","import { useCallback, useRef } from 'react'\nimport { useState } from 'react'\nimport { toast } from 'sonner'\n\n/** 生成可下载链接(Lab 通用) */\nexport function useDownload(): (blob: Blob, filename: string) => void {\n const urlsRef = useRef<string[]>([])\n return useCallback((blob: Blob, filename: string) => {\n const url = URL.createObjectURL(blob)\n urlsRef.current.push(url)\n const link = document.createElement('a')\n link.href = url\n link.download = filename\n document.body.appendChild(link)\n link.click()\n link.remove()\n window.setTimeout(() => URL.revokeObjectURL(url), 15_000)\n }, [])\n}\n\n/** 带 busy 状态的异步执行器:统一错误 toast,禁止静默吞异常 */\nexport function useLabAction() {\n const [busy, setBusy] = useState<string | null>(null)\n const run = useCallback(\n async <T,>(label: string, task: () => Promise<T>): Promise<T | null> => {\n setBusy(label)\n try {\n return await task()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n console.error(err)\n toast.error('操作失败', { description: message })\n return null\n } finally {\n setBusy(null)\n }\n },\n [],\n )\n return { busy, run }\n}\n","import { Loader2 } from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport { useDownload } from '../hooks/use-lab-action'\n\n/**\n * Lab 共享的可视组件。\n *\n * 只导出组件(React Fast Refresh 要求单文件只导出组件);\n * 工具函数与 hook 放在 `../hooks/use-lab-action`。\n */\n\n/** 文件选择按钮 */\nexport function FilePickButton({\n label,\n accept,\n multiple,\n onFiles,\n disabled,\n}: {\n label: string\n accept: string\n multiple?: boolean\n onFiles: (files: File[]) => void\n disabled?: boolean\n}) {\n const inputRef = useRef<HTMLInputElement | null>(null)\n return (\n <>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n className=\"hidden\"\n onChange={(e) => {\n const files = Array.from(e.target.files ?? [])\n if (files.length > 0) onFiles(files)\n e.target.value = ''\n }}\n />\n <button\n type=\"button\"\n disabled={disabled}\n onClick={() => inputRef.current?.click()}\n className=\"flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed px-3 py-3.5 text-xs text-muted-foreground transition-colors hover:border-primary/50 hover:text-foreground disabled:pointer-events-none disabled:opacity-50\"\n >\n <span className=\"max-w-full truncate\">{label}</span>\n </button>\n </>\n )\n}\n\n/** 结果预览:视频 + 下载 */\nexport function ResultPreview({ blob, filename }: { blob: Blob; filename: string }) {\n const download = useDownload()\n const [url] = useState(() => URL.createObjectURL(blob))\n useEffect(() => () => URL.revokeObjectURL(url), [url])\n\n return (\n <div className=\"space-y-1.5\">\n <video\n src={url}\n controls\n className=\"w-full rounded-md border bg-stage-bg\"\n style={{ maxHeight: 200 }}\n />\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"min-w-0 flex-1 truncate text-[10px] text-muted-foreground\">\n {(blob.size / 1024 / 1024).toFixed(2)} MB\n </span>\n <button\n type=\"button\"\n onClick={() => download(blob, filename)}\n className=\"rounded-md bg-primary px-2 py-1 text-[11px] text-primary-foreground hover:bg-primary/90\"\n >\n 下载\n </button>\n </div>\n </div>\n )\n}\n\n/** 忙碌提示 */\nexport function Busy({ label }: { label: string }) {\n return (\n <div className=\"flex items-center gap-2 rounded-md bg-muted px-3 py-2 text-xs text-muted-foreground\">\n <Loader2 className=\"size-3 animate-spin\" />\n {label}\n </div>\n )\n}\n\n/** 简易滑杆 */\nexport function SliderRow({\n label,\n value,\n min,\n max,\n step,\n format,\n onChange,\n}: {\n label: string\n value: number\n min: number\n max: number\n step: number\n format?: (value: number) => string\n onChange: (value: number) => void\n}) {\n return (\n <div className=\"space-y-1\">\n <div className=\"flex justify-between text-[10px] text-muted-foreground\">\n <span>{label}</span>\n <span className=\"font-mono tabular-nums\">{format ? format(value) : value.toFixed(2)}</span>\n </div>\n <input\n type=\"range\"\n min={min}\n max={max}\n step={step}\n value={value}\n onChange={(e) => onChange(Number(e.target.value))}\n className=\"h-1 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary\"\n />\n </div>\n )\n}\n","import { useState } from 'react'\nimport { AlertTriangle } from 'lucide-react'\nimport { concatMp4, readMp4Info, type Mp4Info } from '@cut-workbench/core/lab'\nimport { formatDuration, formatResolution } from '@/lib/format'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, FilePickButton, ResultPreview } from './shared'\n\n/**\n * Lab 1:无损拼接。\n *\n * 用 `fastConcatMP4` 直接拼接多个 MP4 的样本,不重编码。\n * 前提是编码 / 分辨率 / 采样率完全一致;不一致时本 Lab 会先预检并给出具体差异。\n */\nexport function ConcatenateLab() {\n const [files, setFiles] = useState<File[]>([])\n const [infos, setInfos] = useState<Mp4Info[]>([])\n const [result, setResult] = useState<Blob | null>(null)\n const { busy, run } = useLabAction()\n\n const handleFiles = async (picked: File[]) => {\n setFiles(picked)\n setResult(null)\n setInfos([])\n const detected = await run('正在读取文件信息…', async () => {\n const list: Mp4Info[] = []\n for (const file of picked) list.push(await readMp4Info(file))\n return list\n })\n if (detected != null) setInfos(detected)\n }\n\n const handleConcat = async () => {\n const output = await run('正在无损拼接…', () => concatMp4(files))\n if (output != null) {\n setResult(output.blob)\n setInfos(output.infos)\n }\n }\n\n const first = infos[0]\n const mismatch =\n first != null &&\n infos.some(\n (info) =>\n info.width !== first.width ||\n info.height !== first.height ||\n info.codec !== first.codec ||\n info.audioSampleRate !== first.audioSampleRate,\n )\n\n return (\n <LabCard\n title=\"无损拼接\"\n description=\"直接把多个 MP4 的样本拼在一起,不重编码,速度极快。要求规格完全一致。\"\n apis={['fastConcatMP4', 'MP4Clip.getFileHeaderBinData']}\n >\n <FilePickButton\n label={files.length > 0 ? `已选 ${files.length} 个文件(重新选择)` : '选择 2 个以上 MP4'}\n accept=\"video/mp4\"\n multiple\n onFiles={(picked) => void handleFiles(picked)}\n disabled={busy != null}\n />\n\n {files.length > 0 && (\n <ul className=\"space-y-1 text-[10px] text-muted-foreground\">\n {files.map((file, index) => {\n const info = infos[index]\n return (\n <li key={`${file.name}-${index}`} className=\"flex justify-between gap-2\">\n <span className=\"truncate\">{file.name}</span>\n <span className=\"shrink-0 font-mono\">\n {info != null\n ? `${formatResolution(info.width, info.height)} · ${formatDuration(info.durationUs)} · ${info.codec}`\n : '检测中…'}\n </span>\n </li>\n )\n })}\n </ul>\n )}\n\n {mismatch && (\n <div className=\"flex items-start gap-1.5 rounded-md bg-warning/10 p-2 text-[10px] text-warning\">\n <AlertTriangle className=\"mt-0.5 size-3 shrink-0\" />\n 检测到规格不一致,拼接会失败并给出具体原因(fastConcatMP4 的硬性要求)。\n </div>\n )}\n\n <button\n type=\"button\"\n disabled={files.length < 2 || busy != null}\n onClick={() => void handleConcat()}\n className=\"rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\n >\n 开始拼接\n </button>\n\n {busy != null && <Busy label={busy} />}\n {result != null && <ResultPreview blob={result} filename=\"concat.mp4\" />}\n </LabCard>\n )\n}\n","import { useState } from 'react'\r\nimport { mixAudioIntoMp4 } from '@cut-workbench/core/lab'\r\nimport { LabCard } from './LabCard'\r\nimport { useLabAction } from '../hooks/use-lab-action'\r\nimport { Busy, FilePickButton, ResultPreview, SliderRow } from './shared'\r\n\r\n/**\r\n * Lab 2:配音。\r\n *\r\n * `mixinMP4AndAudio` 只重编码音频轨,视频轨原样复制 —— 因此速度快、画质无损。\r\n */\r\nexport function RemuxLab() {\r\n const [video, setVideo] = useState<File | null>(null)\r\n const [audio, setAudio] = useState<File | null>(null)\r\n const [volume, setVolume] = useState(1)\r\n const [loop, setLoop] = useState(true)\r\n const [result, setResult] = useState<Blob | null>(null)\r\n const { busy, run } = useLabAction()\r\n\r\n const handleMix = async () => {\r\n if (video == null || audio == null) return\r\n const output = await run('正在混合音频…', () =>\r\n mixAudioIntoMp4(video, audio, volume, loop),\r\n )\r\n if (output != null) setResult(output)\r\n }\r\n\r\n return (\r\n <LabCard\r\n title=\"视频配音\"\r\n description=\"把音轨混入 MP4:只重编码音频,视频轨不动,画质零损失。\"\r\n apis={['mixinMP4AndAudio']}\r\n >\r\n <FilePickButton\r\n label={video == null ? '选择视频(MP4)' : video.name}\r\n accept=\"video/mp4\"\r\n onFiles={(files) => {\r\n setVideo(files[0] ?? null)\r\n setResult(null)\r\n }}\r\n disabled={busy != null}\r\n />\r\n <FilePickButton\r\n label={audio == null ? '选择音频(MP3/WAV/M4A)' : audio.name}\r\n accept=\"audio/*\"\r\n onFiles={(files) => {\r\n setAudio(files[0] ?? null)\r\n setResult(null)\r\n }}\r\n disabled={busy != null}\r\n />\r\n\r\n <SliderRow\r\n label=\"音量\"\r\n value={volume}\r\n min={0}\r\n max={2}\r\n step={0.05}\r\n format={(v) => `${Math.round(v * 100)}%`}\r\n onChange={setVolume}\r\n />\r\n <label className=\"flex items-center gap-2 text-[11px] text-muted-foreground\">\r\n <input\r\n type=\"checkbox\"\r\n checked={loop}\r\n onChange={(e) => setLoop(e.target.checked)}\r\n className=\"accent-primary\"\r\n />\r\n 音频短于视频时循环填充\r\n </label>\r\n\r\n <button\r\n type=\"button\"\r\n disabled={video == null || audio == null || busy != null}\r\n onClick={() => void handleMix()}\r\n className=\"rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\r\n >\r\n 开始混合\r\n </button>\r\n\r\n {busy != null && <Busy label={busy} />}\r\n {result != null && <ResultPreview blob={result} filename=\"mixin.mp4\" />}\r\n </LabCard>\r\n )\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { isWebGL2Available, renderChromakeyFrame, createMp4Clip, type Mp4ClipHandle } from '@cut-workbench/core/lab'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, FilePickButton, SliderRow } from './shared'\nimport { toast } from 'sonner'\n\n/**\n * Lab 3:绿幕抠像。\n *\n * `createChromakey` 返回一个函数,把 VideoFrame 转成去背后的 ImageBitmap。\n * 配合 `MP4Clip.tickInterceptor` 即可对整段视频生效(导出时同样有效)。\n *\n * ⚠️ 依赖 WebGL2;不支持时本卡片隐藏操作、仅显示原因。\n */\n/**\n * 抠像结果的兜底底色:读取主题令牌,而不是写死 `#101018`。\n *\n * 抠像后的 VideoFrame 带透明通道,需要一层底色才能看清;用 `--cw-stage-bg`\n * 让 Lab 预览区与工作台舞台保持一致,宿主换主题时一起变。\n * 取不到时退回深色(纯逻辑环境 / 未包 `.cw-root`)。\n */\nfunction readStageBackground(): string {\n if (typeof document === 'undefined') return '#101018'\n const root = document.querySelector('.cw-root')\n if (root == null) return '#101018'\n const value = getComputedStyle(root).getPropertyValue('--cw-stage-bg').trim()\n return value.length > 0 ? value : '#101018'\n}\n\nexport function ChromakeyLab() {\n // WebGL2 探测是纯函数,可安全在 useState 初始化中执行\n const [webgl2] = useState(isWebGL2Available)\n const [file, setFile] = useState<File | null>(null)\n const [similarity, setSimilarity] = useState(0.4)\n const [smoothness, setSmoothness] = useState(0.1)\n const [spill, setSpill] = useState(0.1)\n const [timeUs, setTimeUs] = useState(0)\n const [durationUs, setDurationUs] = useState(0)\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const clipRef = useRef<Mp4ClipHandle | null>(null)\n const { busy, run } = useLabAction()\n\n useEffect(() => {\n return () => {\n clipRef.current?.destroy()\n clipRef.current = null\n }\n }, [])\n\n const render = useCallback(async () => {\n const clip = clipRef.current\n const canvas = canvasRef.current\n if (clip == null || canvas == null) return\n await renderChromakeyFrame(\n clip,\n timeUs,\n { similarity, smoothness, spill, keyColor: [0, 255, 0] },\n canvas,\n // 底色由主题令牌驱动:抠像结果画在透明画布上,用舞台色兜底,\n // 这样宿主换主题时 Lab 预览区也跟着变\n readStageBackground(),\n )\n }, [similarity, smoothness, spill, timeUs])\n\n const handleFile = async (files: File[]) => {\n const picked = files[0]\n if (picked == null) return\n const result = await run('正在解析视频…', async () => {\n clipRef.current?.destroy()\n const created = await createMp4Clip(picked)\n return created\n })\n if (result == null) return\n clipRef.current = result.clip\n setFile(picked)\n setDurationUs(result.meta.duration)\n setTimeUs(Math.min(result.meta.duration / 2, 1_000_000))\n toast.success('已载入视频,拖动参数查看抠像效果')\n }\n\n useEffect(() => {\n if (clipRef.current == null || busy != null) return\n void render()\n }, [busy, render])\n\n return (\n <LabCard\n title=\"绿幕抠像\"\n description=\"用 WebGL2 实时扣除背景色,通过 tickInterceptor 作用到整段视频。\"\n apis={['createChromakey', 'MP4Clip.tickInterceptor']}\n unavailable={webgl2 ? null : '当前设备不支持 WebGL2,createChromakey 无法运行。'}\n >\n <FilePickButton\n label={file?.name ?? '选择绿幕视频(MP4)'}\n accept=\"video/mp4\"\n onFiles={(files) => void handleFile(files)}\n disabled={busy != null}\n />\n\n <div className=\"relative overflow-hidden rounded-md border bg-stage-bg\">\n <canvas ref={canvasRef} className=\"block w-full\" style={{ maxHeight: 180 }} />\n {file == null && (\n <div className=\"pointer-events-none absolute inset-0 flex h-24 items-center justify-center text-[10px] text-muted-foreground\">\n 选择视频后显示抠像结果\n </div>\n )}\n </div>\n\n <SliderRow\n label=\"相似度 similarity\"\n value={similarity}\n min={0}\n max={1}\n step={0.01}\n format={(v) => v.toFixed(2)}\n onChange={setSimilarity}\n />\n <SliderRow\n label=\"平滑度 smoothness\"\n value={smoothness}\n min={0}\n max={1}\n step={0.01}\n format={(v) => v.toFixed(2)}\n onChange={setSmoothness}\n />\n <SliderRow\n label=\"饱和度 spill\"\n value={spill}\n min={0}\n max={1}\n step={0.01}\n format={(v) => v.toFixed(2)}\n onChange={setSpill}\n />\n {durationUs > 0 && (\n <SliderRow\n label=\"取帧时间\"\n value={timeUs}\n min={0}\n max={Math.max(1, durationUs - 1)}\n step={100_000}\n format={(v) => `${(v / 1e6).toFixed(2)}s`}\n onChange={setTimeUs}\n />\n )}\n {busy != null && <Busy label={busy} />}\n <p className=\"text-[10px] leading-relaxed text-muted-foreground\">\n keyColor 固定为纯绿 (0,255,0)。实际项目中可读取首帧像素自动取色。\n </p>\n </LabCard>\n )\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { Play, Square } from 'lucide-react'\nimport {\n burnSubtitles,\n createSubtitlePreviewClip,\n renderSubtitleFrame,\n type SubtitleClipHandle,\n} from '@cut-workbench/core/lab'\nimport { parseSrt } from '@cut-workbench/core'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, ResultPreview, SliderRow } from './shared'\n\nconst SAMPLE_SRT = `1\n00:00:00,000 --> 00:00:02,000\n这是第一句字幕\n\n2\n00:00:02,000 --> 00:00:04,000\n第二句会带描边\n\n3\n00:00:04,000 --> 00:00:06,000\nEmbedSubtitlesClip 烧录进画面\n`\n\n/**\n * Lab 4:字幕烧录。\n *\n * `EmbedSubtitlesClip` 本身就是一个 IClip:tick 返回带字幕的 VideoFrame。\n * 因此既能放进 Combinator 导出,也能直接逐帧预览。\n */\nexport function SubtitleLab() {\n const [srt, setSrt] = useState(SAMPLE_SRT)\n const [timeUs, setTimeUs] = useState(0)\n const [playing, setPlaying] = useState(false)\n const [result, setResult] = useState<Blob | null>(null)\n const { busy, run } = useLabAction()\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const clipRef = useRef<SubtitleClipHandle | null>(null)\n const rafRef = useRef<number | null>(null)\n\n const cues = parseSrt(srt)\n const durationUs = cues.length > 0 ? cues[cues.length - 1]!.endMs * 1000 : 0\n\n // 重新创建字幕 clip\n useEffect(() => {\n clipRef.current?.destroy()\n if (cues.length === 0) {\n clipRef.current = null\n return\n }\n try {\n clipRef.current = createSubtitlePreviewClip(srt, 640, 360)\n } catch (err) {\n console.warn('[SubtitleLab] create failed', err)\n clipRef.current = null\n }\n return () => {\n clipRef.current?.destroy()\n clipRef.current = null\n }\n }, [cues.length, srt])\n\n const draw = useCallback(async () => {\n const clip = clipRef.current\n const canvas = canvasRef.current\n if (clip == null || canvas == null) return\n await renderSubtitleFrame(clip, timeUs, canvas)\n }, [timeUs])\n\n useEffect(() => {\n void draw()\n }, [draw])\n\n // 播放循环(简单 rAF 递增时间)\n useEffect(() => {\n if (!playing) return\n let last = performance.now()\n let current = timeUs\n const tick = () => {\n const now = performance.now()\n current += (now - last) * 1000\n last = now\n if (durationUs > 0 && current >= durationUs) {\n setTimeUs(durationUs)\n setPlaying(false)\n return\n }\n setTimeUs(current)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current != null) cancelAnimationFrame(rafRef.current)\n }\n // 只在开始播放时启动\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [playing, durationUs])\n\n const handleBurn = async () => {\n const width = 1280\n const height = 720\n const output = await run('正在烧录字幕…', () =>\n burnSubtitles(srt, width, height, durationUs),\n )\n if (output != null) setResult(output)\n }\n\n return (\n <LabCard\n title=\"字幕烧录\"\n description=\"SRT 解析后逐帧绘制到画面,最终编码进视频流(硬字幕)。\"\n apis={['EmbedSubtitlesClip', 'Combinator']}\n >\n <textarea\n value={srt}\n onChange={(e) => setSrt(e.target.value)}\n rows={5}\n className=\"w-full rounded-md border bg-transparent p-2 font-mono text-[10px] outline-none focus-visible:border-ring\"\n placeholder=\"粘贴 SRT 内容\"\n />\n\n <div className=\"overflow-hidden rounded-md border bg-stage-bg\">\n <canvas\n ref={canvasRef}\n width={640}\n height={360}\n className=\"block w-full\"\n style={{ maxHeight: 180 }}\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPlaying((p) => !p)}\n disabled={cues.length === 0}\n className=\"flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-[11px] disabled:opacity-50\"\n >\n {playing ? <Square className=\"size-3\" /> : <Play className=\"size-3\" />}\n {playing ? '停止' : '预览'}\n </button>\n <span className=\"text-[10px] text-muted-foreground\">\n {cues.length} 条 · {(timeUs / 1e6).toFixed(2)}s / {(durationUs / 1e6).toFixed(2)}s\n </span>\n </div>\n\n <SliderRow\n label=\"时间\"\n value={timeUs}\n min={0}\n max={Math.max(1, durationUs)}\n step={10_000}\n format={(v) => `${(v / 1e6).toFixed(2)}s`}\n onChange={(v) => {\n setPlaying(false)\n setTimeUs(v)\n }}\n />\n\n <button\n type=\"button\"\n disabled={cues.length === 0 || busy != null}\n onClick={() => void handleBurn()}\n className=\"rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\n >\n 烧录并导出 MP4\n </button>\n\n {busy != null && <Busy label={busy} />}\n {result != null && <ResultPreview blob={result} filename=\"subtitles.mp4\" />}\n </LabCard>\n )\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { Split } from 'lucide-react'\nimport { splitMp4Tracks, type TrackSplitResult } from '@cut-workbench/core/lab'\nimport { formatDuration } from '@/lib/format'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, FilePickButton } from './shared'\n\n/**\n * Lab 5:轨道拆分。\n *\n * `splitTrack()` 从一个 MP4Clip 派生出「纯视频」与「纯音频」两个 clip,\n * 它们与源共享样本表,因此内存开销很小。\n */\nexport function TrackSplitLab() {\n const [file, setFile] = useState<File | null>(null)\n const [result, setResult] = useState<TrackSplitResult | null>(null)\n const { busy, run } = useLabAction()\n const cleanupRef = useRef<(() => void) | null>(null)\n\n useEffect(() => {\n return () => cleanupRef.current?.()\n }, [])\n\n const handleSplit = async () => {\n if (file == null) return\n cleanupRef.current?.()\n const output = await run('正在拆分轨道…', () => splitMp4Tracks(file))\n setResult(output)\n cleanupRef.current = () => {\n output?.videoClip?.destroy()\n output?.audioClip?.destroy()\n }\n }\n\n return (\n <LabCard\n title=\"轨道拆分\"\n description=\"把一个 MP4 拆成纯视频轨与纯音频轨两个独立 IClip,用于分别编辑。\"\n apis={['MP4Clip.splitTrack']}\n >\n <FilePickButton\n label={file?.name ?? '选择 MP4'}\n accept=\"video/mp4\"\n onFiles={(files) => {\n setFile(files[0] ?? null)\n setResult(null)\n }}\n disabled={busy != null}\n />\n\n <button\n type=\"button\"\n disabled={file == null || busy != null}\n onClick={() => void handleSplit()}\n className=\"flex items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\n >\n <Split className=\"size-3\" />\n 拆分轨道\n </button>\n\n {busy != null && <Busy label={busy} />}\n\n {result != null && (\n <div className=\"grid gap-1.5 text-[10px]\">\n <TrackResult\n label=\"视频轨\"\n present={result.videoMeta != null}\n detail={\n result.videoMeta != null\n ? `${result.videoMeta.width}×${result.videoMeta.height} · ${formatDuration(result.videoMeta.duration)}`\n : '源文件无视频轨'\n }\n />\n <TrackResult\n label=\"音频轨\"\n present={result.audioMeta != null}\n detail={\n result.audioMeta != null\n ? `48kHz 双声道 · ${formatDuration(result.audioMeta.duration)}`\n : '源文件无音频轨'\n }\n />\n <p className=\"pt-1 leading-relaxed text-muted-foreground\">\n 拆分出的 clip 与源共享样本表,可分别创建 Sprite 放到不同轨道;两者在时间轴上对齐即等于原片。\n </p>\n </div>\n )}\n </LabCard>\n )\n}\n\nfunction TrackResult({\n label,\n present,\n detail,\n}: {\n label: string\n present: boolean\n detail: string\n}) {\n return (\n <div className=\"flex items-center justify-between gap-2 rounded-md border px-2 py-1.5\">\n <span className={present ? 'text-foreground' : 'text-muted-foreground'}>{label}</span>\n <span className={present ? 'font-mono text-success' : 'text-muted-foreground'}>\n {present ? detail : detail}\n </span>\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\r\nimport { extractKeyframes } from '@cut-workbench/core/lab'\r\nimport { formatTimecode } from '@cut-workbench/core'\r\nimport { LabCard } from './LabCard'\r\nimport { useLabAction } from '../hooks/use-lab-action'\r\nimport { Busy, FilePickButton } from './shared'\r\n\r\ninterface FrameItem {\r\n ts: number\r\n url: string\r\n size: number\r\n}\r\n\r\n/**\r\n * Lab 6:缩略图抽帧。\r\n *\r\n * `MP4Clip.thumbnails()` 默认每个关键帧生成一张;也可以传 step 按固定间隔取。\r\n * 这里展示全部关键帧与时间戳。\r\n */\r\nexport function ThumbnailLab() {\r\n const [file, setFile] = useState<File | null>(null)\r\n const [frames, setFrames] = useState<FrameItem[]>([])\r\n const [width, setWidth] = useState(120)\r\n const { busy, run } = useLabAction()\r\n\r\n useEffect(() => {\r\n return () => {\r\n for (const frame of frames) URL.revokeObjectURL(frame.url)\r\n }\r\n }, [frames])\r\n\r\n const handleExtract = async () => {\r\n if (file == null) return\r\n const output = await run('正在抽取关键帧…', () => extractKeyframes(file, width))\r\n if (output != null) setFrames(output)\r\n }\r\n\r\n return (\r\n <LabCard\r\n title=\"缩略图抽帧\"\r\n description=\"按关键帧抽取缩略图,可用于时间轴缩略图带或场景切分预览。\"\r\n apis={['MP4Clip.thumbnails']}\r\n >\r\n <FilePickButton\r\n label={file?.name ?? '选择 MP4'}\r\n accept=\"video/mp4\"\r\n onFiles={(files) => {\r\n setFile(files[0] ?? null)\r\n setFrames([])\r\n }}\r\n disabled={busy != null}\r\n />\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <label className=\"text-[10px] text-muted-foreground\">宽度</label>\r\n <input\r\n type=\"number\"\r\n min={40}\r\n max={480}\r\n step={20}\r\n value={width}\r\n onChange={(e) => setWidth(Number(e.target.value))}\r\n className=\"h-6 w-16 rounded border bg-transparent px-1.5 text-[11px]\"\r\n />\r\n <button\r\n type=\"button\"\r\n disabled={file == null || busy != null}\r\n onClick={() => void handleExtract()}\r\n className=\"ml-auto rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\r\n >\r\n 抽取\r\n </button>\r\n </div>\r\n\r\n {busy != null && <Busy label={busy} />}\r\n\r\n {frames.length > 0 && (\r\n <>\r\n <p className=\"text-[10px] text-muted-foreground\">共 {frames.length} 张关键帧</p>\r\n <div className=\"thin-scrollbar grid max-h-52 grid-cols-3 gap-1.5 overflow-auto\">\r\n {frames.map((frame, index) => (\r\n <figure key={`${frame.ts}-${index}`} className=\"space-y-0.5\">\r\n <img\r\n src={frame.url}\r\n alt=\"\"\r\n className=\"w-full rounded border object-cover\"\r\n loading=\"lazy\"\r\n />\r\n <figcaption className=\"font-mono text-[9px] text-muted-foreground tabular-nums\">\r\n {formatTimecode(frame.ts)}\r\n </figcaption>\r\n </figure>\r\n ))}\r\n </div>\r\n </>\r\n )}\r\n </LabCard>\r\n )\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { Circle, Pause, Play, Square } from 'lucide-react'\nimport { toast } from 'sonner'\nimport {\n AVRecorder,\n createDemoCanvasStream,\n fixRecordedDuration,\n formatRecorderState,\n type RecorderHandle,\n} from '@cut-workbench/core/lab'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { ResultPreview } from './shared'\n\ntype Mode = 'demo' | 'camera'\n\n/**\n * Lab 7:屏幕 / 画布录制。\n *\n * `AVRecorder` 接收任意 MediaStream 并输出 MP4(H.264 + AAC)。\n * 这里提供两种源:\n * - 程序化画布(Canvas.captureStream,无需授权);\n * - 摄像头(getUserMedia,需要用户授权)。\n */\nexport function RecorderLab() {\n const [mode, setMode] = useState<Mode>('demo')\n const [recording, setRecording] = useState(false)\n const [paused, setPaused] = useState(false)\n const [bytes, setBytes] = useState(0)\n const [state, setState] = useState<string>('inactive')\n const [result, setResult] = useState<Blob | null>(null)\n const [previewStream, setPreviewStream] = useState<MediaStream | null>(null)\n const videoRef = useRef<HTMLVideoElement | null>(null)\n const recorderRef = useRef<RecorderHandle | null>(null)\n const demoRef = useRef<{ stream: MediaStream; stop: () => void } | null>(null)\n const cameraRef = useRef<MediaStream | null>(null)\n const { run } = useLabAction()\n\n // 预览流挂到 <video>\n useEffect(() => {\n const video = videoRef.current\n if (video == null) return\n video.srcObject = previewStream\n if (previewStream != null) void video.play().catch(() => undefined)\n return () => {\n if (video.srcObject === previewStream) video.srcObject = null\n }\n }, [previewStream])\n\n const cleanup = useCallback(() => {\n demoRef.current?.stop()\n demoRef.current = null\n cameraRef.current?.getTracks().forEach((t) => t.stop())\n cameraRef.current = null\n recorderRef.current?.dispose()\n recorderRef.current = null\n setPreviewStream(null)\n }, [])\n\n useEffect(() => cleanup, [cleanup])\n\n const acquireStream = async (): Promise<MediaStream | null> => {\n cleanup()\n if (mode === 'demo') {\n const demo = createDemoCanvasStream(1280, 720)\n demoRef.current = demo\n setPreviewStream(demo.stream)\n return demo.stream\n }\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })\n cameraRef.current = stream\n setPreviewStream(stream)\n return stream\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n toast.error('无法访问摄像头', { description: message })\n return null\n }\n }\n\n const handleStart = async () => {\n const stream = await acquireStream()\n if (stream == null) return\n setBytes(0)\n setResult(null)\n const recorder = createRecorderAdapter(stream, (total) => setBytes(total))\n recorderRef.current = recorder\n recorder.start()\n setRecording(true)\n setPaused(false)\n setState(recorder.state())\n }\n\n const handlePauseToggle = () => {\n const recorder = recorderRef.current\n if (recorder == null) return\n if (paused) {\n recorder.resume()\n setPaused(false)\n } else {\n recorder.pause()\n setPaused(true)\n }\n setState(recorder.state())\n }\n\n const handleStop = async () => {\n const recorder = recorderRef.current\n if (recorder == null) return\n const output = await run('正在封装 MP4…', async () => {\n const raw = await recorder.stop()\n // AVRecorder 输出的是 fMP4,moov 时长为 0,需修复后播放器才显示正确总长\n return fixRecordedDuration(raw)\n })\n setRecording(false)\n setPaused(false)\n setState(recorder.state())\n recorderRef.current = null\n if (output != null) {\n setResult(output)\n toast.success(`录制完成 · ${(output.size / 1024 / 1024).toFixed(2)} MB`)\n }\n }\n\n return (\n <LabCard\n title=\"屏幕 / 画布录制\"\n description=\"用 AVRecorder 把 MediaStream 实时编码为 MP4。可录制程序化画布或摄像头。\"\n apis={['AVCanvas.captureStream', 'AVRecorder.start/pause/resume/stop']}\n >\n <div className=\"flex gap-1.5\">\n {(['demo', 'camera'] as Mode[]).map((item) => (\n <button\n key={item}\n type=\"button\"\n disabled={recording}\n onClick={() => setMode(item)}\n className={`flex-1 rounded-md border px-2 py-1 text-[11px] disabled:opacity-50 ${\n mode === item ? 'border-primary bg-primary/10 text-primary' : 'text-muted-foreground'\n }`}\n >\n {item === 'demo' ? '动态画布' : '摄像头'}\n </button>\n ))}\n </div>\n\n <div className=\"overflow-hidden rounded-md border bg-stage-bg\">\n <video\n ref={videoRef}\n muted\n playsInline\n className=\"block w-full\"\n style={{ maxHeight: 170 }}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {!recording ? (\n <button\n type=\"button\"\n onClick={() => void handleStart()}\n className=\"flex items-center gap-1 rounded-md bg-destructive px-2.5 py-1 text-[11px] text-destructive-foreground\"\n >\n <Circle className=\"size-3 fill-current\" />\n 开始录制\n </button>\n ) : (\n <>\n <button\n type=\"button\"\n onClick={handlePauseToggle}\n className=\"flex items-center gap-1 rounded-md bg-secondary px-2.5 py-1 text-[11px]\"\n >\n {paused ? <Play className=\"size-3\" /> : <Pause className=\"size-3\" />}\n {paused ? '继续' : '暂停'}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleStop()}\n className=\"flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-[11px] text-primary-foreground\"\n >\n <Square className=\"size-3\" />\n 停止\n </button>\n </>\n )}\n <span className=\"ml-auto font-mono text-[10px] text-muted-foreground tabular-nums\">\n {(bytes / 1024 / 1024).toFixed(2)} MB · {formatRecorderState(state)}\n </span>\n </div>\n\n {result != null && <ResultPreview blob={result} filename=\"recording.mp4\" />}\n </LabCard>\n )\n}\n\n/** 适配 RecorderHandle(隔离 AVRecorder 细节) */\nfunction createRecorderAdapter(\n stream: MediaStream,\n onChunk: (bytes: number) => void,\n): RecorderHandle {\n const recorder = new AVRecorder(stream, { expectFPS: 30 })\n const chunks: BlobPart[] = []\n let total = 0\n\n return {\n start: () => {\n const readable = recorder.start(300)\n void (async () => {\n const reader = readable.getReader()\n for (;;) {\n try {\n const { done, value } = await reader.read()\n if (done) break\n if (value != null) {\n chunks.push(new Uint8Array(value))\n total += value.byteLength\n onChunk(total)\n }\n } catch {\n break\n }\n }\n })()\n },\n pause: () => recorder.pause(),\n resume: () => recorder.resume(),\n stop: async () => {\n await recorder.stop()\n return new Blob(chunks, { type: 'video/mp4' })\n },\n state: () => recorder.state,\n dispose: () => {\n stream.getTracks().forEach((t) => t.stop())\n },\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { Play, Square } from 'lucide-react'\nimport {\n FrameCounterClip,\n exportCustomClip,\n renderClipFrameToCanvas,\n type CustomClipHandle,\n} from '@cut-workbench/core/lab'\nimport { formatTimecode } from '@cut-workbench/core'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, ResultPreview, SliderRow } from './shared'\n\n/**\n * Lab 8:自定义 IClip。\n *\n * `FrameCounterClip` 是本项目实现的 IClip:不依赖任何媒体文件,\n * 逐帧用 OffscreenCanvas 画出帧号 / 进度条 / 色相变化。\n *\n * 它证明 IClip 接口(tick / ready / meta / clone / split / destroy)可以被任意扩展,\n * 且能直接被 Combinator 编码成 MP4。\n */\nexport function CustomClipLab() {\n const [durationSec, setDurationSec] = useState(5)\n const [timeUs, setTimeUs] = useState(0)\n const [playing, setPlaying] = useState(false)\n const [result, setResult] = useState<Blob | null>(null)\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const clipRef = useRef<CustomClipHandle | null>(null)\n const rafRef = useRef<number | null>(null)\n const { busy, run } = useLabAction()\n\n const durationUs = Math.round(durationSec * 1_000_000)\n\n // 重建 clip(时长变化时)\n useEffect(() => {\n clipRef.current?.destroy()\n try {\n clipRef.current = new FrameCounterClip(640, 360, durationUs, 30)\n } catch (err) {\n console.warn('[CustomClipLab] create failed', err)\n clipRef.current = null\n }\n return () => {\n clipRef.current?.destroy()\n clipRef.current = null\n }\n }, [durationUs])\n\n const draw = useCallback(async () => {\n const clip = clipRef.current\n const canvas = canvasRef.current\n if (clip == null || canvas == null) return\n await renderClipFrameToCanvas(clip, timeUs, canvas)\n }, [timeUs])\n\n useEffect(() => {\n void draw()\n }, [draw])\n\n // 播放\n useEffect(() => {\n if (!playing) return\n let last = performance.now()\n let current = timeUs\n const tick = () => {\n const now = performance.now()\n current += (now - last) * 1000\n last = now\n if (current >= durationUs) {\n setTimeUs(durationUs - 1)\n setPlaying(false)\n return\n }\n setTimeUs(current)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current != null) cancelAnimationFrame(rafRef.current)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [playing, durationUs])\n\n const handleExport = async () => {\n const clip = clipRef.current\n if (clip == null) return\n const output = await run('正在编码自定义 Clip…', () => exportCustomClip(clip, durationUs))\n if (output != null) setResult(output)\n }\n\n return (\n <LabCard\n title=\"自定义 IClip\"\n description=\"纯程序化渲染的 Clip(帧计数器),不依赖任何媒体文件,证明接口可扩展。\"\n apis={['IClip.tick', 'IClip.clone', 'IClip.split', 'Combinator']}\n >\n <div className=\"overflow-hidden rounded-md border bg-stage-bg\">\n <canvas\n ref={canvasRef}\n width={640}\n height={360}\n className=\"block w-full\"\n style={{ maxHeight: 180 }}\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n if (!playing && timeUs >= durationUs - 1) setTimeUs(0)\n setPlaying((p) => !p)\n }}\n className=\"flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-[11px]\"\n >\n {playing ? <Square className=\"size-3\" /> : <Play className=\"size-3\" />}\n {playing ? '停止' : '预览'}\n </button>\n <span className=\"font-mono text-[10px] text-muted-foreground tabular-nums\">\n {formatTimecode(timeUs)} / {formatTimecode(durationUs)}\n </span>\n </div>\n\n <SliderRow\n label=\"时间\"\n value={timeUs}\n min={0}\n max={Math.max(1, durationUs - 1)}\n step={10_000}\n format={(v) => `${(v / 1e6).toFixed(2)}s`}\n onChange={(v) => {\n setPlaying(false)\n setTimeUs(v)\n }}\n />\n <SliderRow\n label=\"时长\"\n value={durationSec}\n min={1}\n max={20}\n step={0.5}\n format={(v) => `${v.toFixed(1)}s`}\n onChange={(v) => {\n setPlaying(false)\n setDurationSec(v)\n setTimeUs(0)\n }}\n />\n\n <button\n type=\"button\"\n disabled={busy != null}\n onClick={() => void handleExport()}\n className=\"rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\n >\n 编码为 MP4\n </button>\n\n {busy != null && <Busy label={busy} />}\n {result != null && <ResultPreview blob={result} filename=\"custom-clip.mp4\" />}\n </LabCard>\n )\n}\n","import { FlaskConical } from 'lucide-react'\r\nimport { ConcatenateLab } from './ConcatenateLab'\r\nimport { RemuxLab } from './RemuxLab'\r\nimport { ChromakeyLab } from './ChromakeyLab'\r\nimport { SubtitleLab } from './SubtitleLab'\r\nimport { TrackSplitLab } from './TrackSplitLab'\r\nimport { ThumbnailLab } from './ThumbnailLab'\r\nimport { RecorderLab } from './RecorderLab'\r\nimport { CustomClipLab } from './CustomClipLab'\r\n\r\n/**\r\n * 能力实验室:每个 WebAV 高级 API 一个可独立运行的卡片。\r\n *\r\n * 与主编辑器共享同一套能力探测结果,因此这里不再重复检测。\r\n */\r\nexport function LabPage() {\r\n return (\r\n <div className=\"mx-auto max-w-7xl space-y-4 p-4\">\r\n <header className=\"space-y-1\">\r\n <div className=\"flex items-center gap-2\">\r\n <FlaskConical className=\"size-4 text-primary\" />\r\n <h1 className=\"text-sm font-semibold\">能力实验室</h1>\r\n </div>\r\n <p className=\"text-xs text-muted-foreground\">\r\n 覆盖 av-cliper / av-canvas / av-recorder 的进阶 API。每个卡片可独立运行,失败时给出可读原因。\r\n </p>\r\n </header>\r\n\r\n <div className=\"grid gap-3 md:grid-cols-2 xl:grid-cols-3\">\r\n <ConcatenateLab />\r\n <RemuxLab />\r\n <ChromakeyLab />\r\n <SubtitleLab />\r\n <TrackSplitLab />\r\n <ThumbnailLab />\r\n <RecorderLab />\r\n <CustomClipLab />\r\n </div>\r\n\r\n <footer className=\"border-t pt-3 text-[10px] leading-relaxed text-muted-foreground\">\r\n 说明:所有输出均为 MP4(H.264 + AAC 48kHz 双声道)。WebAV 生成的是 fMP4 分片,\r\n 部分播放器需「修复时长」;浏览器本地处理,文件不会上传。\r\n </footer>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"lab.cjs","names":["Card","cn","CardHeader","FlaskConical","CardTitle","CardDescription","Badge","CardContent","useRef","useCallback","useWorkbench","useState","useRef","_Fragment","useState","Loader2","useState","readMp4Info","concatMp4","formatResolution","formatDuration","AlertTriangle","useState","mixAudioIntoMp4","useWorkbench","useState","isWebGL2Available","useRef","useCallback","renderChromakeyFrame","createMp4Clip","useState","useRef","parseSrt","createSubtitlePreviewClip","useCallback","renderSubtitleFrame","burnSubtitles","Square","Play","useState","useRef","splitMp4Tracks","Split","formatDuration","useState","extractKeyframes","_Fragment","formatTimecode","useWorkbench","useState","useRef","useCallback","createDemoCanvasStream","fixRecordedDuration","Circle","_Fragment","Play","Pause","Square","formatRecorderState","AVRecorder","useState","useRef","FrameCounterClip","useCallback","renderClipFrameToCanvas","exportCustomClip","Square","Play","formatTimecode","FlaskConical"],"sources":["../src/features/lab/components/LabCard.tsx","../src/features/lab/hooks/use-lab-action.ts","../src/features/lab/components/shared.tsx","../src/features/lab/components/ConcatenateLab.tsx","../src/features/lab/components/RemuxLab.tsx","../src/features/lab/components/ChromakeyLab.tsx","../src/features/lab/components/SubtitleLab.tsx","../src/features/lab/components/TrackSplitLab.tsx","../src/features/lab/components/ThumbnailLab.tsx","../src/features/lab/components/RecorderLab.tsx","../src/features/lab/components/CustomClipLab.tsx","../src/features/lab/components/LabPage.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { FlaskConical } from 'lucide-react'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { cn } from '@/lib/cn'\n\ninterface LabCardProps {\n title: string\n description: string\n apis: string[]\n children: ReactNode\n unavailable?: string | null\n className?: string\n}\n\n/** Lab 卡片外壳:标题 + 用到的 API + (不可用时的原因) */\nexport function LabCard({\n title,\n description,\n apis,\n children,\n unavailable,\n className,\n}: LabCardProps) {\n return (\n <Card className={cn('flex flex-col', className)}>\n <CardHeader className=\"gap-1\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex size-6 items-center justify-center rounded bg-primary/10 text-primary\">\n <FlaskConical className=\"size-3.5\" />\n </div>\n <CardTitle className=\"text-sm\">{title}</CardTitle>\n </div>\n <CardDescription className=\"text-xs\">{description}</CardDescription>\n <div className=\"flex flex-wrap gap-1 pt-1\">\n {apis.map((api) => (\n <Badge\n key={api}\n variant=\"outline\"\n className=\"font-mono text-[10px] font-normal text-muted-foreground\"\n >\n {api}\n </Badge>\n ))}\n </div>\n </CardHeader>\n <CardContent className=\"flex flex-1 flex-col gap-2.5\">\n {unavailable != null ? (\n <div className=\"rounded-md border border-dashed border-warning/40 bg-warning/5 p-3 text-xs text-warning\">\n {unavailable}\n </div>\n ) : (\n children\n )}\n </CardContent>\n </Card>\n )\n}\n","import { useCallback, useRef } from 'react'\nimport { useState } from 'react'\nimport { useWorkbench } from '@/context'\nimport { notify } from '@/lib/notify'\n\n/** 生成可下载链接(Lab 通用) */\nexport function useDownload(): (blob: Blob, filename: string) => void {\n const urlsRef = useRef<string[]>([])\n return useCallback((blob: Blob, filename: string) => {\n const url = URL.createObjectURL(blob)\n urlsRef.current.push(url)\n const link = document.createElement('a')\n link.href = url\n link.download = filename\n document.body.appendChild(link)\n link.click()\n link.remove()\n window.setTimeout(() => URL.revokeObjectURL(url), 15_000)\n }, [])\n}\n\n/** 带 busy 状态的异步执行器:统一错误 toast,禁止静默吞异常 */\nexport function useLabAction() {\n const { bridge } = useWorkbench()\n const [busy, setBusy] = useState<string | null>(null)\n const run = useCallback(\n async <T,>(label: string, task: () => Promise<T>): Promise<T | null> => {\n setBusy(label)\n try {\n return await task()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n console.error(err)\n notify(bridge, { level: 'error', title: '操作失败', description: message })\n return null\n } finally {\n setBusy(null)\n }\n },\n [bridge],\n )\n return { busy, run }\n}\n","import { Loader2 } from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport { useDownload } from '../hooks/use-lab-action'\n\n/**\n * Lab 共享的可视组件。\n *\n * 只导出组件(React Fast Refresh 要求单文件只导出组件);\n * 工具函数与 hook 放在 `../hooks/use-lab-action`。\n */\n\n/** 文件选择按钮 */\nexport function FilePickButton({\n label,\n accept,\n multiple,\n onFiles,\n disabled,\n}: {\n label: string\n accept: string\n multiple?: boolean\n onFiles: (files: File[]) => void\n disabled?: boolean\n}) {\n const inputRef = useRef<HTMLInputElement | null>(null)\n return (\n <>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n className=\"hidden\"\n onChange={(e) => {\n const files = Array.from(e.target.files ?? [])\n if (files.length > 0) onFiles(files)\n e.target.value = ''\n }}\n />\n <button\n type=\"button\"\n disabled={disabled}\n onClick={() => inputRef.current?.click()}\n className=\"flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed px-3 py-3.5 text-xs text-muted-foreground transition-colors hover:border-primary/50 hover:text-foreground disabled:pointer-events-none disabled:opacity-50\"\n >\n <span className=\"max-w-full truncate\">{label}</span>\n </button>\n </>\n )\n}\n\n/** 结果预览:视频 + 下载 */\nexport function ResultPreview({ blob, filename }: { blob: Blob; filename: string }) {\n const download = useDownload()\n const [url] = useState(() => URL.createObjectURL(blob))\n useEffect(() => () => URL.revokeObjectURL(url), [url])\n\n return (\n <div className=\"space-y-1.5\">\n <video\n src={url}\n controls\n className=\"w-full rounded-md border bg-stage-bg\"\n style={{ maxHeight: 200 }}\n />\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"min-w-0 flex-1 truncate text-[10px] text-muted-foreground\">\n {(blob.size / 1024 / 1024).toFixed(2)} MB\n </span>\n <button\n type=\"button\"\n onClick={() => download(blob, filename)}\n className=\"rounded-md bg-primary px-2 py-1 text-[11px] text-primary-foreground hover:bg-primary/90\"\n >\n 下载\n </button>\n </div>\n </div>\n )\n}\n\n/** 忙碌提示 */\nexport function Busy({ label }: { label: string }) {\n return (\n <div className=\"flex items-center gap-2 rounded-md bg-muted px-3 py-2 text-xs text-muted-foreground\">\n <Loader2 className=\"size-3 animate-spin\" />\n {label}\n </div>\n )\n}\n\n/** 简易滑杆 */\nexport function SliderRow({\n label,\n value,\n min,\n max,\n step,\n format,\n onChange,\n}: {\n label: string\n value: number\n min: number\n max: number\n step: number\n format?: (value: number) => string\n onChange: (value: number) => void\n}) {\n return (\n <div className=\"space-y-1\">\n <div className=\"flex justify-between text-[10px] text-muted-foreground\">\n <span>{label}</span>\n <span className=\"font-mono tabular-nums\">{format ? format(value) : value.toFixed(2)}</span>\n </div>\n <input\n type=\"range\"\n min={min}\n max={max}\n step={step}\n value={value}\n onChange={(e) => onChange(Number(e.target.value))}\n className=\"h-1 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary\"\n />\n </div>\n )\n}\n","import { useState } from 'react'\nimport { AlertTriangle } from 'lucide-react'\nimport { concatMp4, readMp4Info, type Mp4Info } from '@cut-workbench/core/lab'\nimport { formatDuration, formatResolution } from '@/lib/format'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, FilePickButton, ResultPreview } from './shared'\n\n/**\n * Lab 1:无损拼接。\n *\n * 用 `fastConcatMP4` 直接拼接多个 MP4 的样本,不重编码。\n * 前提是编码 / 分辨率 / 采样率完全一致;不一致时本 Lab 会先预检并给出具体差异。\n */\nexport function ConcatenateLab() {\n const [files, setFiles] = useState<File[]>([])\n const [infos, setInfos] = useState<Mp4Info[]>([])\n const [result, setResult] = useState<Blob | null>(null)\n const { busy, run } = useLabAction()\n\n const handleFiles = async (picked: File[]) => {\n setFiles(picked)\n setResult(null)\n setInfos([])\n const detected = await run('正在读取文件信息…', async () => {\n const list: Mp4Info[] = []\n for (const file of picked) list.push(await readMp4Info(file))\n return list\n })\n if (detected != null) setInfos(detected)\n }\n\n const handleConcat = async () => {\n const output = await run('正在无损拼接…', () => concatMp4(files))\n if (output != null) {\n setResult(output.blob)\n setInfos(output.infos)\n }\n }\n\n const first = infos[0]\n const mismatch =\n first != null &&\n infos.some(\n (info) =>\n info.width !== first.width ||\n info.height !== first.height ||\n info.codec !== first.codec ||\n info.audioSampleRate !== first.audioSampleRate,\n )\n\n return (\n <LabCard\n title=\"无损拼接\"\n description=\"直接把多个 MP4 的样本拼在一起,不重编码,速度极快。要求规格完全一致。\"\n apis={['fastConcatMP4', 'MP4Clip.getFileHeaderBinData']}\n >\n <FilePickButton\n label={files.length > 0 ? `已选 ${files.length} 个文件(重新选择)` : '选择 2 个以上 MP4'}\n accept=\"video/mp4\"\n multiple\n onFiles={(picked) => void handleFiles(picked)}\n disabled={busy != null}\n />\n\n {files.length > 0 && (\n <ul className=\"space-y-1 text-[10px] text-muted-foreground\">\n {files.map((file, index) => {\n const info = infos[index]\n return (\n <li key={`${file.name}-${index}`} className=\"flex justify-between gap-2\">\n <span className=\"truncate\">{file.name}</span>\n <span className=\"shrink-0 font-mono\">\n {info != null\n ? `${formatResolution(info.width, info.height)} · ${formatDuration(info.durationUs)} · ${info.codec}`\n : '检测中…'}\n </span>\n </li>\n )\n })}\n </ul>\n )}\n\n {mismatch && (\n <div className=\"flex items-start gap-1.5 rounded-md bg-warning/10 p-2 text-[10px] text-warning\">\n <AlertTriangle className=\"mt-0.5 size-3 shrink-0\" />\n 检测到规格不一致,拼接会失败并给出具体原因(fastConcatMP4 的硬性要求)。\n </div>\n )}\n\n <button\n type=\"button\"\n disabled={files.length < 2 || busy != null}\n onClick={() => void handleConcat()}\n className=\"rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\n >\n 开始拼接\n </button>\n\n {busy != null && <Busy label={busy} />}\n {result != null && <ResultPreview blob={result} filename=\"concat.mp4\" />}\n </LabCard>\n )\n}\n","import { useState } from 'react'\r\nimport { mixAudioIntoMp4 } from '@cut-workbench/core/lab'\r\nimport { LabCard } from './LabCard'\r\nimport { useLabAction } from '../hooks/use-lab-action'\r\nimport { Busy, FilePickButton, ResultPreview, SliderRow } from './shared'\r\n\r\n/**\r\n * Lab 2:配音。\r\n *\r\n * `mixinMP4AndAudio` 只重编码音频轨,视频轨原样复制 —— 因此速度快、画质无损。\r\n */\r\nexport function RemuxLab() {\r\n const [video, setVideo] = useState<File | null>(null)\r\n const [audio, setAudio] = useState<File | null>(null)\r\n const [volume, setVolume] = useState(1)\r\n const [loop, setLoop] = useState(true)\r\n const [result, setResult] = useState<Blob | null>(null)\r\n const { busy, run } = useLabAction()\r\n\r\n const handleMix = async () => {\r\n if (video == null || audio == null) return\r\n const output = await run('正在混合音频…', () =>\r\n mixAudioIntoMp4(video, audio, volume, loop),\r\n )\r\n if (output != null) setResult(output)\r\n }\r\n\r\n return (\r\n <LabCard\r\n title=\"视频配音\"\r\n description=\"把音轨混入 MP4:只重编码音频,视频轨不动,画质零损失。\"\r\n apis={['mixinMP4AndAudio']}\r\n >\r\n <FilePickButton\r\n label={video == null ? '选择视频(MP4)' : video.name}\r\n accept=\"video/mp4\"\r\n onFiles={(files) => {\r\n setVideo(files[0] ?? null)\r\n setResult(null)\r\n }}\r\n disabled={busy != null}\r\n />\r\n <FilePickButton\r\n label={audio == null ? '选择音频(MP3/WAV/M4A)' : audio.name}\r\n accept=\"audio/*\"\r\n onFiles={(files) => {\r\n setAudio(files[0] ?? null)\r\n setResult(null)\r\n }}\r\n disabled={busy != null}\r\n />\r\n\r\n <SliderRow\r\n label=\"音量\"\r\n value={volume}\r\n min={0}\r\n max={2}\r\n step={0.05}\r\n format={(v) => `${Math.round(v * 100)}%`}\r\n onChange={setVolume}\r\n />\r\n <label className=\"flex items-center gap-2 text-[11px] text-muted-foreground\">\r\n <input\r\n type=\"checkbox\"\r\n checked={loop}\r\n onChange={(e) => setLoop(e.target.checked)}\r\n className=\"accent-primary\"\r\n />\r\n 音频短于视频时循环填充\r\n </label>\r\n\r\n <button\r\n type=\"button\"\r\n disabled={video == null || audio == null || busy != null}\r\n onClick={() => void handleMix()}\r\n className=\"rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\r\n >\r\n 开始混合\r\n </button>\r\n\r\n {busy != null && <Busy label={busy} />}\r\n {result != null && <ResultPreview blob={result} filename=\"mixin.mp4\" />}\r\n </LabCard>\r\n )\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { isWebGL2Available, renderChromakeyFrame, createMp4Clip, type Mp4ClipHandle } from '@cut-workbench/core/lab'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { useWorkbench } from '@/context'\nimport { notify } from '@/lib/notify'\nimport { Busy, FilePickButton, SliderRow } from './shared'\n\n/**\n * Lab 3:绿幕抠像。\n *\n * `createChromakey` 返回一个函数,把 VideoFrame 转成去背后的 ImageBitmap。\n * 配合 `MP4Clip.tickInterceptor` 即可对整段视频生效(导出时同样有效)。\n *\n * ⚠️ 依赖 WebGL2;不支持时本卡片隐藏操作、仅显示原因。\n */\n/**\n * 抠像结果的兜底底色:读取主题令牌,而不是写死 `#101018`。\n *\n * 抠像后的 VideoFrame 带透明通道,需要一层底色才能看清;用 `--cw-stage-bg`\n * 让 Lab 预览区与工作台舞台保持一致,宿主换主题时一起变。\n * 取不到时退回深色(纯逻辑环境 / 未包 `.cw-root`)。\n */\nfunction readStageBackground(): string {\n if (typeof document === 'undefined') return '#101018'\n const root = document.querySelector('.cw-root')\n if (root == null) return '#101018'\n const value = getComputedStyle(root).getPropertyValue('--cw-stage-bg').trim()\n return value.length > 0 ? value : '#101018'\n}\n\nexport function ChromakeyLab() {\n const { bridge } = useWorkbench()\n // WebGL2 探测是纯函数,可安全在 useState 初始化中执行\n const [webgl2] = useState(isWebGL2Available)\n const [file, setFile] = useState<File | null>(null)\n const [similarity, setSimilarity] = useState(0.4)\n const [smoothness, setSmoothness] = useState(0.1)\n const [spill, setSpill] = useState(0.1)\n const [timeUs, setTimeUs] = useState(0)\n const [durationUs, setDurationUs] = useState(0)\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const clipRef = useRef<Mp4ClipHandle | null>(null)\n const { busy, run } = useLabAction()\n\n useEffect(() => {\n return () => {\n clipRef.current?.destroy()\n clipRef.current = null\n }\n }, [])\n\n const render = useCallback(async () => {\n const clip = clipRef.current\n const canvas = canvasRef.current\n if (clip == null || canvas == null) return\n await renderChromakeyFrame(\n clip,\n timeUs,\n { similarity, smoothness, spill, keyColor: [0, 255, 0] },\n canvas,\n // 底色由主题令牌驱动:抠像结果画在透明画布上,用舞台色兜底,\n // 这样宿主换主题时 Lab 预览区也跟着变\n readStageBackground(),\n )\n }, [similarity, smoothness, spill, timeUs])\n\n const handleFile = async (files: File[]) => {\n const picked = files[0]\n if (picked == null) return\n const result = await run('正在解析视频…', async () => {\n clipRef.current?.destroy()\n const created = await createMp4Clip(picked)\n return created\n })\n if (result == null) return\n clipRef.current = result.clip\n setFile(picked)\n setDurationUs(result.meta.duration)\n setTimeUs(Math.min(result.meta.duration / 2, 1_000_000))\n notify(bridge, { level: 'success', title: '已载入视频,拖动参数查看抠像效果' })\n }\n\n useEffect(() => {\n if (clipRef.current == null || busy != null) return\n void render()\n }, [busy, render])\n\n return (\n <LabCard\n title=\"绿幕抠像\"\n description=\"用 WebGL2 实时扣除背景色,通过 tickInterceptor 作用到整段视频。\"\n apis={['createChromakey', 'MP4Clip.tickInterceptor']}\n unavailable={webgl2 ? null : '当前设备不支持 WebGL2,createChromakey 无法运行。'}\n >\n <FilePickButton\n label={file?.name ?? '选择绿幕视频(MP4)'}\n accept=\"video/mp4\"\n onFiles={(files) => void handleFile(files)}\n disabled={busy != null}\n />\n\n <div className=\"relative overflow-hidden rounded-md border bg-stage-bg\">\n <canvas ref={canvasRef} className=\"block w-full\" style={{ maxHeight: 180 }} />\n {file == null && (\n <div className=\"pointer-events-none absolute inset-0 flex h-24 items-center justify-center text-[10px] text-muted-foreground\">\n 选择视频后显示抠像结果\n </div>\n )}\n </div>\n\n <SliderRow\n label=\"相似度 similarity\"\n value={similarity}\n min={0}\n max={1}\n step={0.01}\n format={(v) => v.toFixed(2)}\n onChange={setSimilarity}\n />\n <SliderRow\n label=\"平滑度 smoothness\"\n value={smoothness}\n min={0}\n max={1}\n step={0.01}\n format={(v) => v.toFixed(2)}\n onChange={setSmoothness}\n />\n <SliderRow\n label=\"饱和度 spill\"\n value={spill}\n min={0}\n max={1}\n step={0.01}\n format={(v) => v.toFixed(2)}\n onChange={setSpill}\n />\n {durationUs > 0 && (\n <SliderRow\n label=\"取帧时间\"\n value={timeUs}\n min={0}\n max={Math.max(1, durationUs - 1)}\n step={100_000}\n format={(v) => `${(v / 1e6).toFixed(2)}s`}\n onChange={setTimeUs}\n />\n )}\n {busy != null && <Busy label={busy} />}\n <p className=\"text-[10px] leading-relaxed text-muted-foreground\">\n keyColor 固定为纯绿 (0,255,0)。实际项目中可读取首帧像素自动取色。\n </p>\n </LabCard>\n )\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { Play, Square } from 'lucide-react'\nimport {\n burnSubtitles,\n createSubtitlePreviewClip,\n renderSubtitleFrame,\n type SubtitleClipHandle,\n} from '@cut-workbench/core/lab'\nimport { parseSrt } from '@cut-workbench/core'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, ResultPreview, SliderRow } from './shared'\n\nconst SAMPLE_SRT = `1\n00:00:00,000 --> 00:00:02,000\n这是第一句字幕\n\n2\n00:00:02,000 --> 00:00:04,000\n第二句会带描边\n\n3\n00:00:04,000 --> 00:00:06,000\nEmbedSubtitlesClip 烧录进画面\n`\n\n/**\n * Lab 4:字幕烧录。\n *\n * `EmbedSubtitlesClip` 本身就是一个 IClip:tick 返回带字幕的 VideoFrame。\n * 因此既能放进 Combinator 导出,也能直接逐帧预览。\n */\nexport function SubtitleLab() {\n const [srt, setSrt] = useState(SAMPLE_SRT)\n const [timeUs, setTimeUs] = useState(0)\n const [playing, setPlaying] = useState(false)\n const [result, setResult] = useState<Blob | null>(null)\n const { busy, run } = useLabAction()\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const clipRef = useRef<SubtitleClipHandle | null>(null)\n const rafRef = useRef<number | null>(null)\n\n const cues = parseSrt(srt)\n const durationUs = cues.length > 0 ? cues[cues.length - 1]!.endMs * 1000 : 0\n\n // 重新创建字幕 clip\n useEffect(() => {\n clipRef.current?.destroy()\n if (cues.length === 0) {\n clipRef.current = null\n return\n }\n try {\n clipRef.current = createSubtitlePreviewClip(srt, 640, 360)\n } catch (err) {\n console.warn('[SubtitleLab] create failed', err)\n clipRef.current = null\n }\n return () => {\n clipRef.current?.destroy()\n clipRef.current = null\n }\n }, [cues.length, srt])\n\n const draw = useCallback(async () => {\n const clip = clipRef.current\n const canvas = canvasRef.current\n if (clip == null || canvas == null) return\n await renderSubtitleFrame(clip, timeUs, canvas)\n }, [timeUs])\n\n useEffect(() => {\n void draw()\n }, [draw])\n\n // 播放循环(简单 rAF 递增时间)\n useEffect(() => {\n if (!playing) return\n let last = performance.now()\n let current = timeUs\n const tick = () => {\n const now = performance.now()\n current += (now - last) * 1000\n last = now\n if (durationUs > 0 && current >= durationUs) {\n setTimeUs(durationUs)\n setPlaying(false)\n return\n }\n setTimeUs(current)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current != null) cancelAnimationFrame(rafRef.current)\n }\n // 只在开始播放时启动\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [playing, durationUs])\n\n const handleBurn = async () => {\n const width = 1280\n const height = 720\n const output = await run('正在烧录字幕…', () =>\n burnSubtitles(srt, width, height, durationUs),\n )\n if (output != null) setResult(output)\n }\n\n return (\n <LabCard\n title=\"字幕烧录\"\n description=\"SRT 解析后逐帧绘制到画面,最终编码进视频流(硬字幕)。\"\n apis={['EmbedSubtitlesClip', 'Combinator']}\n >\n <textarea\n value={srt}\n onChange={(e) => setSrt(e.target.value)}\n rows={5}\n className=\"w-full rounded-md border bg-transparent p-2 font-mono text-[10px] outline-none focus-visible:border-ring\"\n placeholder=\"粘贴 SRT 内容\"\n />\n\n <div className=\"overflow-hidden rounded-md border bg-stage-bg\">\n <canvas\n ref={canvasRef}\n width={640}\n height={360}\n className=\"block w-full\"\n style={{ maxHeight: 180 }}\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPlaying((p) => !p)}\n disabled={cues.length === 0}\n className=\"flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-[11px] disabled:opacity-50\"\n >\n {playing ? <Square className=\"size-3\" /> : <Play className=\"size-3\" />}\n {playing ? '停止' : '预览'}\n </button>\n <span className=\"text-[10px] text-muted-foreground\">\n {cues.length} 条 · {(timeUs / 1e6).toFixed(2)}s / {(durationUs / 1e6).toFixed(2)}s\n </span>\n </div>\n\n <SliderRow\n label=\"时间\"\n value={timeUs}\n min={0}\n max={Math.max(1, durationUs)}\n step={10_000}\n format={(v) => `${(v / 1e6).toFixed(2)}s`}\n onChange={(v) => {\n setPlaying(false)\n setTimeUs(v)\n }}\n />\n\n <button\n type=\"button\"\n disabled={cues.length === 0 || busy != null}\n onClick={() => void handleBurn()}\n className=\"rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\n >\n 烧录并导出 MP4\n </button>\n\n {busy != null && <Busy label={busy} />}\n {result != null && <ResultPreview blob={result} filename=\"subtitles.mp4\" />}\n </LabCard>\n )\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { Split } from 'lucide-react'\nimport { splitMp4Tracks, type TrackSplitResult } from '@cut-workbench/core/lab'\nimport { formatDuration } from '@/lib/format'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, FilePickButton } from './shared'\n\n/**\n * Lab 5:轨道拆分。\n *\n * `splitTrack()` 从一个 MP4Clip 派生出「纯视频」与「纯音频」两个 clip,\n * 它们与源共享样本表,因此内存开销很小。\n */\nexport function TrackSplitLab() {\n const [file, setFile] = useState<File | null>(null)\n const [result, setResult] = useState<TrackSplitResult | null>(null)\n const { busy, run } = useLabAction()\n const cleanupRef = useRef<(() => void) | null>(null)\n\n useEffect(() => {\n return () => cleanupRef.current?.()\n }, [])\n\n const handleSplit = async () => {\n if (file == null) return\n cleanupRef.current?.()\n const output = await run('正在拆分轨道…', () => splitMp4Tracks(file))\n setResult(output)\n cleanupRef.current = () => {\n output?.videoClip?.destroy()\n output?.audioClip?.destroy()\n }\n }\n\n return (\n <LabCard\n title=\"轨道拆分\"\n description=\"把一个 MP4 拆成纯视频轨与纯音频轨两个独立 IClip,用于分别编辑。\"\n apis={['MP4Clip.splitTrack']}\n >\n <FilePickButton\n label={file?.name ?? '选择 MP4'}\n accept=\"video/mp4\"\n onFiles={(files) => {\n setFile(files[0] ?? null)\n setResult(null)\n }}\n disabled={busy != null}\n />\n\n <button\n type=\"button\"\n disabled={file == null || busy != null}\n onClick={() => void handleSplit()}\n className=\"flex items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\n >\n <Split className=\"size-3\" />\n 拆分轨道\n </button>\n\n {busy != null && <Busy label={busy} />}\n\n {result != null && (\n <div className=\"grid gap-1.5 text-[10px]\">\n <TrackResult\n label=\"视频轨\"\n present={result.videoMeta != null}\n detail={\n result.videoMeta != null\n ? `${result.videoMeta.width}×${result.videoMeta.height} · ${formatDuration(result.videoMeta.duration)}`\n : '源文件无视频轨'\n }\n />\n <TrackResult\n label=\"音频轨\"\n present={result.audioMeta != null}\n detail={\n result.audioMeta != null\n ? `48kHz 双声道 · ${formatDuration(result.audioMeta.duration)}`\n : '源文件无音频轨'\n }\n />\n <p className=\"pt-1 leading-relaxed text-muted-foreground\">\n 拆分出的 clip 与源共享样本表,可分别创建 Sprite 放到不同轨道;两者在时间轴上对齐即等于原片。\n </p>\n </div>\n )}\n </LabCard>\n )\n}\n\nfunction TrackResult({\n label,\n present,\n detail,\n}: {\n label: string\n present: boolean\n detail: string\n}) {\n return (\n <div className=\"flex items-center justify-between gap-2 rounded-md border px-2 py-1.5\">\n <span className={present ? 'text-foreground' : 'text-muted-foreground'}>{label}</span>\n <span className={present ? 'font-mono text-success' : 'text-muted-foreground'}>\n {present ? detail : detail}\n </span>\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\r\nimport { extractKeyframes } from '@cut-workbench/core/lab'\r\nimport { formatTimecode } from '@cut-workbench/core'\r\nimport { LabCard } from './LabCard'\r\nimport { useLabAction } from '../hooks/use-lab-action'\r\nimport { Busy, FilePickButton } from './shared'\r\n\r\ninterface FrameItem {\r\n ts: number\r\n url: string\r\n size: number\r\n}\r\n\r\n/**\r\n * Lab 6:缩略图抽帧。\r\n *\r\n * `MP4Clip.thumbnails()` 默认每个关键帧生成一张;也可以传 step 按固定间隔取。\r\n * 这里展示全部关键帧与时间戳。\r\n */\r\nexport function ThumbnailLab() {\r\n const [file, setFile] = useState<File | null>(null)\r\n const [frames, setFrames] = useState<FrameItem[]>([])\r\n const [width, setWidth] = useState(120)\r\n const { busy, run } = useLabAction()\r\n\r\n useEffect(() => {\r\n return () => {\r\n for (const frame of frames) URL.revokeObjectURL(frame.url)\r\n }\r\n }, [frames])\r\n\r\n const handleExtract = async () => {\r\n if (file == null) return\r\n const output = await run('正在抽取关键帧…', () => extractKeyframes(file, width))\r\n if (output != null) setFrames(output)\r\n }\r\n\r\n return (\r\n <LabCard\r\n title=\"缩略图抽帧\"\r\n description=\"按关键帧抽取缩略图,可用于时间轴缩略图带或场景切分预览。\"\r\n apis={['MP4Clip.thumbnails']}\r\n >\r\n <FilePickButton\r\n label={file?.name ?? '选择 MP4'}\r\n accept=\"video/mp4\"\r\n onFiles={(files) => {\r\n setFile(files[0] ?? null)\r\n setFrames([])\r\n }}\r\n disabled={busy != null}\r\n />\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <label className=\"text-[10px] text-muted-foreground\">宽度</label>\r\n <input\r\n type=\"number\"\r\n min={40}\r\n max={480}\r\n step={20}\r\n value={width}\r\n onChange={(e) => setWidth(Number(e.target.value))}\r\n className=\"h-6 w-16 rounded border bg-transparent px-1.5 text-[11px]\"\r\n />\r\n <button\r\n type=\"button\"\r\n disabled={file == null || busy != null}\r\n onClick={() => void handleExtract()}\r\n className=\"ml-auto rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\r\n >\r\n 抽取\r\n </button>\r\n </div>\r\n\r\n {busy != null && <Busy label={busy} />}\r\n\r\n {frames.length > 0 && (\r\n <>\r\n <p className=\"text-[10px] text-muted-foreground\">共 {frames.length} 张关键帧</p>\r\n <div className=\"thin-scrollbar grid max-h-52 grid-cols-3 gap-1.5 overflow-auto\">\r\n {frames.map((frame, index) => (\r\n <figure key={`${frame.ts}-${index}`} className=\"space-y-0.5\">\r\n <img\r\n src={frame.url}\r\n alt=\"\"\r\n className=\"w-full rounded border object-cover\"\r\n loading=\"lazy\"\r\n />\r\n <figcaption className=\"font-mono text-[9px] text-muted-foreground tabular-nums\">\r\n {formatTimecode(frame.ts)}\r\n </figcaption>\r\n </figure>\r\n ))}\r\n </div>\r\n </>\r\n )}\r\n </LabCard>\r\n )\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { Circle, Pause, Play, Square } from 'lucide-react'\nimport {\n AVRecorder,\n createDemoCanvasStream,\n fixRecordedDuration,\n formatRecorderState,\n type RecorderHandle,\n} from '@cut-workbench/core/lab'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { ResultPreview } from './shared'\nimport { useWorkbench } from '@/context'\nimport { notify } from '@/lib/notify'\n\ntype Mode = 'demo' | 'camera'\n\n/**\n * Lab 7:屏幕 / 画布录制。\n *\n * `AVRecorder` 接收任意 MediaStream 并输出 MP4(H.264 + AAC)。\n * 这里提供两种源:\n * - 程序化画布(Canvas.captureStream,无需授权);\n * - 摄像头(getUserMedia,需要用户授权)。\n */\nexport function RecorderLab() {\n const { bridge } = useWorkbench()\n const [mode, setMode] = useState<Mode>('demo')\n const [recording, setRecording] = useState(false)\n const [paused, setPaused] = useState(false)\n const [bytes, setBytes] = useState(0)\n const [state, setState] = useState<string>('inactive')\n const [result, setResult] = useState<Blob | null>(null)\n const [previewStream, setPreviewStream] = useState<MediaStream | null>(null)\n const videoRef = useRef<HTMLVideoElement | null>(null)\n const recorderRef = useRef<RecorderHandle | null>(null)\n const demoRef = useRef<{ stream: MediaStream; stop: () => void } | null>(null)\n const cameraRef = useRef<MediaStream | null>(null)\n const { run } = useLabAction()\n\n // 预览流挂到 <video>\n useEffect(() => {\n const video = videoRef.current\n if (video == null) return\n video.srcObject = previewStream\n if (previewStream != null) void video.play().catch(() => undefined)\n return () => {\n if (video.srcObject === previewStream) video.srcObject = null\n }\n }, [previewStream])\n\n const cleanup = useCallback(() => {\n demoRef.current?.stop()\n demoRef.current = null\n cameraRef.current?.getTracks().forEach((t) => t.stop())\n cameraRef.current = null\n recorderRef.current?.dispose()\n recorderRef.current = null\n setPreviewStream(null)\n }, [])\n\n useEffect(() => cleanup, [cleanup])\n\n const acquireStream = async (): Promise<MediaStream | null> => {\n cleanup()\n if (mode === 'demo') {\n const demo = createDemoCanvasStream(1280, 720)\n demoRef.current = demo\n setPreviewStream(demo.stream)\n return demo.stream\n }\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })\n cameraRef.current = stream\n setPreviewStream(stream)\n return stream\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n notify(bridge, { level: 'error', title: '无法访问摄像头', description: message })\n return null\n }\n }\n\n const handleStart = async () => {\n const stream = await acquireStream()\n if (stream == null) return\n setBytes(0)\n setResult(null)\n const recorder = createRecorderAdapter(stream, (total) => setBytes(total))\n recorderRef.current = recorder\n recorder.start()\n setRecording(true)\n setPaused(false)\n setState(recorder.state())\n }\n\n const handlePauseToggle = () => {\n const recorder = recorderRef.current\n if (recorder == null) return\n if (paused) {\n recorder.resume()\n setPaused(false)\n } else {\n recorder.pause()\n setPaused(true)\n }\n setState(recorder.state())\n }\n\n const handleStop = async () => {\n const recorder = recorderRef.current\n if (recorder == null) return\n const output = await run('正在封装 MP4…', async () => {\n const raw = await recorder.stop()\n // AVRecorder 输出的是 fMP4,moov 时长为 0,需修复后播放器才显示正确总长\n return fixRecordedDuration(raw)\n })\n setRecording(false)\n setPaused(false)\n setState(recorder.state())\n recorderRef.current = null\n if (output != null) {\n setResult(output)\n notify(bridge, {\n level: 'success',\n title: `录制完成 · ${(output.size / 1024 / 1024).toFixed(2)} MB`,\n })\n }\n }\n\n return (\n <LabCard\n title=\"屏幕 / 画布录制\"\n description=\"用 AVRecorder 把 MediaStream 实时编码为 MP4。可录制程序化画布或摄像头。\"\n apis={['AVCanvas.captureStream', 'AVRecorder.start/pause/resume/stop']}\n >\n <div className=\"flex gap-1.5\">\n {(['demo', 'camera'] as Mode[]).map((item) => (\n <button\n key={item}\n type=\"button\"\n disabled={recording}\n onClick={() => setMode(item)}\n className={`flex-1 rounded-md border px-2 py-1 text-[11px] disabled:opacity-50 ${\n mode === item ? 'border-primary bg-primary/10 text-primary' : 'text-muted-foreground'\n }`}\n >\n {item === 'demo' ? '动态画布' : '摄像头'}\n </button>\n ))}\n </div>\n\n <div className=\"overflow-hidden rounded-md border bg-stage-bg\">\n <video\n ref={videoRef}\n muted\n playsInline\n className=\"block w-full\"\n style={{ maxHeight: 170 }}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {!recording ? (\n <button\n type=\"button\"\n onClick={() => void handleStart()}\n className=\"flex items-center gap-1 rounded-md bg-destructive px-2.5 py-1 text-[11px] text-destructive-foreground\"\n >\n <Circle className=\"size-3 fill-current\" />\n 开始录制\n </button>\n ) : (\n <>\n <button\n type=\"button\"\n onClick={handlePauseToggle}\n className=\"flex items-center gap-1 rounded-md bg-secondary px-2.5 py-1 text-[11px]\"\n >\n {paused ? <Play className=\"size-3\" /> : <Pause className=\"size-3\" />}\n {paused ? '继续' : '暂停'}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleStop()}\n className=\"flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-[11px] text-primary-foreground\"\n >\n <Square className=\"size-3\" />\n 停止\n </button>\n </>\n )}\n <span className=\"ml-auto font-mono text-[10px] text-muted-foreground tabular-nums\">\n {(bytes / 1024 / 1024).toFixed(2)} MB · {formatRecorderState(state)}\n </span>\n </div>\n\n {result != null && <ResultPreview blob={result} filename=\"recording.mp4\" />}\n </LabCard>\n )\n}\n\n/** 适配 RecorderHandle(隔离 AVRecorder 细节) */\nfunction createRecorderAdapter(\n stream: MediaStream,\n onChunk: (bytes: number) => void,\n): RecorderHandle {\n const recorder = new AVRecorder(stream, { expectFPS: 30 })\n const chunks: BlobPart[] = []\n let total = 0\n\n return {\n start: () => {\n const readable = recorder.start(300)\n void (async () => {\n const reader = readable.getReader()\n for (;;) {\n try {\n const { done, value } = await reader.read()\n if (done) break\n if (value != null) {\n chunks.push(new Uint8Array(value))\n total += value.byteLength\n onChunk(total)\n }\n } catch {\n break\n }\n }\n })()\n },\n pause: () => recorder.pause(),\n resume: () => recorder.resume(),\n stop: async () => {\n await recorder.stop()\n return new Blob(chunks, { type: 'video/mp4' })\n },\n state: () => recorder.state,\n dispose: () => {\n stream.getTracks().forEach((t) => t.stop())\n },\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { Play, Square } from 'lucide-react'\nimport {\n FrameCounterClip,\n exportCustomClip,\n renderClipFrameToCanvas,\n type CustomClipHandle,\n} from '@cut-workbench/core/lab'\nimport { formatTimecode } from '@cut-workbench/core'\nimport { LabCard } from './LabCard'\nimport { useLabAction } from '../hooks/use-lab-action'\nimport { Busy, ResultPreview, SliderRow } from './shared'\n\n/**\n * Lab 8:自定义 IClip。\n *\n * `FrameCounterClip` 是本项目实现的 IClip:不依赖任何媒体文件,\n * 逐帧用 OffscreenCanvas 画出帧号 / 进度条 / 色相变化。\n *\n * 它证明 IClip 接口(tick / ready / meta / clone / split / destroy)可以被任意扩展,\n * 且能直接被 Combinator 编码成 MP4。\n */\nexport function CustomClipLab() {\n const [durationSec, setDurationSec] = useState(5)\n const [timeUs, setTimeUs] = useState(0)\n const [playing, setPlaying] = useState(false)\n const [result, setResult] = useState<Blob | null>(null)\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const clipRef = useRef<CustomClipHandle | null>(null)\n const rafRef = useRef<number | null>(null)\n const { busy, run } = useLabAction()\n\n const durationUs = Math.round(durationSec * 1_000_000)\n\n // 重建 clip(时长变化时)\n useEffect(() => {\n clipRef.current?.destroy()\n try {\n clipRef.current = new FrameCounterClip(640, 360, durationUs, 30)\n } catch (err) {\n console.warn('[CustomClipLab] create failed', err)\n clipRef.current = null\n }\n return () => {\n clipRef.current?.destroy()\n clipRef.current = null\n }\n }, [durationUs])\n\n const draw = useCallback(async () => {\n const clip = clipRef.current\n const canvas = canvasRef.current\n if (clip == null || canvas == null) return\n await renderClipFrameToCanvas(clip, timeUs, canvas)\n }, [timeUs])\n\n useEffect(() => {\n void draw()\n }, [draw])\n\n // 播放\n useEffect(() => {\n if (!playing) return\n let last = performance.now()\n let current = timeUs\n const tick = () => {\n const now = performance.now()\n current += (now - last) * 1000\n last = now\n if (current >= durationUs) {\n setTimeUs(durationUs - 1)\n setPlaying(false)\n return\n }\n setTimeUs(current)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current != null) cancelAnimationFrame(rafRef.current)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [playing, durationUs])\n\n const handleExport = async () => {\n const clip = clipRef.current\n if (clip == null) return\n const output = await run('正在编码自定义 Clip…', () => exportCustomClip(clip, durationUs))\n if (output != null) setResult(output)\n }\n\n return (\n <LabCard\n title=\"自定义 IClip\"\n description=\"纯程序化渲染的 Clip(帧计数器),不依赖任何媒体文件,证明接口可扩展。\"\n apis={['IClip.tick', 'IClip.clone', 'IClip.split', 'Combinator']}\n >\n <div className=\"overflow-hidden rounded-md border bg-stage-bg\">\n <canvas\n ref={canvasRef}\n width={640}\n height={360}\n className=\"block w-full\"\n style={{ maxHeight: 180 }}\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n if (!playing && timeUs >= durationUs - 1) setTimeUs(0)\n setPlaying((p) => !p)\n }}\n className=\"flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-[11px]\"\n >\n {playing ? <Square className=\"size-3\" /> : <Play className=\"size-3\" />}\n {playing ? '停止' : '预览'}\n </button>\n <span className=\"font-mono text-[10px] text-muted-foreground tabular-nums\">\n {formatTimecode(timeUs)} / {formatTimecode(durationUs)}\n </span>\n </div>\n\n <SliderRow\n label=\"时间\"\n value={timeUs}\n min={0}\n max={Math.max(1, durationUs - 1)}\n step={10_000}\n format={(v) => `${(v / 1e6).toFixed(2)}s`}\n onChange={(v) => {\n setPlaying(false)\n setTimeUs(v)\n }}\n />\n <SliderRow\n label=\"时长\"\n value={durationSec}\n min={1}\n max={20}\n step={0.5}\n format={(v) => `${v.toFixed(1)}s`}\n onChange={(v) => {\n setPlaying(false)\n setDurationSec(v)\n setTimeUs(0)\n }}\n />\n\n <button\n type=\"button\"\n disabled={busy != null}\n onClick={() => void handleExport()}\n className=\"rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground disabled:opacity-50\"\n >\n 编码为 MP4\n </button>\n\n {busy != null && <Busy label={busy} />}\n {result != null && <ResultPreview blob={result} filename=\"custom-clip.mp4\" />}\n </LabCard>\n )\n}\n","import { FlaskConical } from 'lucide-react'\r\nimport { ConcatenateLab } from './ConcatenateLab'\r\nimport { RemuxLab } from './RemuxLab'\r\nimport { ChromakeyLab } from './ChromakeyLab'\r\nimport { SubtitleLab } from './SubtitleLab'\r\nimport { TrackSplitLab } from './TrackSplitLab'\r\nimport { ThumbnailLab } from './ThumbnailLab'\r\nimport { RecorderLab } from './RecorderLab'\r\nimport { CustomClipLab } from './CustomClipLab'\r\n\r\n/**\r\n * 能力实验室:每个 WebAV 高级 API 一个可独立运行的卡片。\r\n *\r\n * 与主编辑器共享同一套能力探测结果,因此这里不再重复检测。\r\n */\r\nexport function LabPage() {\r\n return (\r\n <div className=\"mx-auto max-w-7xl space-y-4 p-4\">\r\n <header className=\"space-y-1\">\r\n <div className=\"flex items-center gap-2\">\r\n <FlaskConical className=\"size-4 text-primary\" />\r\n <h1 className=\"text-sm font-semibold\">能力实验室</h1>\r\n </div>\r\n <p className=\"text-xs text-muted-foreground\">\r\n 覆盖 av-cliper / av-canvas / av-recorder 的进阶 API。每个卡片可独立运行,失败时给出可读原因。\r\n </p>\r\n </header>\r\n\r\n <div className=\"grid gap-3 md:grid-cols-2 xl:grid-cols-3\">\r\n <ConcatenateLab />\r\n <RemuxLab />\r\n <ChromakeyLab />\r\n <SubtitleLab />\r\n <TrackSplitLab />\r\n <ThumbnailLab />\r\n <RecorderLab />\r\n <CustomClipLab />\r\n </div>\r\n\r\n <footer className=\"border-t pt-3 text-[10px] leading-relaxed text-muted-foreground\">\r\n 说明:所有输出均为 MP4(H.264 + AAC 48kHz 双声道)。WebAV 生成的是 fMP4 分片,\r\n 部分播放器需「修复时长」;浏览器本地处理,文件不会上传。\r\n </footer>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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"}
package/dist/lab.js CHANGED
@@ -1,9 +1,8 @@
1
- import { a as formatResolution, c as CardContent, d as CardTitle, f as Badge, l as CardDescription, n as formatDuration, p as cn, s as Card, u as CardHeader } from "./format-CV2aoZZC.js";
1
+ import { b as Badge, d as CardDescription, f as CardHeader, i as formatDuration, l as Card, p as CardTitle, s as formatResolution, t as notify, u as CardContent, x as cn, y as useWorkbench } from "./notify-CEwFQgjC.js";
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import { formatTimecode, parseSrt } from "@cut-workbench/core";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { AlertTriangle, Circle, FlaskConical, Loader2, Pause, Play, Split, Square } from "lucide-react";
6
- import { toast } from "sonner";
7
6
  import { AVRecorder, FrameCounterClip, burnSubtitles, concatMp4, createDemoCanvasStream, createMp4Clip, createSubtitlePreviewClip, exportCustomClip, extractKeyframes, fixRecordedDuration, formatRecorderState, isWebGL2Available, mixAudioIntoMp4, readMp4Info, renderChromakeyFrame, renderClipFrameToCanvas, renderSubtitleFrame, splitMp4Tracks } from "@cut-workbench/core/lab";
8
7
  // src/features/lab/components/LabCard.tsx
9
8
  /** Lab 卡片外壳:标题 + 用到的 API + (不可用时的原因) */
@@ -63,6 +62,7 @@ function useDownload() {
63
62
  }
64
63
  /** 带 busy 状态的异步执行器:统一错误 toast,禁止静默吞异常 */
65
64
  function useLabAction() {
65
+ const { bridge } = useWorkbench();
66
66
  const [busy, setBusy] = useState(null);
67
67
  return {
68
68
  busy,
@@ -73,12 +73,16 @@ function useLabAction() {
73
73
  } catch (err) {
74
74
  const message = err instanceof Error ? err.message : String(err);
75
75
  console.error(err);
76
- toast.error("操作失败", { description: message });
76
+ notify(bridge, {
77
+ level: "error",
78
+ title: "操作失败",
79
+ description: message
80
+ });
77
81
  return null;
78
82
  } finally {
79
83
  setBusy(null);
80
84
  }
81
- }, [])
85
+ }, [bridge])
82
86
  };
83
87
  }
84
88
  // src/features/lab/components/shared.tsx
@@ -344,6 +348,7 @@ function readStageBackground() {
344
348
  return value.length > 0 ? value : "#101018";
345
349
  }
346
350
  function ChromakeyLab() {
351
+ const { bridge } = useWorkbench();
347
352
  const [webgl2] = useState(isWebGL2Available);
348
353
  const [file, setFile] = useState(null);
349
354
  const [similarity, setSimilarity] = useState(.4);
@@ -392,7 +397,10 @@ function ChromakeyLab() {
392
397
  setFile(picked);
393
398
  setDurationUs(result.meta.duration);
394
399
  setTimeUs(Math.min(result.meta.duration / 2, 1e6));
395
- toast.success("已载入视频,拖动参数查看抠像效果");
400
+ notify(bridge, {
401
+ level: "success",
402
+ title: "已载入视频,拖动参数查看抠像效果"
403
+ });
396
404
  };
397
405
  useEffect(() => {
398
406
  if (clipRef.current == null || busy != null) return;
@@ -795,6 +803,7 @@ function ThumbnailLab() {
795
803
  * - 摄像头(getUserMedia,需要用户授权)。
796
804
  */
797
805
  function RecorderLab() {
806
+ const { bridge } = useWorkbench();
798
807
  const [mode, setMode] = useState("demo");
799
808
  const [recording, setRecording] = useState(false);
800
809
  const [paused, setPaused] = useState(false);
@@ -844,7 +853,11 @@ function RecorderLab() {
844
853
  return stream;
845
854
  } catch (err) {
846
855
  const message = err instanceof Error ? err.message : String(err);
847
- toast.error("无法访问摄像头", { description: message });
856
+ notify(bridge, {
857
+ level: "error",
858
+ title: "无法访问摄像头",
859
+ description: message
860
+ });
848
861
  return null;
849
862
  }
850
863
  };
@@ -885,7 +898,10 @@ function RecorderLab() {
885
898
  recorderRef.current = null;
886
899
  if (output != null) {
887
900
  setResult(output);
888
- toast.success(`录制完成 · ${(output.size / 1024 / 1024).toFixed(2)} MB`);
901
+ notify(bridge, {
902
+ level: "success",
903
+ title: `录制完成 · ${(output.size / 1024 / 1024).toFixed(2)} MB`
904
+ });
889
905
  }
890
906
  };
891
907
  return /* @__PURE__ */ jsxs(LabCard, {