mcpField
C
composition

composition

github.com/editframe创意与媒体19153 次安装2 周前更新
代码仓库

关于 composition

使用 Editframe Web 组件或 React 组件构建视频合成的 Skill,提供统一时间线模型来编排场景、媒体、时长、转场和渲染。

主要功能

  1. 01支持 Web 组件与 React 语法
  2. 02场景排序与时长模式
  3. 03视频、音频、图像和文本元素
  4. 04与时间线同步的动画
  5. 05通过浏览器或 API 导出视频

适用场景

  1. 01用视频片段编排场景
  2. 02添加旁白、音乐和音效
  3. 03制作定时文本与语音字幕
  4. 04将合成内容导出为视频

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/editframe/skills --skill composition

安装后在 Claude Code 中输入/composition确认已加载。

详细介绍

视频合成

使用 Editframe 元素构建视频场景。可以使用 Web 组件,例如 <ef-timegroup> 和 <ef-video>;也可以使用 React 组件,例如 <Timegroup> 和 <Video>。两种语法共用同一套合成模型和渲染流程。选择适合你项目的语法即可。

Web 组件属性使用 kebab-case,例如 file-id 和 api-host。React 属性使用 camelCase,例如 fileId 和 apiHost。有四个属性不遵循此规则:sourcein、sourceout、trimstart 和 trimend。这些属性在 HTML 属性和 React 属性中均使用相同的小写字符串。元素参考部分列出了每个属性的两种形式。

让 Editframe 控制时间和媒体

Editframe 拥有合成时钟和所有媒体源。使用 Editframe 元素和属性构建时间安排和媒体。使用 CSS 控制布局和外观。使用 JavaScript 在 Editframe 帧钩子中计算视觉效果。

需求Editframe 工具
场景、场景顺序和剪辑点使用 mode="sequence" 的 ef-timegroup
场景时长使用 contain 根据媒体确定时长;使用带 duration 的 fixed 设置编辑节拍;使用 fit 适配父级时长
场景内的开始时间子元素上的 offset
转场序列上的 overlap,以及 --ef-transition-duration 和 --ef-transition-out-start
视频片段和裁剪使用带有 sourcein、sourceout、trimstart 和 trimend 的 ef-video
旁白、音乐和音效提示使用带有 offset、volume 以及相同裁剪属性的 ef-audio
图像ef-image
错落显示的文本使用带有 split 和 stagger 的 ef-text
语音字幕使用包含按词计时的转录数据的 ef-captions
音频响应视觉效果使用带有 target 的 ef-waveform
随进度变化的样式--ef-progress 和 --ef-duration
每帧的计算状态addFrameTask、React onFrame 或 useTimingInfo()
视频输出从浏览器编辑器导出、使用 renderTimegroupToVideo(),或使用 ef-workbench 的 exportVideo() 方法

Editframe 还会控制合成中的所有 CSS 动画和 element.animate() 动画。它会暂停每个动画,并将其定位到对应时间元素的局部时间。该时间元素是动画元素本身,或离它最近的时间祖先。animation-delay 从该元素开始计时,而不是从页面加载时开始计时。请将每个动画放在其时钟所对应的时间组中。

不要使用通用浏览器工具重建这些系统:

  • 不要使用 <video> 或 <audio> 元素。Editframe 不会定位它们。导出时只会混合来自 ef-video 和 ef-audio 的声音。
  • 不要使用 setTimeout、setInterval、requestAnimationFrame 或墙上时钟来计时事件。这些计时器不会响应定位操作,也不会遵循渲染时钟。
  • 不要在组件渲染或帧任务运行时调用 Math.random()。导出时会在离屏克隆中定位每一帧,而且某些渲染会在时间线中途启动新文档。因此,这些值会在预览、渲染结果以及渲染的各个部分之间发生变化。请根据索引或提供的时间计算变化值。对于随机值,请在模块作用域中调用一次具有固定种子的生成器。不要使用 Date、performance.now() 或 crypto 生成可见内容,因为它们无法保证确定性。
  • 不要运行第二个动画时钟,例如会自行播放的动画库时间线。创建暂停状态的库动画,并通过 addFrameTask 将其定位到提供的时间:使用 { paused: true } 构建 GSAP 时间线,并以秒为单位调用 seek(ownCurrentTime);创建 autoplay: false 的 anime.js 动画,并以毫秒为单位调用 seek(ownCurrentTime * 1000)。在帧任务中使用 renderer.render() 渲染 Three.js 场景,创建 WebGLRenderer 时设置 preserveDrawingBuffer: true,并且不要调用 setAnimationLoop()。
  • 不要剪切、拼接或混音媒体文件;如果 Editframe 属性可以表达相应编辑,就使用该属性。
  • 不要手动安排字幕文本的时间。请向 ef-captions 提供按词计时的转录数据。

快速开始

<ef-timegroup mode="sequence" class="w-full h-full">
  <ef-timegroup mode="fixed" duration="2s" class="absolute w-full h-full">
    <ef-image src="logo.png" class="size-full object-contain"></ef-image>
    <ef-text class="absolute inset-x-0 bottom-12 text-center text-white text-3xl">My Brand</ef-text>
  </ef-timegroup>
  <ef-timegroup class="absolute w-full h-full">
    <ef-video src="main.mp4" class="size-full object-cover"></ef-video>
  </ef-timegroup>
</ef-timegroup>
import { Timegroup, Video, Image, Text, TimelineRoot } from "@editframe/react";

const MyVideo = ({ id }: { id: string }) => (
  <Timegroup id={id} mode="sequence" className="w-full h-full">
    <Timegroup mode="fixed" duration="2s" className="absolute w-full h-full">
      <Image src="logo.png" className="size-full object-contain" />
      <Text className="absolute inset-x-0 bottom-12 text-center text-white text-3xl">My Brand</Text>
    </Timegroup>
    <Timegroup className="absolute w-full h-full">
      <Video src="main.mp4" className="size-full object-cover" />
    </Timegroup>
  </Timegroup>
);

export const App = () => <TimelineRoot id="my-video" component={MyVideo} />;

此示例做出两个时长决策。Logo 卡片使用 fixed duration="2s"。Logo 没有自然的显示时长,因此此时长是编辑上的选择。

第二个场景默认采用 contain 模式。它唯一的子元素是 main.mp4,因此片段决定了场景时长。更长的音频同级元素会延长此场景。关于音乐铺底,请参阅“时间组与序列”下的 fit 示例。

每个进行渲染或导出的 React 合成都需要使用 TimelineRoot 包装器。原因请参阅下文的 React 部分。

此示例直接加载媒体,不使用 <ef-configuration>/<Configuration>。如需本地 JIT 转码、API 路由或签名,请添加此包装器。请参阅下文“高级 → 配置”。

时长单位

5s(秒)| 1000ms(毫秒)| 2m(分钟)

使用 npm create @editframe 开始新项目。详情请参阅 editframe-create 技能。

核心概念

时间模型

每个元素——ef-timegroup、ef-video、ef-text 等——都是一个时间元素。每个时间元素都包含两个以秒为单位的时间属性:

  • duration — 元素的时长。
  • ownCurrentTime — 元素本地播放头的位置。Editframe 根据最近的祖先元素的时间以及元素自身的 offset/窗口,推导出此值。

媒体元素还包含一个时间属性:currentSourceTime。这是经过 sourcein/修剪后,在底层源文件中的位置。

只有根元素的 currentTime 支持直接写入。使用 seek() 或 seekForRender() 设置它。Editframe 会从根元素推导出所有其他时间值。每个推导出的值都会沿树单向向下传递。

这些属性的 *Ms 变体(durationMs、ownCurrentTimeMs 等)仍然存在,以保持向后兼容。这些变体已弃用。请在新代码中使用以秒为单位的字段。

Editframe 支持四个修剪属性:

  • sourcein/sourceout — 选择源文件中的一个窗口。
  • trimstart/trimend — 从元素自身时长的开头或结尾剪去时间。

使用 offset 在 fixed、contain 或 fit 父元素中定位子元素。

时间组与序列

ef-timegroup 是唯一的容器元素。它的 mode 属性控制两件事:时间组自身的时长,以及每个子元素的时间窗口。下表列出了各个模式。

ModeDurationChildren
fixedduration 属性。每个子元素按自己的 offset 定位,并行播放。
sequence子元素时长之和,减去每对相邻子元素之间的 overlap。连续播放;仅显示当前活动的子元素(通过 hidden 切换,并设置 data-active 属性)。
contain(默认)所有子元素的 offset + duration 中最晚的时间点。并行播放,每个子元素按自己的 offset 定位。
fit继承最近的祖先时间组的时长(如果它是根元素,则回退到 contain)。并行播放,每个子元素按自己的 offset 定位。

使用 sequence 模式连续播放一系列场景。请参阅上面的快速入门示例。设置 overlap(CSS 时间值,例如 overlap="1s"),即可让下一个场景提前相应时长开始。这样,下一个场景就能与上一个场景的尾段同时播放,形成交叉淡化或转场窗口。

选择模式时,只需问一个问题:是媒体决定此元素的时长,还是由你决定?

  • 由媒体决定。 用于视频片段、旁白或其他时长取决于自身内容的元素。不要设置 duration。将 mode 留空,使其默认采用 contain,此时时间组的时长由其内容决定。这样,合成内容就变成了可复用的模板。你可以先构建时间线,再确定最终媒体的时长。之后替换为时长不同的片段,时间安排仍然有效,无需手动修改。
  • 由编辑决定。 用于标题卡片、固定时长的片尾或其他由你控制时长的节拍。这类内容没有可推导的自然时长。设置 fixed duration="..."。
  • 继承。 fit 模式始终跟随最近的祖先时间组的时长,而非自身内容的时长。当某个元素必须与一个时长尚未确定的相对元素完全等长时,使用 fit。最常见的情况是:在由场景组成的 sequence 下方放置背景音轨。
<ef-timegroup class="w-full h-full"> 
  <ef-timegroup mode="sequence" class="absolute w-full h-full">
    <ef-timegroup mode="fixed" duration="3s" class="absolute w-full h-full">
      <ef-video src="a.mp4" class="size-full object-cover"></ef-video>
    </ef-timegroup>
    <ef-timegroup class="absolute w-full h-full">
      <ef-video src="b.mp4" class="size-full object-cover"></ef-video>
    </ef-timegroup>
  </ef-timegroup>
  <ef-timegroup mode="fit">
    <ef-audio src="bed.mp3" volume="0.3"></ef-audio>
  </ef-timegroup>
</ef-timegroup>

背景音轨的播放时长与序列完全一致。今天,序列时长为 8 秒。明天更换片段后,序列时长变为 12 秒。无论哪种情况,都无需手动同步时长。

不要默认将每个场景都设为 fixed。这样会失去模板的主要优势。每次媒体源时长变化时,你都得手动重新调整时间,而无法让合成内容自动适配。

转场

利用 sequence 时间组的 overlap 构建转场。Editframe 没有单独的转场元素。在序列上设置 overlap 后,Editframe 会为每个发生重叠的子场景设置两个 CSS 自定义属性:

  • --ef-transition-duration — 重叠时长,以秒为单位。
  • --ef-transition-out-start — 场景应开始播放退出动画的时间点,以秒为单位。该值等于 duration - overlap。

根据这两个属性设置场景淡化、滑动或缩放动画的延迟和时长。Editframe 会将动画定位到场景的时钟,因此无论如何定位或渲染,退出动画都能保持正确。

<ef-timegroup mode="sequence" overlap="0.5s">
  <ef-timegroup mode="fixed" duration="3s" class="scene">
    <ef-video src="a.mp4" class="size-full object-cover"></ef-video>
  </ef-timegroup>
  <ef-timegroup mode="fixed" duration="3s" class="scene">
    <ef-video src="b.mp4" class="size-full object-cover"></ef-video>
  </ef-timegroup>
</ef-timegroup>

<style>
  .scene {
    animation: fade-out linear;
    animation-duration: var(--ef-transition-duration, 0s);
    animation-delay: var(--ef-transition-out-start, 9999s);
    animation-fill-mode: forwards;
  }
  @keyframes fade-out {
    to { opacity: 0; }
  }
</style>
时间变量

每次定位时,Editframe 都会在每个时间元素上发布两个 CSS 自定义属性:

  • --ef-duration — 元素自身的时长,采用 CSS 时间值(例如 5s)。
  • --ef-progress — 取值范围为 0 到 1 的数字,无单位。

使用这些属性来设置随元素时钟变化的样式。无需脚本。

ef-timegroup[mode="fixed"] .progress-bar {
  transform: scaleX(var(--ef-progress));
}

ef-text 片段(见下文)还会获得 --ef-index、--ef-word-index、--ef-stagger-offset 和 --ef-seed。ef-captions 的单词会获得 --ef-word-index、--ef-word-seed 和 --ef-word-start。

脚本编写

Editframe 提供两个钩子,让你运行与合成时钟同步的 JavaScript。请使用这些钩子,而不要使用 requestAnimationFrame。

切勿使用 requestAnimationFrame、setInterval 或墙上时钟计时器来驱动逐帧状态。 浏览器会在标签页或窗口转入后台时降低 requestAnimationFrame 回调的频率,甚至完全暂停回调。无头和离屏导出始终在这种后台状态下运行。每当预览标签页失去焦点时,普通编辑也经常处于这种状态。由 requestAnimationFrame 驱动的合成会悄无声息地停止动画,或与渲染时钟失去同步。

addFrameTask 不存在这些问题。定位/渲染管线会在每个渲染帧直接调用它。无论标签页是否可见,它都能保持正确。

  • timegroup.initializer = (timegroup) => { ... } — 这是一个仅限 JavaScript 的属性,不是 HTML 属性。它对每个实例只运行一次,对每个渲染克隆也只运行一次(见下文“渲染”)。请将其编写为同步函数。不要在其中等待任何操作。可用它来注册 addFrameTask 回调并设置状态。你可以在其中调用 resolveDuration() 或 addFrameTask。如果抛出错误,或运行时间超过 10ms,则会记录警告;运行时间超过 100ms 则会记录错误。
  • timegroup.addFrameTask((info) => { ... }) — 注册逐帧回调。Editframe 会在每次定位时调用此回调,并传入 { ownCurrentTime, currentTime, duration, percentComplete, element }(所有时间单位均为秒)。此方法会返回一个注销函数。
<ef-timegroup id="scene" mode="fixed" duration="5s">
  <ef-text id="counter" class="absolute inset-0 flex items-center justify-center text-6xl"></ef-text>
</ef-timegroup>
<script>
  const scene = document.getElementById("scene");
  scene.initializer = (timegroup) => {
    const counter = timegroup.querySelector("#counter");
    timegroup.addFrameTask(({ ownCurrentTime }) => {
      counter.textContent = Math.floor(ownCurrentTime * 10).toString();
    });
  };
</script>

在 React 中,将相同的函数作为 <Timegroup> 的 initializer/onFrame 属性传入。onFrame 是对单次 addFrameTask 调用的简写。

媒体元素

所有视频、音频和图像源都通过 ef-video、ef-audio 或 ef-image 加载。每个元素都是时序叶元素。Editframe 会在预览和导出时,将其定位到合成时间。导出时会混合 ef-video 和 ef-audio 元素的声音。

通过属性而非单独的媒体工具来编辑媒体:

  • 使用 sourcein 和 sourceout 选择源素材的部分内容。
  • 使用 trimstart 和 trimend 从元素自身的时长中裁剪时间。
  • 使用 offset 将片段或声音提示放置到场景中。
  • 使用 volume 设置音量,或使用 mute 将其静音。
  • 使用 ef-surface 再次显示视频解码后的帧。

完整属性列表请参阅下方的元素参考:裁剪、音量、变体阶梯、FFT 分析等。少数元素的行为值得进一步说明。

ef-text 会将其文本内容拆分为 ef-text-segment 子元素。设置 split="word"、split="char" 或 split="line",以选择拆分单位。设置 stagger(CSS 时间值)可延迟每个片段的起始时间。

每个 ef-text-segment 都在轻量 DOM 中渲染,不带影子根。它带有 --ef-index、--ef-word-index、--ef-stagger-offset 和 --ef-seed 自定义属性。

将入场动画附加到片段本身。每个片段在开始后都会保持可见。下一个交错片段开始时,该片段的 data-active 属性会结束。可使用此属性实现临时强调。如果下一个片段在动画结束前开始,[data-active] 上的入场动画就会提前停止。

@keyframes pop-in {
  from { opacity: 0; transform: scale(0.5); }
}
ef-text-segment {
  animation: pop-in 0.4s calc(var(--ef-stagger-offset)) both;
}

ef-captions 支持通过以下三种方式接收字幕数据:

  • captions-src — JSON 文件的 URL。
  • captions-script — 内嵌 <script type="application/json"> 元素的 ID。
  • captions.captionsData = {...} — JavaScript 属性,而非 HTML 属性。用于代码动态生成的文字稿。

JSON 数据采用 segments 或 word_segments 结构。每个单词或片段都包含 start 和 end,单位可以是秒或毫秒。Editframe 会根据数值大小判断单位。

通过 ef-captions-active-word 部件或子元素设置当前单词的样式。也可以使用 --ef-word-index/--ef-word-seed 实现逐词交错效果。

ef-waveform 可将 ef-audio 或 ef-video 的 target 可视化。选择 mode:bars、line、curve、bricks、pixel、wave、spikes 或 roundBars。

ef-surface 会将另一个元素的像素镜像到自身画布上。将 target 设置为画布或任意 HTMLElement。可用它在第二个样式不同的元素中重用视频解码后的帧,而无需重新解码。

渲染

浏览器导出。 调用 @editframe/elements 中的 renderTimegroupToVideo(timegroup, options),将实时布局的 ef-timegroup 编码为视频文件。此函数使用 WebCodecs,并驱动与播放相同的 seekForRender 流程。

关键选项:

  • width/height/fps
  • from/to — 导出范围,单位为秒。
  • videoCodec/audioCodec/videoBitrate/audioBitrate
  • target — mediabunny 输出目标。
  • signal — AbortSignal。
  • onProgress(({ frame, totalFrames, time, duration }) => ...)

直接调用会跳转并更改传入的 timegroup。仅当该合成不是正在进行实时交互预览的内容时,才直接调用它。

要导出他人正在主动查看或编辑的合成,请勿直接调用 createRenderClone。此函数是内部原语,并非公共 API。请改用以下两种方式之一:

  • 对于应用内的自定义导出操作,请调用 ef-workbench 的 exportVideo() 方法(参见 editor-gui 技能)。此方法会为你克隆、渲染并释放屏幕外副本。
  • 对于云端渲染(@editframe/api 中的 createRender),无需额外操作。此流程已通过 window.EF_RENDER 运行;它会自动安装并为你处理屏幕外克隆。

自定义渲染数据。 渲染宿主(例如 Playwright 脚本)可以在运行合成之前,将任意 JSON 注入 window.EF_RENDER_DATA。在浏览器中,使用 getRenderData<T>()(模块级函数)或 useRenderData<T>() React Hook 读取此数据。可用它为渲染设置参数,例如传入用户姓名,而无需对合成本身进行模板化。

运行时和云端渲染。 此技能仅涵盖浏览器内导出。有关通过已安装的 Editframe 运行时进行导出,请参见 editframe-api 技能中的 CLI 部分。有关服务器端渲染和转录任务,请参见 editframe-api 技能的其余部分。

React

每个需要渲染或导出的 React 合成都必须使用 TimelineRoot 包装器。TimelineRoot 会挂载你的合成,也会注册克隆工厂。exportVideo() 和 window.EF_RENDER(参见上文“渲染”)会使用此工厂创建屏幕外渲染克隆。借助此工厂,克隆是一个实时的第二个 React 挂载,Hook、initializer 和 onFrame 闭包都能正常工作。没有它,克隆只是一个缺少这些功能的失效 cloneNode 副本。

const MyTimeline = ({ id }: { id: string }) => (
  <Timegroup id={id} mode="sequence">
    <MyScenes />
  </Timegroup>
);

<TimelineRoot id="root" component={MyTimeline} />

component 属性必须是 React.ComponentType<{ id: string }>。它必须在根节点直接渲染 <Timegroup id={id}>,或通过嵌套的自定义组件进行渲染。每次挂载时(无论实时挂载还是克隆挂载),TimelineRoot 都会将 id 转发给 component。

Hook(均来自 @editframe/react):

  • useTimingInfo() — 将返回的 ref 附加到 Timegroup。组件会在每一帧使用 { ownCurrentTime, duration, percentComplete }(单位为秒)重新渲染。
  • useMediaInfo(ref) — 将 ref 传给 Video 或 Audio 元素。返回 { readyState, duration, currentTime, paused }。
  • usePanZoomTransform(ref) — 将 ref 传给 PanZoom 元素。返回 { transform: { x, y, scale }, reset(), fitToContent(padding?), screenToCanvas(x, y), canvasToScreen(x, y) }。
  • useRenderData<T>() — 参见上文“渲染”。
  • usePlayback(target) — 订阅播放、暂停、跳转和音量状态,用于构建自定义预览控件。editor-gui 技能中的现成 GUI 组件可替代此 Hook 的大多数用法。

高级

配置。 ef-configuration/<Configuration> 提供媒体路由和身份验证设置。当本地媒体可直接加载且不需要 API 路由或签名时,此项为可选。

在使用已安装 Editframe 运行时的项目中,媒体由运行时管理。在自定义设置中进行本地 JIT 转码时,请在媒体子元素连接之前,在其祖先配置上设置 api-host。使用提供本地媒体路由的浏览器源。有关这两种设置,请参阅 dev-server 技能。

对于经过身份验证的媒体,请为部署配置 api-host 和 signing-url。image-proxy 属性控制跨源图像代理。完整属性列表请参阅下方的元素参考。

media-engine 保留供将来使用。目前唯一的引擎是本地渲染。

React Three Fiber。 使用 @editframe/react/r3f 中的 <CompositionCanvas> 包裹 <Timegroup> 子元素。这会提供一个与合成时间同步的 R3F <Canvas>。在场景组件中使用 useCompositionTime() 读取时钟,该函数以秒为单位返回 { time, duration }。

<Timegroup mode="fixed" duration="14s">
  <CompositionCanvas shadows>
    <MyScene />
  </CompositionCanvas>
</Timegroup>

要在 Web Worker 中渲染 3D 并释放主线程,请使用 OffscreenCompositionCanvas。自行构造 Worker。在 worker 脚本中调用 @react-three/offscreen 自带的 render() 函数来驱动它。没有 renderOffscreen 导出项。

服务器端渲染。 @editframe/react/server 和 @editframe/elements/server 仅导出合成组件和类型。它们不包含自定义元素注册,也不包含 DOM、canvas 或 WebCodecs 代码。可在 Next.js 或 Remix 服务器代码中安全导入。

这些服务器入口点不包含 GUI 组件或钩子。请改为在客户端渲染这些内容,例如使用 dynamic(() => import("@editframe/react"), { ssr: false }) 或等效方式。

对于 Next.js,请使用 @editframe/nextjs-plugin 中的 withEditframe() 包裹 next.config.mjs,并添加其路由处理程序。有关设置和选项,请参阅 dev-server 技能。有关可运行示例,请参阅 editframe-create 技能的 nextjs 模板。

任一服务器入口点中的 getRenderInfo() 在调用时仍需要一个挂载了 ef-timegroup 的活动 document。仅在已渲染的页面内调用它,例如在 Playwright 脚本或云渲染主机中。不要在服务器上用它解析 HTML 字符串。

包入口点

导入环境包含内容
@editframe/elements浏览器所有自定义元素、canvas/WebCodecs 渲染。
@editframe/elements/server浏览器、Node、SSR仅类型,以及 getRenderInfo()(运行时仅限浏览器)。
@editframe/elements/gui浏览器编辑器 GUI 自定义元素(时间线、播放头、控制柄等)——请参阅 editor-gui 技能。
@editframe/elements/styles.css, /theme.css浏览器基础样式和主题样式。
@editframe/react浏览器React 合成和 GUI 组件、钩子。
@editframe/react/server浏览器、Node、SSR仅合成组件,不含钩子或 GUI。
@editframe/react/r3f浏览器CompositionCanvas、OffscreenCompositionCanvas、useCompositionTime。

样式

使用 CSS 或 Tailwind 为元素设置样式。样式控制布局和外观。Editframe 元素和属性控制时间。使用 absolute 定位子元素。使用 w-full h-full 或 size-full 设置其大小。

合成实践

选择一种支持预期顺序和后续编辑的结构。以下指导适用于 HTML 和 React 合成。

场景结构

当场景需要独立的本地时钟时,使用嵌套的时间组。根据上述时长模型选择 contain、fixed 或 fit。场景的 ownCurrentTime 从零开始,因此其动画不受之前场景影响。

sequence 会对所有相邻子项应用同一个 overlap 值。如果过渡需要不同的时长,请使用不同的组或显式偏移。当重叠时长小于相邻两个场景的时长时,总时长等于 sum(durations) − overlap × (n−1)。每个声明的场景时长都包含其过渡窗口。

sequence 会隐藏未激活的场景。只有在视觉设计需要时才添加场景级不透明度,例如交叉淡化。过渡变量描述的是重叠窗口,并不要求所有元素同时进入或退出。

将场景专属动画放在场景内部。当运动涉及多个场景或持久覆盖层时,使用父级帧任务。根据提供的时间推导状态,以确保直接跳转和反向跳转都能重现相同画面。增量式模拟需要确定性的重置和重播策略。

动画

先通过 Editframe 结构设置时序。为每个节拍设置独立的时间组。使用 offset 将元素放置在节拍内。使用 ef-text 的 split 和 stagger 显示文本。使用过渡变量退出场景。

然后选择每个视觉属性的变化方式:

  • 对于可用固定时间线描述的运动,使用关键帧。Editframe 会根据时间元素的时钟调整关键帧。
  • 对于程序化几何图形、计数器或其他计算状态,使用 addFrameTask 或 React onFrame。
  • 对于随元素进度变化的样式,使用 --ef-progress。

根据设计安排入场、停留和退出,而不是套用单一的显示模式。选择显示效果、过渡或注意力引导方式时,使用运动设计目录。

  • 对于重复元素,根据索引一次性计算固定延迟。
  • 对于环境循环,使用重复关键帧,并为每个项目设置独立相位。
  • 对于独立变换,使用嵌套包装器或显式变换组合。
  • 对于在序列重叠期间退出的元素,使用 --ef-transition-out-start 和 --ef-transition-duration。

根据动画前后的预期状态设置动画填充模式:

  • 使用 backwards,在延迟期间应用第一个关键帧。
  • 使用 forwards,在动画完成后保留最后一个关键帧。
  • 当两种状态都需要保留时,使用 both。

这些规则同样适用于文本片段。Editframe 不会自动添加填充模式。检查同一属性上是否存在重叠动画;保留的状态可能会覆盖其他动画。

CSS 变换动画默认会替换静态变换。单独的包装器可以保留布局变换(例如居中),同时让内部元素执行动画。

资源

使用预览环境和导出环境都能解析的资源 URL。使用已安装的 Editframe 运行时,通过浏览器编辑器或 Agent 运行时导入媒体。将每个新源文件添加到插件的 source.files 列表中。对于自定义设置中的本地媒体路由,本地路径会相对于配置的 root 解析。

当打包器必须包含资源时,使用资源导入或其他受支持的打包器引用。仅将文件放在 src/ 中不会内联文件。公共目录中的文件需要托管环境或单独的打包步骤。检查最终导出环境,确保没有无法解析的 URL。

如果后续更改需要可编辑的源资源,请保留它们。对于修剪和偏移等时间编辑,使用合成属性。如果所需效果有要求(例如响度标准化),请预处理媒体。避免反复对唯一的源文件进行破坏性编辑。

在 React 中,<Image> 会包装一个自定义元素。它不接受 alt 属性。请使用与 HTMLElement 兼容的元素引用,而不是 HTMLImageElement。

音频

当预览时间与导出时间需要保持一致时,请在合成中加入音频。

sequence 的直接子元素会成为另一个节拍。在外层时间组下,将连续的音乐背景与序列并列放置。对于时长未知的背景音乐,请使用 fit,如上所示。对于固定时长,请将背景音乐限制为该时长。若短音频源需要覆盖更长的视频,则必须明确指定重复策略。

将特定场景的音效提示放在该场景内。将 offset 设为相对于场景起始位置的偏移。使用 sourcein/sourceout 和 trimstart/trimend 选择源内容并进行时间裁剪。

volume 是线性增益值,而非分贝值。这些元素会将其限制在 0–1 的范围内。大于 1 的值不会放大源音频。需要放大时,请离线应用增益。请检查是否存在削波。

验证

开发期间请检查有代表性的帧,包括开场、文字密集的帧、转场边界和结尾。在等待 seekForRender() 完成后截取浏览器画面,可用于检查布局。导出的帧也能揭示捕获差异。

交付前请渲染预期输出。观看渲染后的视频,检查时序、连贯性、可读性和转场。聆听音效提示的位置、语音清晰度、音乐平衡和削波情况。仅仅成功编码并不能证明视觉或音频质量合格。

检查输出元数据中的时长、尺寸、帧率和音频流。请使用实际输出路径:

ffprobe output/demo.mp4
ffmpeg -i output/demo.mp4 -af volumedetect -f null -

音量统计有助于发现静音或削波风险,但不能替代聆听。如果无法渲染或检查,请说明限制以及仍需进行的检查。

发送反馈

如有问题或改进建议,请向 Editframe 发送反馈:

  • 如果 Editframe 或此技能导致问题,请找到变通方法并继续任务。然后报告问题和变通方法。
  • 如果你发现有助于其他任务(而不仅是当前任务)的改进,请发送建议。例如,建议改进说明、补充示例、优化默认设置或新增功能。

每个不同的问题或建议分别发送一份报告。如果报告发送失败,请继续任务。

curl -sS https://editframe.com/api/v1/feedback \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $EDITFRAME_API_KEY" \
  --data-binary @- <<'JSON'
{
  "category": "bug",
  "skill": "composition",
  "summary": "One line that names the problem or the suggestion",
  "details": "For a problem: steps, expected result, actual result, and errors. For a suggestion: the change and how it helps.",
  "workaround": "The workaround that you used. Omit this field for a suggestion."
}
JSON

将 category 设为 bug、documentation、feature_request、performance 或 other。API 密钥为可选项。不提供密钥的报告将以匿名方式提交。请勿在报告中填写机密信息、API 密钥或个人数据。所有字段、限制和响应请参阅 editframe-api 技能。

元素参考

ef-audio

<ef-audio> 在合成中播放音频。

属性

属性React 属性类型默认值描述
durationdurationstring—此元素保持活动状态的时长(CSS 时间,例如 "5s")。
fft-decayfftDecaynumber8在 getFrequencyData 中进行平均的衰减帧数(默认值为 8)。
fft-gainfftGainnumber3频率分析前应用的线性增益(默认值为 3.0)。
fft-sizefftSizenumber128getFrequencyData/getTimeDomainData 的 FFT 窗口大小(会舍入为 2 的幂,默认值为 128)。
file-idfileIdstring | null—云文件标识符——解析为 ${apiHost}/api/v1/files/${fileId},优先级高于 src。
interpolate-frequenciesinterpolateFrequenciesbooleanfalse通过在 FFT 频段之间插值,而非使用最近邻采样,平滑 getFrequencyData 频段数据。
looploopbooleanfalse循环属性——当此时间元素是 PlaybackController 下的自驱动根元素时,控制器会回到 0,而不是在 duration 处暂停。
mutemutedbooleanfalse静音此元素的音频。
offsetoffsetnumber—在并行(fixed/contain/fit)父级时间组中的起始偏移(秒)。
sourceinsourceinstring—源文件中的起始时间(CSS 时间,例如 "1.5s")。
sourceoutsourceoutstring—源文件中的结束时间(CSS 时间,例如 "1.5s")。
srcsrcstring—源音频文件的 URL。
trimendtrimendstring—从源文件末尾裁去的时长(CSS 时间)。
trimstarttrimstartstring—从源文件开头裁去的时长(CSS 时间)。
volumevolumenumber1音量(0.0 到 1.0)。

触发事件

  • child-duration-changed({ duration: number })——嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
ef-captions

<ef-captions> 显示同步字幕,并逐词高亮。

属性

属性React 属性类型默认值描述
captions-scriptcaptionsScriptstring""包含字幕数据的 <script type="application/json"> 元素的 ID,可替代 captionsSrc。
captions-srccaptionsSrcstring""包含 segments/word_segments 字幕数据的 JSON 文件的 URL 或路径。
durationdurationstring—此元素保持活动状态的时长(CSS 时间,例如 "5s")。
looploopbooleanfalse循环属性——当此时间元素是 PlaybackController 下的自驱动根元素时,控制器会回到 0,而不是在 duration 处暂停。
offsetoffsetnumber—在并行(fixed/contain/fit)父级时间组中的起始偏移(秒)。
targettargetstring""这些字幕所对应的 <ef-video>/<ef-audio> 元素的 ID。
word-stylewordStylestring""自由格式的样式钩子(无内置行为)——与 @editframe/elements 的 word-style 属性保持一致,确保以此为目标的使用方 CSS/选择器仍能正常工作(例如 ef-captions[word-style="highlight"])。

触发事件

  • child-duration-changed({ duration: number })——嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
ef-captions-active-word

<ef-captions-active-word> 包裹 <ef-captions> 当前正在读出的单词。

ef-captions-after-active-word

<ef-captions-after-active-word> 包裹 <ef-captions> 中活动单词之后的字幕文本。

ef-captions-before-active-word

<ef-captions-before-active-word> 包裹 <ef-captions> 中活动单词之前的字幕文本。

ef-captions-segment

<ef-captions-segment> 将一行字幕的前置、当前和后续单词元素分组。除非你在 <ef-captions> 内自行编写此元素,否则 <ef-captions> 会自动创建它。

ef-configuration

<ef-configuration> 是根配置元素。

属性

属性React 属性类型默认值描述
api-hostapiHoststring""用于文件/资源请求的 Editframe API 基础 URL。
image-proxyimageProxy"auto" \"none""auto"
media-enginemediaEnginestring"local"为未来的引擎切换预留;本地渲染始终使用默认编解码器集。
signing-urlsigningURLstring"/@ef-sign-url"用于请求已签名持有者令牌,以便通过跨源媒体访问进行身份验证的 URL(默认值为 "/@ef-sign-url",与 @editframe/elements 匹配)。

触发事件

  • playback-attached({ controller: PlaybackController })— 当 EFConfigurationElement.attachPlayback 创建新的 PlaybackController 时触发。这样一来,在附加操作发生前已解析其 PlaybackBridge 的 GUI 控件无需轮询即可获知该操作。
ef-image

<ef-image> 是静态图像时间叶节点。

属性

属性React 属性类型默认值描述
asset-idassetIdstring | null—已弃用的 file-id 别名。
durationdurationstring—此元素的活动时长(CSS 时间,例如 "5s")。
file-idfileIdstring | null—云端文件标识符 — 解析为 ${apiHost}/api/v1/files/${fileId},优先级高于 src。
looploopbooleanfalse循环属性 — 当此时间元素是 PlaybackController 下自行驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。
offsetoffsetnumber—在并行(fixed/contain/fit)父级时间组中的起始偏移(秒)。
srcsrcstring—源图像文件的 URL。

CSS 部件

  • canvas — 渲染画布。此画布是一个轻 DOM 子元素,而非影子 DOM 元素。

触发事件

  • child-duration-changed({ duration: number })— 当嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
  • contentchange({ reason: string })— 当图像源发生影响渲染的变化时触发。例如,src、file-id 或 asset-id 的新值都会触发此事件。
ef-motionblur

<ef-motionblur> 为其子元素应用运动模糊。

属性

属性React 属性类型默认值描述
amountamountnumber0以像素为单位的显式模糊量。
angleanglenumber0以度为单位的显式模糊方向。
fpsfpsnumber30用于将 shutterAngle 转换为快门时长的帧率。
sensitivitysensitivitynumber1计算模糊量之前应用于检测到的运动的乘数。
shutter-angleshutterAnglenumber180以度为单位的模拟相机快门角 — 自动采样时控制模糊强度(默认值为 180)。
thresholdthresholdnumber0.5应用任何模糊之前的最小运动幅度(像素/度)。
ef-pan-zoom

<ef-pan-zoom> 是一个平移和缩放视口。

属性

属性React 属性类型默认值说明
auto-fitautoFitbooleanfalse自动将内容适配到视口,而不是使用显式的 x/y/scale。
scalescalenumber1当前缩放比例。
xxnumber0当前水平平移偏移量,单位为像素。
yynumber0当前垂直平移偏移量,单位为像素。

CSS 部件

  • content — 经过变换的包装元素。它会对插槽中的内容应用平移和缩放。

触发事件

  • transform-changed({ x: number; y: number; scale: number; })— 每次平移或缩放时,都会携带 { x, y, scale } 触发。

方法

  • screenToCanvas(screenX: number, screenY: number): { x: number; y: number } — 将屏幕坐标(例如 event.clientX 和 event.clientY)转换为画布坐标。
  • canvasToScreen(canvasX: number, canvasY: number): { x: number; y: number } — 将画布坐标转换为屏幕坐标。
  • reset(): void — 将平移和缩放重置为恒等变换(x: 0, y: 0, scale: 1)。
  • fitToContent(padding?: number): void — 将内容居中并缩放,使其适配视口。padding 参数用于设置边距,取值为 0 到 1 之间的数字。
ef-surface

<ef-surface> 会将另一个元素的像素镜像到内部画布上。

属性

属性React 属性类型默认值说明
durationdurationstring—此元素处于活动状态的时长(CSS 时间,例如 "5s")。
looploopbooleanfalse循环属性 — 当此时间元素是 PlaybackController 下由自身驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。
offsetoffsetnumber—在并行(fixed/contain/fit)父级时间组中的起始偏移量(秒)。
targettargetstring""要镜像的元素 ID(位图源或任意 HTMLElement)。

CSS 部件

  • canvas — 用于渲染的画布。此画布是一个轻 DOM 子元素,而非影子 DOM 元素。

触发事件

  • child-duration-changed({ duration: number })— 嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
ef-text

<ef-text> 是带有行、单词或字符拆分效果的动画文本。

属性

属性React 属性类型默认值描述
blur-amountblur-amountnumber—设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 amount。
blur-angleblur-anglenumber—设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 angle。
blur-fpsblur-fpsnumber—设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 fps。
blur-sensitivityblur-sensitivitynumber—设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 sensitivity。
blur-shutter-angleblur-shutter-anglenumber—设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 shutter-angle。
blur-thresholdblur-thresholdnumber—设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 threshold。
durationdurationstring—此元素处于活动状态的时长(CSS 时间,例如 "5s")。
easingeasingstring"linear"应用于错开时间的 CSS 缓动关键字。
looploopbooleanfalse循环属性——当此时间元素是 PlaybackController 下由自身驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。
motion-blurmotionBlurbooleanfalse将每个片段包装在 <ef-motionblur> 元素中,以自动检测方向模糊。
offsetoffsetnumber—在并行(fixed/contain/fit)父级时间组中的开始偏移量(秒)。
splitsplit"line" \"word" \"char"
staggerstaggerstring""每个拆分片段开始动画之间的延迟(CSS 时间,例如 "100ms")。

触发事件

  • child-duration-changed({ duration: number })——当嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
ef-text-segment

<ef-text-segment> 是 EFTextElement 的一个错开动画单元。

ef-timegroup

<ef-timegroup> 是一个时间容器。

属性

属性React prop类型默认值说明
durationdurationstring—当 mode="fixed" 时的固定时长(CSS 时间值,例如 "5s");在其他模式下会被忽略。
fpsfpsnumber—渲染帧率。
looploopbooleanfalse循环属性——当此时间元素是在 PlaybackController 下自行驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。
modemode"fixed" \"sequence" \"contain" \
offsetoffsetnumber—在并行(fixed/contain/fit)父级 timegroup 中的开始偏移(秒)。
overlapoverlapnumber0sequence 模式下相邻项目之间的重叠时间(例如 "1s")。

触发事件

  • child-duration-changed ({ duration: number }) — 嵌套媒体元素的时长已确定或发生变化时,在最近的提供上下文的祖先元素上触发。
  • playback-attached ({ controller: PlaybackController }) — TODO:记录此事件。

方法

  • addFrameTask(callback: FrameTaskCallback): () => void — 注册逐帧回调函数。此方法会返回一个注销函数。
  • waitForContentReady(): Promise<void> — 等待此 timegroup 以及与当前呈现相关的时间子元素就绪。在 sequence 模式下,此方法只等待当前活动场景。曾经对每个空闲的同级元素都进行等待,导致 Showcase 导出无限期挂起,因为后续片段的 #load() 方法与共享注册表输入发生争用。
  • seekForRender(time: number, options?: SeekForRenderOptions): Promise<void> — 当 strictVideoPaint 为 true 时,此方法还会验证每个活动的 ef-video 后代是否都绘制了覆盖此次 seek 播放头位置的样本,而不只是确认画布上仍有较早的样本(hasPaintedFrame 是粘滞状态)。画布上的陈旧帧会导致剪辑点处编码出 N-1。方法会在抛出错误前短暂重试。此检查会额外消耗几个微任务周期,因此请将其用于最终渲染捕获,而非交互式预览。有关此方法所基于的确定性同步和内容就绪约定,请参阅基类 TemporalElement.seekForRender 方法。
    • options.strictVideoPaint(可选):boolean — 为 true 时,还会验证每个活动的 <ef-video> 后代是否为此帧绘制了样本。仅元数据状态已就绪还不够。完整检查说明请参阅 EFTimegroupElement.seekForRender。此选项在基类实现中无效,因为只有容器才能遍历其后代。
    • options.paintPhaseTiming(可选):boolean — 发出严格绘制阶段诊断;验证仍会独立启用。
    • options.preferProxy(可选):boolean — 为 true 时,如果代理层级中有轻量级拖动代理或低质量代理,则优先使用。请在时间线胶片条和合成缩略图中设置此选项。这样可避免繁重场景仅为绘制小型预览,就打开每个片段的播放层级(role=high)输入。最终导出或 EF_FRAMEGEN 请勿设置此选项,以便 sync 仍等待播放层级就绪。
    • options.signal(可选):AbortSignal — 使用此信号中止正在进行的准备或呈现步骤,例如 SampleChase 泵或迭代器的 next 调用。当更新的 beginFrame 调用替代了一个停滞的调用时,EF_FRAMEGEN 会使用此选项。
ef-video

<ef-video> 是视频时间叶节点。

属性

属性React prop类型默认值描述
durationdurationstring—此元素处于活动状态的时长(CSS 时间,例如 "5s")。
fft-decayfftDecaynumber8对 getFrequencyData 进行平均的衰减帧数(默认值为 8)。
fft-gainfftGainnumber3频率分析前应用的线性增益(默认值为 3.0)。
fft-sizefftSizenumber128getFrequencyData/getTimeDomainData 的 FFT 窗口大小(四舍五入为 2 的幂,默认值为 128)。
file-idfileIdstring | null—云文件标识符 — 解析为 ${apiHost}/api/v1/files/${fileId},优先级高于 src/variants。
interpolate-frequenciesinterpolateFrequenciesbooleanfalse通过在 FFT 频带间插值而非采用最近邻采样,平滑 getFrequencyData 频段。
looploopbooleanfalse循环属性 — 当此时间元素在 PlaybackController 下作为自动驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。
mutemutedbooleanfalse静音视频的音轨。
offsetoffsetnumber—在并行(fixed/contain/fit)父时间组中的起始偏移量(秒)。
play-roleplayRole"low" \"high""high"
sourceinsourceinstring—源文件中的起始时间(CSS 时间,例如 "1.5s")。
sourceoutsourceoutstring—源文件中的结束时间(CSS 时间,例如 "1.5s")。
srcsrcstring—源视频文件的 URL。
trimendtrimendstring—从源文件末尾裁去的时长(CSS 时间)。
trimstarttrimstartstring—从源文件开头裁去的时长(CSS 时间)。
variant-modevariantMode"single-high" \"scrub-upgrade" \"multi-bitrate" \
variantsvariantsstring""变体阶梯清单的 URL(包含多个分辨率/比特率),用于替代 src。
volumevolumenumber1音量(0.0 到 1.0)。

CSS 部件

  • canvas — 用于渲染帧的画布。此画布是轻量 DOM 子元素,而非 Shadow DOM 元素。

触发事件

  • child-duration-changed({ duration: number })— 嵌套媒体元素的时长已知或发生变化时,在最近的上下文提供祖先上触发。
  • variantchange({ role: "scrub" \| "low" \| "high"; url: string; window: number \| null })— 活动解码变体发生变化时触发,例如其角色、URL 或拖动预览窗口发生变化。
ef-waveform

<ef-waveform> 是一种实时音频响应型波形可视化元素,与 ef-audio 或 ef-video 目标绑定。

属性

属性React 属性类型默认值说明
bar-spacingbarSpacingnumber0.5基于条形的可视化模式中,条形间距的比例(0-1)。
colorcolorstring"currentColor""currentColor"(默认值)会解析为宿主计算后的 CSS color。
durationdurationstring—此元素的活动时长(CSS 时间,例如 "5s")。
line-widthlineWidthnumber4线条/曲线可视化模式中的描边宽度(px)。
looploopbooleanfalse循环属性——当此时间元素是 PlaybackController 下由自身驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。
modemode"line" \"bars" \"roundBars" \
offsetoffsetnumber—在并行(fixed/contain/fit)父级时间组中的起始偏移量(秒)。
targettargetstring""要进行可视化的 <ef-audio>/<ef-video> 元素的 ID。

CSS 部件

  • canvas — 渲染画布。此画布是轻量 DOM 子元素,而非影子 DOM 元素。

触发事件

  • child-duration-changed({ duration: number })— 当嵌套媒体元素的时长变为已知或发生变化时,在最近的提供上下文的祖先元素上触发。

评论

支持 Markdown