@ chatoctopus/ 时间线
Adobe Premiere Pro、DaVinci Resolution和OpenTimelineIO的视频编辑时间表.
生成造型良好的FCPXML 1.8 (Final Cut Pro),FCP7 XML / xmeml v5 (Primiere, Resolution),和 OTIO (OpenTimelineIO) 有帧精确的理性时间数学——没有浮点漂移.
安装
npm install @chatoctopus/timeline
需要节点.js QQ 18. 一揽子措施仅限无害环境管理。 对于 buildTimelineFromFiles() 或者说 probeMediaReference(), (中文(简体) ), FFmpeg/FF探测器 必须在您的路径上安装 。 格式之间的转换不需要FFmpeg/FFprobe.
国 际
包裹船装有 timeline CLI专注于格式转换和验证:
npx @chatoctopus/timeline convert ./edit.fcpxml --to otio --out ./edit.otio
npx @chatoctopus/timeline validate ./edit.xml
npx @chatoctopus/timeline validate ./edit.otio --json
图标
| 命令 | 说明 |
|---|---|
convert <input> --to <fcpx|premiere|resolve|otio> [--out <path>] | 自动检测输入格式,转换为目标编辑格式,并写入文件(--out)或作取出. 进口和亏损出口警告会写到stderr. |
validate <input> [--json] | 验证时间段完整性和框架对齐性;在硬错误上输出非零 |
sanitize <input> [--out <path>] | 重写中性占位符的绝对媒体路径, 这样一个工程文件就可以安全地作为测试固定共享 。 逐一审查项目/剪接名称和标记文本. |
快速启动
从已存在的工程文件导入
自动检测FCPXML,xmeml,或OTIO格式.
import { importTimeline, exportTimeline } from "@chatoctopus/timeline"
import { readFileSync, writeFileSync } from "fs"
// Read a Final Cut Pro project
const fcpxml = readFileSync("project.fcpxml", "utf-8")
const { timeline, warnings } = importTimeline(fcpxml)
console.log(`Imported "${timeline.name}" with ${timeline.tracks.length} tracks`)
if (warnings.length > 0) console.warn("Warnings:", warnings)
// Convert to Premiere Pro format
writeFileSync("project.xml", exportTimeline(timeline, "premiere"))
格式转换
import { importTimeline, exportTimeline } from "@chatoctopus/timeline"
import { readFileSync, writeFileSync } from "fs"
// Premiere XML -> Final Cut Pro
const premiereXml = readFileSync("edit.xml", "utf-8")
const { timeline } = importTimeline(premiereXml)
writeFileSync("edit.fcpxml", exportTimeline(timeline, "fcpx"))
// Final Cut Pro -> DaVinci Resolve
const fcpxml = readFileSync("edit.fcpxml", "utf-8")
const { timeline: tl } = importTimeline(fcpxml)
writeFileSync("edit-resolve.xml", exportTimeline(tl, "resolve"))
// OTIO -> Final Cut Pro
const otio = readFileSync("project.otio", "utf-8")
const { timeline: tl2 } = importTimeline(otio)
writeFileSync("project.fcpxml", exportTimeline(tl2, "fcpx"))
// Any format -> OTIO
const anyFile = readFileSync("timeline.fcpxml", "utf-8")
const { timeline: tl3 } = importTimeline(anyFile)
writeFileSync("timeline.otio", exportTimeline(tl3, "otio"))
从媒体文件中建立时间表
最简单的路径:提供文件路径和可选修剪点. 元数据通过 FF Probe 自动提取到内置 ExternalReference 对象.
buildTimelineFromFiles() 严格验证修剪输入 : startAt 财务报告和审定财务报表 duration 必须是有限、非负数, 0 图像需要明确 duration,而混合帧率的剪接如果不能始终如一地得到代表,则可能被拒绝.
import { buildTimelineFromFiles, exportTimeline } from "@chatoctopus/timeline"
import { writeFileSync } from "fs"
const timeline = await buildTimelineFromFiles("Wedding Highlights", [
{ path: "/footage/ceremony.mp4", startAt: 30, duration: 10 },
{ path: "/footage/reception.mp4", duration: 15 },
{ path: "/footage/speeches.mp4", startAt: 120, duration: 20 },
{ path: "/slides/title-card.png", duration: 3 },
])
// Final Cut Pro
writeFileSync("wedding.fcpxml", exportTimeline(timeline, "fcpx"))
// Adobe Premiere Pro
writeFileSync("wedding.xml", exportTimeline(timeline, "premiere"))
// DaVinci Resolve
writeFileSync("wedding.xml", exportTimeline(timeline, "resolve"))
// OpenTimelineIO (universal interchange)
writeFileSync("wedding.otio", exportTimeline(timeline, "otio"))
手动构建时长
为了全面控制,建造OTIO-第一 Timeline 模式直接。 所有时间使用 Rational 数字({ num, den }保持帧对齐.
import {
exportTimeline,
rational,
ZERO,
FRAME_RATES,
} from "@chatoctopus/timeline"
import type { Timeline } from "@chatoctopus/timeline"
import { writeFileSync } from "fs"
const timeline: Timeline = {
name: "My Edit",
format: {
width: 1920,
height: 1080,
frameRate: FRAME_RATES["29.97"], // { num: 30000, den: 1001 }
audioRate: 48000,
colorSpace: "1-1-1 (Rec. 709)",
},
tracks: [
{
kind: "video",
name: "V1",
items: [
{
kind: "clip",
name: "interview",
mediaReference: {
type: "external",
name: "interview.mp4",
targetUrl: "file:///footage/interview.mp4",
mediaKind: "video",
availableRange: {
startTime: ZERO,
duration: rational(9000 * 1001, 30000), // 9000 frames at 29.97fps
},
},
sourceRange: {
startTime: rational(300 * 1001, 30000), // start from frame 300 in source
duration: rational(150 * 1001, 30000), // 150 frames = ~5 seconds
},
metadata: {
role: "dialogue",
},
},
],
},
],
}
writeFileSync("output.otio", exportTimeline(timeline, "otio"))
writeFileSync("output.fcpxml", exportTimeline(timeline, "fcpx"))
Timeline 是OTIO,FCPXML和xmeml工作流程的API.
API 参考
高级别职能
| 函数 | 说明 |
|---|---|
exportTimeline(timeline, editor, options?) | 导出a Timeline。 。 。 . editor 实值 "fcpx", (中文(简体) ), "premiere", (中文(简体) ), "resolve",或 "otio"。 。 。 . |
importTimeline(content) | 将 FCPXML、 xmeml 或 OTIO 分析为 Timeline。 。 。 . |
buildTimelineFromFiles(name, files) | 使用 FF 检测和构建线性文件 Timeline 与内置媒体引用. |
createTimeline(options) | 创建一个 Timeline 带有用于合成或程序编辑的默认格式值. |
格式 - 特定函数
| 函数 | 说明 |
|---|---|
writeFCPXML(timeline, options?) | 生成 FCPXML 1. 8 字符串 Timeline |
readFCPXML(xmlString) | 将 FCPXML 分析为 Timeline |
writeXMEML(timeline, options?) | 生成 xmeml v5 字符串 Timeline |
readXMEML(xmlString) | 将 x meml 分析为 Timeline |
writeOTIO(timeline) | 生成 OTIO JSON 从 Timeline |
readOTIO(jsonString) | 将OTIO JSON分析为 Timeline |
丢失的导出注释 :
exportTimeline(timeline, "fcpx", {
onWarning(message) {
console.warn(message)
},
})
使用 onWarning 如果您想要在切换、标记、元数据、缺失的引用或其他只使用核心字段时得到通知,则导出到 FCPXML 或 xmeml .
过渡是核心模型中的重叠项目. computeTimelineDuration() 说明这种重叠,而目前的FCPXML和xmeml则输出平整的向枪托切换并警告,因为这些适配器尚未释放出本土的过渡元素.
时间公用事业
所有时间使用 Rational (单位:千美元){ num: number, den: number })来避免浮点漂移.
| 函数 | 说明 |
|---|---|
rational(num, den) | 创建简化的合理数字 |
add(a, b) | 添加两个理性 |
subtract(a, b) | 减法( 缩略语为零) |
toSeconds(r) | 将理性转换为浮动秒 |
toFCPString(r) | 格式为 FCP 时间字符串("1001/24000s"(中文(简体) ) |
parseFCPString(s) | 解析 FCP 时间字符串回到理性 |
secondsToFrameAligned(secs, frameRate) | 转换秒, 折叠到最近的框架边界 |
toFrames(duration, frameDuration) | 转换合理到帧数 |
parseTimecode(tc, frameRate) | 解析 SMPTE 时码( S)"01:00:00;00"),并有下限支持 |
FRAME_RATES | 常见预设 : "23.976", (中文(简体) ), "24", (中文(简体) ), "25", (中文(简体) ), "29.97", (中文(简体) ), "30", (中文(简体) ), "50", (中文(简体) ), "59.94", (中文(简体) ), "60" |
验证
| 函数 | 说明 |
|---|---|
validateTimeline(timeline) | 返回数组 ValidationError (检查介质对比,源范围,帧对齐,维度) |
hasErrors(results) | true 如果有重大错误(不仅仅是警告) |
computeTimelineDuration(timeline) | 从所有音轨项目计算总持续时间 |
探测
| 函数 | 说明 |
|---|---|
probeMediaReference(filePath) | 在文件上运行FFprobe并返回一个人口稠密的 ExternalReference 用于视频、音频或图像媒体 |
类型
interface Timeline {
name: string
format: NLEFormat
tracks: Track[]
metadata?: Record<string, unknown>
markers?: Marker[]
globalStartTime?: Rational
}
interface NLEFormat {
width: number
height: number
frameRate: Rational // e.g. { num: 30000, den: 1001 } for 29.97fps
audioRate: number // e.g. 48000
audioChannels?: number
audioLayout?: string
colorSpace?: string
}
type TrackItem = Clip | Gap | Transition
interface Track {
kind: "video" | "audio"
name?: string
items: TrackItem[]
metadata?: Record<string, unknown>
markers?: Marker[]
enabled?: boolean
}
interface Clip {
kind: "clip"
name: string
mediaReference: MediaReference
sourceRange?: TimeRange
metadata?: Record<string, unknown>
markers?: Marker[]
enabled?: boolean
}
interface Gap {
kind: "gap"
sourceRange: TimeRange
metadata?: Record<string, unknown>
enabled?: boolean
}
interface Transition {
kind: "transition"
name?: string
transitionType?: string
inOffset: Rational
outOffset: Rational
metadata?: Record<string, unknown>
}
interface TimeRange {
startTime: Rational
duration: Rational
}
type MediaReference = ExternalReference | MissingReference
interface ExternalReference {
type: "external"
targetUrl: string
name?: string
mediaKind?: "video" | "audio" | "image" | "unknown"
availableRange?: TimeRange
metadata?: Record<string, unknown>
streamInfo?: StreamInfo
}
interface MissingReference {
type: "missing"
name?: string
metadata?: Record<string, unknown>
}
interface StreamInfo {
hasVideo?: boolean
hasAudio?: boolean
width?: number
height?: number
frameRate?: Rational
audioRate?: number
audioChannels?: number
colorSpace?: string
}
type NLEEditor = "fcpx" | "premiere" | "resolve" | "otio"
仍然将图像建模为 ExternalReference 对象为 mediaKind: "image" 在正常的视频轨道。 片段通过 sourceRange,以及 buildTimelineFromFiles() 将包含一个 availableRange 在规定明确期限时.
构建器输入 :
interface TimelineFileInput {
path: string
startAt?: number
duration?: number
track?: number
kind?: "video" | "audio"
}
interface CreateTimelineOptions {
name: string
format?: Partial<NLEFormat>
tracks?: Track[]
metadata?: Record<string, unknown>
markers?: Marker[]
globalStartTime?: Rational
}
支持的格式
| 格式 | 延长 | 编辑器/工具 | 读取 | 写入 |
|---|---|---|---|---|
| FCPXML 1 | .fcpxml | 最终剪接Pro | 对 | 对 |
| xmeml v5 数据 | .xml | Adobe Primiere Pro, DaVinci 决议 | 对 | 对 |
| 开放时间线IO | .otio | 18+、Hiero、rv和OTIO生态系统 | 对 | 对 |
FCPXML 导入封面 asset-clip- 风格的脊椎 -- DaVinci 解析输出和这个软件包产生的东西。 最后切口的集装箱要素<clip>, (中文(简体) ), <sync-clip>, (中文(简体) ), <mc-clip>, (中文(简体) ), <ref-clip>, (中文(简体) ), <audition>, (中文(简体) ), <title>尚未解析的,将用一个警告而省略.
核查
运行测试套件 :
npm test
从 NLEs 导出的真实工程文件存在 tests/fixtures/。所有文件都会自动被扫描 tests/fixtures.test.ts:导入,验证,导出到所有格式,并进行往返. 要扩大报道范围,请从编辑器中导出一个时间表,对媒体路径进行消毒,然后将文件放入--不需要测试代码.
Fixture测试证明包读取了编辑器所写的. 反之 -- -- 编辑者接受生成的文件 -- -- 在发布前仍需要在目标应用程序中进行人工导入检查.
进行覆盖测试 :
npm run test:coverage
类型检查而不排放 :
npm run lint
构建 :
npm run build
快速烟雾测试
node --input-type=module -e "
import { exportTimeline, rational, ZERO, FRAME_RATES } from './dist/index.js';
const timeline = {
name: 'Smoke Test',
format: {
width: 1920, height: 1080,
frameRate: FRAME_RATES['29.97'],
audioRate: 48000,
},
tracks: [{
kind: 'video',
items: [{
kind: 'clip',
name: 'clip',
mediaReference: {
type: 'external',
name: 'clip.mp4',
targetUrl: 'file:///tmp/clip.mp4',
mediaKind: 'video',
availableRange: {
startTime: ZERO,
duration: rational(300 * 1001, 30000),
},
},
sourceRange: {
startTime: ZERO,
duration: rational(150 * 1001, 30000),
},
}],
}],
};
const fcpxml = exportTimeline(timeline, 'fcpx');
const xmeml = exportTimeline(timeline, 'premiere');
const otio = exportTimeline(timeline, 'otio');
console.log('FCPXML:', fcpxml.includes('<fcpxml') ? 'OK' : 'FAIL');
console.log('xmeml:', xmeml.includes('<xmeml') ? 'OK' : 'FAIL');
console.log('OTIO:', otio.includes('Timeline.1') ? 'OK' : 'FAIL');
console.log('Done.');
"
建筑
src/
├── index.ts Public API: exportTimeline, importTimeline, createTimeline, buildTimelineFromFiles
├── types.ts OTIO-first core types
├── time.ts Rational arithmetic, frame alignment, SMPTE timecode parsing
├── probe.ts FFprobe -> ExternalReference probing
├── media-kind.ts Shared file-extension media kind inference
├── builders.ts Core-native timeline construction helpers
├── validate.ts Core validation and duration computation
├── fcpxml/
│ ├── writer.ts FCPXML 1.8 generation
│ └── reader.ts FCPXML parsing
├── xmeml/
│ ├── writer.ts xmeml v5 generation (Premiere / Resolve)
│ └── reader.ts xmeml parsing
└── otio/
├── writer.ts OpenTimelineIO JSON generation
└── reader.ts OpenTimelineIO JSON parsing
如何运作
合理时间数学 是图书馆的核心。 所有NLE软件在内部使用帧相通的时点——将持续时间表示为理性分数,如 1001/30000s (29.97fps的一个帧). 使用浮点秒会导致帧漂移和"不在编辑帧边界上""最终剪接Pro"出错.
每个剪辑持续时间和抵消时间都经过 secondsToFrameAligned() 与两者的行为相匹配 黄油剪接 (鲁比)和 切开器 (去)这个图书馆的取自.
三种交换格式 涵盖所有主要编辑和工具:
- FCPXML 1 最终剪接 Pro -- -- 无轨磁性时间线
<asset-clip>a 内的元素<spine> - xmeml v5 数据 以音轨为基础,并与链接
<clipitem>视频和音频元素 - 开放时间线IO (单位:千美元)
.otio) -- 由Academy软件基金会支持的行业标准JSON交换格式. OTIO充当了通用的中枢:任何能说OTIO的工具都可以从任何其他格式获得即时访问时间. 在这个包中,OTIO现在直接向核心模型映射,包括明显的缺口,过渡,标记,元数据,以及内置媒体参考.
捐款
最有用的贡献是: 从编辑器导出真实的时间线 - 说吧
特别是锻炼适配器的特性 还没有完全覆盖
(过渡、标记、多相机、下帧时间码、混合帧率、,
嵌入式/组合式剪辑,标题). 每个文件在 tests/fixtures/ 自动
导入、验证、导出到所有格式,并绕行,从而固定
不需要测试代码 .
分享前先放卫生. 真实输出嵌入了绝对介质路径( 您的 用户名、驱动器、客户端/项目名称)。 先擦擦他们:
npx @chatoctopus/timeline sanitize ./your-export.fcpxml --out ./your-export.sanitized.fcpxml
此重写每条媒体路径到中性占位符 。 项目名称, 夹片
名称和标记文本为 没有 被洗干净了 手边检查一下 然后打开一个
将文件添加到 tests/fixtures/,或打开 a
共享时间表文件 问题和
随附相接. 见 捐款额 以全向导.
承认
这个项目借鉴了来自 buttercut 财务报告和审定财务报表 cutlass我们感激地承认这些项目是上游的灵感.
商标
Final Cut Pro是苹果公司的一个商标. Adobe Premiere Pro是Adobe的商标. DaVinci Resolution是黑魔设计Pty有限公司的商标. 其他产品名称,标志,品牌均为各自所有者的财产.
许可证
麻省理工学院