@elah/ 核心
框架不可知视频时序引擎. 没有反应。 无出使相. 只是纯逻辑层——项目状态,回放,帧分辨率,以及媒体管理.
内部使用 @elah/react, (中文(简体) ), @elah/timeline,以及 @elah/editor,但可直接为自定义渲染管道或无头环境所消耗.
反应钩是 没有 在这个包裹中,他们生活在 @elah/react,将下面的存储包成组件使用.
安装
npm install @elah/core
软件包大小 : 大约1.2 MB作为npm tarball(3.0 MB已打包),其中大约一半是捆绑的字体,只有选择加入时才加载(参见 字体) (中文(简体) ). 代码为可树动( :sideEffects: false; 导入 @elah/core/engine 如果您不需要渲染器或解码器。 运行时间道克 : immer, (中文(简体) ), zustand 财务报告和审定财务报表 mediabunny (单位:千美元)^1.50.8)),除通过外,解码器和出口工人在首先解码或出口时懒散地装入 createDefaultDemuxerFactory,它静态导入。 真正的应用程序下载( 构建、 精密化、 gziped; 反应、 清晰反应和 媒体bunny 外部): 编辑器应用程序( )EditorProvider, (中文(简体) ), Preview, (中文(简体) ), Timeline 从 @elah/editor大约 112基B克兹,从0.6.0中的68个KiB(在效应、发电机、序列夹、转子口罩和音频增强之前是133个KiB)起起,它成为首次投入使用的块; prepareFeatures(project) 将工程的特性加载到前面). 单发动机( :TimelineEngine, (中文(简体) ), resolveTimeline)是大约45个基B克兹,发动机加 GpuRenderer 财务报告和审定财务报表 exportVideo 大约75克比克兹。 下方的AI-facing层花费了一个没有导入的应用程序. Bundle 战略.md 解释如何测量捆绑大小(node scripts/measure-bundle.mjs和尚存的启动.
稳定性(1.0): 根入和根入的导出 @elah/core/engine 跟着赛姆 三层人工智能 现场直播 在1.0,并跟随铸币局。 不从根导出; 从自己的条目导入 : @elah/core/spec (视频Spec,组件,系统, specVocabulary, (中文(简体) ), @elah/core/html (HTML/CSS场景转换), @elah/core/ops (业务词汇表和 describe* 帮助者)和 @elah/core/icons (卖家图标集). 每一个都标在下面。 一个项目作为文件版本1至5编写(见 保存并恢复; 6.6.x 无法打开使用更新型剪辑类型的文档.
里面的东西
| 模块 | 说明 |
|---|---|
TimelineEngine | 管理工程状态——轨道、剪辑、撤销/重写 |
PlaybackEngine | 帧精确回放时钟 |
resolveTimeline | 纯函数- 项目- 在特定框架的活场景 |
GpuRenderer | WebGL2 用于视频、图像、文本、形状和自由手层的渲染器 |
AudioPlaybackController | 播放时钟上的多轨音频混音器 |
tracksStore | Vanilla Zustand 商店镜像项目状态 — 用它绑定 useTracksStore 从 @elah/react |
playbackStore | Vanilla Zustand 商店镜像回放状态 — 用它绑定 usePlaybackStore 从 @elah/react |
selectionStore | 用于剪辑选择的香草Zustand商店——将其与 useSelectionStore 从 @elah/react |
transitionsStore | 香草Zustand 存储镜像过渡——用它绑住 useTransitionsStore 从 @elah/react |
textStylePresetsStore | 可重复使用的文本外观( 内置加用户预设) —— 用 useTextStylePresetsStore 从 @elah/react |
clipLoadStore | 预览无法绘制的剪辑( W)'loading' 编号 : 'error') (中文(简体) ). 永不保存或无法撤销的渲染器状态——用 useClipLoadStore 从 @elah/react |
mediaLibraryStore | 媒体资产登记(香草店)——与 useMediaLibrary 从 @elah/react |
importFiles 编号 : importUrl 编号 : importBlob | 将本地文件、 远程 URL 或 blobs 导入媒体库 |
beginImportUrl | 喜欢 importUrl 返回一个 status: 'pending' 资产,所以可以在媒体探测前放置剪辑 |
determineAssetHasAudio 编号 : probeHasAudio | 视频是否带有音频音轨 |
serializeProject 编号 : deserializeProject | 将引擎项目保存为 JSON 并装入返回( 外观) readProjectDocument(中文(简体) ) |
readProjectDocument 编号 : relinkProjectMedia | 验证存储的文档为 Project; 在媒体库重新点回剪辑 |
BUILT_IN_TEXT_TEMPLATES 编号 : applyTextTemplate | 14个文本带有分层出入口运动; applyTextTemplate(template, clip) 返回可撤销的 Partial<Clip> 补丁 |
sampleTextAnimation | 文字/形状出入境动画纯采样器(TextAnimation, (中文(简体) ), MotionSpec(中文(简体) ) |
createFrameSequence 编号 : FrameSequenceController | 有序的图像集(360°轨道,故事板),并有洗涤、回转和预装; frameSequenceToProject 使一个可编辑 |
snapshotMediaLibrary 编号 : hydrateMediaLibrary | 将媒体库变成可存储的条目, 并在页面装入后放回( 身份保存); 存储为您的 |
sourceBlobCache | 共享缓存, 该缓存可以在调试器和预览之间复制视频下载 |
PerfSummary | 一次一秒渲染成本汇总; 沉默, 除非 PERF 追踪通道已打开 |
exportVideo | 通过网络工作者导出时间表: MP4( 默认), WebM, GIF 或 PNG 序列 |
Clip.keyframes, (中文(简体) ), TEXT_ANIMATION_EASINGS, (中文(简体) ), SPRING_PRESETS, (中文(简体) ), MOTION_PRESETS | 运动层:每个变换通道上的键框,31个被命名的易行加立方位贝,步和弹簧相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相 动议(中文(简体) ) |
createGenerator, (中文(简体) ), GENERATOR_REGISTRY | 发电机剪辑:粒子、能量线、光泄漏、镜头照明、梯度背景、计数器、条形图、进度栏 |
EFFECT_REGISTRY, (中文(简体) ), EFFECT_BUNDLES | 23 GPU效应、捆绑和外观; adjustment 剪辑应用到下面的每件事 |
TRANSITION_REGISTRY | 九次过渡,按摘取顺序 |
createCompoundClip, (中文(简体) ), nestClips, (中文(简体) ), createSequenceClip | 复合剪辑(嵌入式项目为一剪辑)和序列剪辑(动画图像或编号帧) |
Clip.audio, (中文(简体) ), clipAudioGain, (中文(简体) ), enhancePlan | 静音、振动、淡出和增强;一个用于预览和导出的信封 |
svgToProject, (中文(简体) ), lottieToClip | 导入 SVG 和 Lottie 作为可编辑剪辑和密钥 |
validateScene, (中文(简体) ), sceneToClip (从) @elah/core/html(中文(简体) ) | 活着. HTML/CSS 场景子集:验证,然后将一个测量的场景转换为一个复合剪辑 |
compileVideoSpec, (中文(简体) ), buildVideo, (中文(简体) ), layProject, (中文(简体) ), specVocabulary (从) @elah/core/spec(中文(简体) ) | 活着. VideoSpec: 一个基于秒的 JSON 文档, 一个模型写, 编译到一个 Project |
applyOps, (中文(简体) ), sanitizeOps, (中文(简体) ), EDITOR_OP_NAMES, (中文(简体) ), describeProject (从) @elah/core/ops(中文(简体) ) | 活着. 57 键入编辑器对编辑工程的模型的操作 |
packageFontsBaseUrl | 包内12个捆绑字体的自有拷贝所住之处( opt- in) |
这些商店 香草 (单位:千美元)zustand/vanilla)——无反应,可从任何地方分出(store.getState(), (中文(简体) ), store.subscribe()) (中文(简体) ). 他们也是 模块级单数页:1 tracksStore,一个 playbackStore等,根据JS领域. @elah/editor是因为 <EditorProvider> 每个线 TimelineEngine编号 :PlaybackEngine 因此只允许 每页一个活动项目 今天得到支持的有: <EditorProvider> (或两台手动接线发动机)一并会让它们覆盖商店中彼此的状态. 一个页面上的多个独立编辑器需要单独的标签/框架/窗口,直到范围商店登陆.
快开始
import { TimelineEngine, PlaybackEngine, resolveTimeline } from '@elah/core'
const engine = new TimelineEngine({ fps: 30, stage: { width: 1920, height: 1080 } })
const playback = new PlaybackEngine({ fps: 30, getTotalFrames: () => engine.getTotalFrames() })
// Add a track, then a clip onto it. addClip takes a single typed
// options object (a discriminated union keyed on `type`) and returns the Clip.
const track = engine.addTrack('video')
engine.addClip({ trackId: track.id, type: 'video', src: 'video.mp4', startFrame: 0, durationFrames: 90 })
// Resolve the scene at frame 15 — pure (frame, project) → Scene.
const scene = resolveTimeline(15, engine.getProject())
使用没有浏览器的引擎
@elah/core/engine 是无浏览器子集:引擎,解析器, PlaybackEngine,则
商店、剪接厂、项目文件 以及纯粹的放置和时间表数学 它离开
退出 WebGL2 渲染器、 WebCodecs 解码器、媒体导入器和 MP4 导出器,即
使用一个模块 import.meta。使用来自 React 本地(Metro 和 Hermes 拒绝) import.meta, (中文(简体) ),
工人,或节点:
import { TimelineEngine, resolveTimeline } from '@elah/core/engine'
两个条目共享模块实例,所以 tracksStore 从任意一个对象都是一样的。 测试,
src/engine.no-browser.test.ts,如果浏览器所绑定的任何东西从中可以到达,则失败.
保存并恢复
serializeProject 编号 : deserializeProject 是简单的一对。 在他们下面,
readProjectDocument 是总读取器: 它返回 a Project 或掷出
ProjectDocumentError (单位:千美元)code: 'unreadable' | 'unsupported-version'从不半
项目。 无文件 version 印花读取为 第 1 版; 1 从更新的
建筑(PROJECT_VERSION,现在5)被拒绝. 项目印有 最低 版本可以
持有它用的东西: 2 adjustment 剪贴板; 3 个用于生成器、形状绘图和6个更新的
过渡; 4 为复合夹; 5 为序列夹. 一个没有使用它们的项目仍然
1版并开通于0.6.x.
import {
serializeProject, deserializeProject,
readProjectDocument, relinkProjectMedia, missingMediaSummary,
} from '@elah/core'
const json = serializeProject(engine)
deserializeProject(engine, json) // throws, leaving the engine untouched, if unreadable
// Or in two steps, so a refusal can be shown before the editor is cleared:
const project = readProjectDocument(JSON.parse(json))
engine.loadProject(project) // options: { transport?: 'rewind' | 'keep', history?: 'reset' | 'keep' }
// Media imported from disk is a blob: URL and does not survive a reload.
// After the library is rebuilt, repair the clips' assetIds and report what is gone:
const { project: repaired, relinked, missing } = relinkProjectMedia(engine.getProject(), assets)
engine.loadProject(repaired, { transport: 'keep', history: 'keep' })
const note = missingMediaSummary(missing) // e.g. "a.mp4, b.mp4 and 3 more"
引擎发射 project:loaded (单位:千美元)ProjectLoadedEvent每次载货后
新项目和要求 transport。 。 。 .
文本模板和动画
import { BUILT_IN_TEXT_TEMPLATES, findTextTemplate, applyTextTemplate } from '@elah/core'
const template = findTextTemplate(BUILT_IN_TEXT_TEMPLATES[0].id)!
engine.updateClip(clip.id, trackId, applyTextTemplate(template, clip)) // one undo entry
应用模板是干净的,不是累积的:模板不使用的字段是
清除. TextAnimation 保持单类型 in编号 :out enum并添加 inMotion 编号 :
outMotion 联合国 MotionSpec数字 : opacity, (中文(简体) ), offsetX, (中文(简体) ), offsetY, (中文(简体) ), scale, (中文(简体) ), rotation, (中文(简体) ),
ease, (中文(简体) ), opacityEase)),在出现时替换了enum.
模板携带的两样东西 只有数据数字 : stagger (将一行打入剪辑中)
和 tracking (假字母间距. applyTextTemplate 忽略
并且这些包里没有任何东西能运行它们
模板本身.
过渡
engine.addTransition({ fromClipId, toClipId, trackId, kind: 'slide', durationFrames: 12, direction: 'left' })
kind 是九项之一: 'fade' | 'slide' | 'wipe' | 'zoom' | 'spin' | 'whip' | 'glitchCut' | 'warpDissolve' | 'lumaWipe'
(单位:千美元)TRANSITION_REGISTRY 以标签和提示列出它们. slide 财务报告和审定财务报表 whip 带一个 direction
(单位:千美元)'left' | 'right' | 'up' | 'down',默认正确);其他人则忽略它。 解脱者保留两者
在现场的片断和列表中的过渡 scene.transitions 进展减缓
WebGL2 渲染器将配对用一个阴影来合成,所以两个片段都继续播放效果,在
预览和导出中。 仅使用的项目 fade, (中文(简体) ), wipe 财务报告和审定财务报表 slide 仍然在运行中出口工人
没有WebGL2(一个2D快照路径,只有左/右幻灯片);一个使用任何其他类型需要WebGL2的工程
没有它,它的出口会很快失败 除了前三张邮票之外,任何种类的项目都是
文档版本3.
运动:键框、弹簧、交错、路径
动画是剪辑上的数据. 键框为整数 相对于片段的开始,这样移动 一个剪辑会移动它的键; 修剪, 速度和拆分重映射它们.
engine.updateClip(title.id, trackId, {
keyframes: {
channels: {
x: {
source: { kind: 'keyframes' },
keys: [
{ id: 'k0', frame: 0, value: 0.2, easing: 'linear' },
{ id: 'k1', frame: 30, value: 0.8, easing: 'back-out' }, // the curve INTO this key
],
},
},
},
})
钥匙 easing 这是其中之一 TEXT_ANIMATION_EASINGS (正、正、正、正、正、正、正、正、正、正、正),
反,弹性和回弹, 每一个在/出/出,和 linear) (中文(简体) ). 钥匙可以带生
easingBezier, (中文(简体) ), steps (楼梯), hold,或封闭式 spring (单位:千美元)SPRING_PRESETS温柔地,
发作 发作 发作 发作 写密钥的纯助手( E)setKeysEasingAtFrame, (中文(简体) ),
moveKeysAtFrame, (中文(简体) ), repeatKeysPatch 重复和优雅, staggerOffsets 开始交错,
端口、中心、边缘或随机和横跨网格, scrambleText)和36项 MOTION_PRESETS 是公开的.
颜色和路径通道也可以键入,以及 setChannelExpression 从一个频道驱动一个频道
表达式 (wiggle, (中文(简体) ), loop, (中文(简体) ), sin,等等).
扭曲( S)skewX, (中文(简体) ), skewY)是一个可键入的信道。 3D 倾斜rotateX, (中文(简体) ), rotateY视频、图像、,
在文字、形状和自由手上拒绝。 剪辑也可以走路径
(单位:千美元) motion.progress (原始内容存档于2018-10-25). Channel. 单行 data-motion hTML 场景层读取的语法
由同一键构建.
新的剪辑类型、效果和发电机
import { createGenerator } from '@elah/core'
const confetti = engine.addClip({
trackId, type: 'generator', name: 'Confetti', startFrame: 0, durationFrames: 90,
generator: createGenerator('particles', { count: 80 }),
})
const grade = engine.addClip({ trackId: overlayTrackId, type: 'adjustment', name: 'Grade', startFrame: 0, durationFrames: 90 })
adjustment:没有自己的相片;其效果,遮盖和混合适用于其下的所有复合体.generator: 8个GENERATOR_REGISTRY) (中文(简体) ).compound:一个嵌入式项目作为一段播放(createCompoundClip· ;engine.nestClips编号 :unnestClip将一个选择变成一个并返回; 巢穴封顶于MAX_COMPOUND_DEPTH) (中文(简体) ).sequence: 动画相片或编号相框集,由相框索引选择,所以预览并导出显示相同的相框.
效果是23个GPU通行证(EFFECT_REGISTRY:颜色分级,加乌西模糊,花纹,谷物,RGB分出,
开花、发光、镜头和波浪扭曲、VHS、CRT、故障、像素、运动模糊等)
EFFECT_BUNDLES 如一击外观,加上口罩,跟踪垫和混合模式. 使用这些提升
文档版本(见 保存并恢复) (中文(简体) ). @elah/editor 播放、保存和导出
这些剪辑;它还没有编辑它们的UI.
模型的场景和视频Spec(活于1.0)
一个模型写出一个视频的两种方式,该视频作为普通,可编辑的片段登陆. 两者都是 现场直播 在1.0中,在他们自己的条目上,所以根保持小.
- HTML/CSS 场景 (单位:千美元)
import { validateScene, sceneToClip } from '@elah/core/html':模型在一个白列子集中写出一个HTML片段和一个样式表.validateScene({ html, css })返回指定所支持的替换符的违反.sceneToClip(measuredScene, { fps })转换浏览器..测量 场景(框,换行符) 和已解析的样式)收成一个由本地剪辑和密钥组成的复合剪辑. 衡量是东道方的工作; 这个包裹没有DOM测量器. - 视频Spec (单位:千美元)
from '@elah/core/spec': 一个基于秒的JSON文档.compileVideoSpec(spec)返回时{ project, issues }永不抛掷;buildVideo(authoredSpec)还可扩大组件和抛光;layProject(engine, project)将结果放入活的发动机上,作为倒转的一步;specVocabulary()使词汇块迅速.
工作、测试过的例子
docs/ai/ELAH_FOR_AI_AGENTS.md。 。 。 .
编辑会通过输入的操作( E)from '@elah/core/ops': sanitizeOps(raw, project) 修复模型的操作列表和 applyOps(engine, ops)
应用它作为倒数一步.
从 CDN 装入字体
文本片段命名了12个捆绑家庭(FONT_REGISTRY: Inter、蒙特塞拉特、波平斯、克罗特斯克航天公司、,
Playfair Display、DM Serif Display、Bebas Neue、Anton、Oswald、Archivo Black、Roboto Mono、Caveat; 拉丁语
只有子集,SIL OFL 1.1。 默认是从 /fonts/ 打开 主持人。 Woff2文件飞船
此套件下 fonts/<id>/,所以,无论复制文件夹到您的公众 fonts/ 目录,或
未捆绑的导入( CDN, 本地- ESM dev 服务器) , 将装载器指向包本身 :
import { exportVideo, packageFontsBaseUrl } from '@elah/core'
await exportVideo(project, { fonts: { baseUrl: packageFontsBaseUrl() } })
导出、音频分析和能力测试工作者从一个跨源模块URL开始,通过一个
blob 模块工人(jsDelivr) /+esm 包含构建; 捆绑的主机为普通
new Worker(new URL(...)) 路径。 使用 packageFontsBaseUrl() 只用于未捆绑的导入: 捆绑器重写
import.meta.url。 。 。 .
框架序列
import { createFrameSequence, frameSequenceToProject } from '@elah/core'
const seq = createFrameSequence({ frames: urls.map((src) => ({ src })), fps: 12, loop: 'wrap' })
engine.loadProject(frameSequenceToProject(seq, { holdFrames: 6 })) // edit/export as image clips
FrameSequenceController 从时钟或拖动驱动序列;
createFramePreloader 温度比指数高; pickFrameSource 选择
合适的候选人.
剪贴和跟踪字段
| 外地 | 适用于 | 含义 |
|---|---|---|
Clip.speed | 视频 | 回放乘数,被夹到0.25-4(engine.setClipSpeed; 时间长度随之而来 |
Clip.crop | 视频,图像 | 规范化源窗口 { x, y, width, height }; 见 normalizeCrop, (中文(简体) ), FULL_CROP |
Clip.cornerRadius | 视频,图像 | 圆角口罩,0-0.5分短边 |
Track.protected | 任意 | 用户无法删除音轨 |
Track.pinned | 任意 | 'bottom' 将车道保持在每道没有固定的轨道下方; addTrack 很荣幸 |
允许任何数量的视频音轨;它们按照音轨顺序复合,最上行道位于上方.
将媒体库保存在页面上
import {
mediaLibraryStore, snapshotMediaLibrary, hydrateMediaLibrary, refreshMissingThumbnails,
} from '@elah/core'
// Save: plain entries (assets still being probed are skipped). Where they go is up to you.
const entries = snapshotMediaLibrary(mediaLibraryStore.getState())
// Restore: ids are kept, so restored clips resolve their `assetId` directly.
const { hydrated, needsThumbnail } = hydrateMediaLibrary(entries, {
referencedSrcs: new Set(/* every media clip's src in the project being opened */),
})
refreshMissingThumbnails(needsThumbnail) // cosmetic, fire-and-forget
这只包含资产元数据和缩略图 。 从磁盘导入的文件是 blob: 網址
随会话而死亡;其字节没有被存储,以及 relinkProjectMedia 报告
剪辑为 missing。 。 。 .
诊断
import { PerfSummary } from '@elah/core'
// In the browser console: __trace.on('PERF') turns the once-a-second summary on.
PerfSummary 计算整个数据勾选(阶段时间、预算超支),而不是
只是GPU上传。 它没有分配,而 PERF 频道关闭了.
剪接厂
返回完全正常状态的独立建设者 Clip 对象( 四舍五入的框架、 默认音量/ opacity, 生成 id) 没有引擎—— 可用于输入的无头管 resolveTimeline 直接说 当你有了引擎,更喜欢 engine.addClip(options) 相反,它构建了剪辑并记录了一个倒数的条目.
import {
createVideoClip,
createAudioClip,
createTextClip,
createImageClip,
createShapeClip,
createFreehandClip,
} from '@elah/core'
const clip = createVideoClip({ trackId: 'v1', src: 'video.mp4', startFrame: 0, durationFrames: 90 })
const rect = createShapeClip({
trackId: 'el1',
startFrame: 0,
durationFrames: 90,
shape: { shapeKind: 'rect', shapeFill: '#22d3ee' }, // 'rect' | 'circle' | 'triangle'
})
导出
import { exportVideo } from '@elah/core'
const blob = await exportVideo(engine.getProject(), {
videoBitrate: 8_000_000,
outputHeight: 1080, // short edge — optional, defaults to the stage's own
onProgress: ({ frame, totalFrames }) => console.log(frame, '/', totalFrames),
})
outputHeight 这是舞台的 简称 边 (1080 =1920×1080或1080×1920;另一条
边沿宽度比,并按解码器的要求四舍五入到平数.
container 选择输出 : 'mp4' (违约), 'webm' (编) transparent: true 对于一个α通道,只有在
浏览器自己的编码和解码检查通过, 'gif' (单位:千美元)gifFps,默认为 15,没有音频)或 'png-sequence' (a ZIP,或你自己的)
frameSink) (中文(简体) ). videoCodec 实值 'avc' | 'hevc' | 'av1' | 'vp9' | 'vp8' 其中浏览器可以编码它,以及
range: 'inOut' 只导出已标记的入出范围。 导出需要 WebCodes 和 OffscreenCanvas:在实际操作中为Chrome或Edge.