Repository navigation
feat(cli): add hyperframes init --timeline to start a project from an OTIO timeline - #7433
Open
scottdmilner wants to merge 1 commit into
Open
scottdmilner wants to merge 1 commit into
scottdmilner wants to merge 1 commit into
Conversation
Preview approval requiredThis pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit |
| const used = new Set<string>(RESERVED_IDS); | ||
| const ids = new Map<string, string>(); | ||
| for (const clip of clips) { | ||
| const base = clip.id.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "clip"; |
…an OTIO timeline `init --timeline timeline.json` builds a project from an edit made in another editor. It reads a flat `hyperframes-timeline/1` JSON file and creates one clip per cut: source trims, speed changes, audio tracks, dissolves, freeze frames and image sequences carry over, and media is copied into `assets/`. New flags are `--timeline`, `--media-root` and `--link-media`. A new `/import-timeline` skill ships a Python converter that writes the JSON from OpenTimelineIO and the formats its adapters read. The schema lives in `packages/core/schemas/timeline-import.json`; the emitter, dissolve planner and media-offset resolver are in `@hyperframes/core/timeline-import`. Tests: core and CLI unit tests, converter tests with hand-written fixtures, an ffmpeg-backed `init` fixture test, and a CI job for the Python converter. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
scottdmilner
force-pushed
the
feat/init-timeline
branch
from
November 9, 2026 19:00
1acab66 to
c1b0765
Compare
5 of 6 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
hyperframes init --timeline timeline.json, which builds a project from an edit made in another editor. It reads a flat JSON description of the edit (hyperframes-timeline/1) and creates one clip element per cut. Source trims, speed changes, audio tracks, dissolves, freeze frames and image sequences carry over, and the media is copied intoassets/.A new
/import-timelineskill ships a Python converter that produceshyperframes-timeline/1JSON from OpenTimelineIO (OTIO) and the other timeline formats OTIO can read.New flags:
--timeline,--media-root(where relative clip paths resolve) and--link-media(symlink instead of copy; copies on Windows and on disks that refuse links).--timelinecan't be combined with--video,--audio,--example,--tailwindor--resolution.Why
Editors hand off cuts as EDL, Final Cut XML, AAF or OTIO. Today there is no way to bring an existing edit into HyperFrames except rebuilding it by hand. Importing the cut list lets someone start from the edit they already made and add motion graphics, captions and overlays on top.
How
Design decisions
OTIO is the first supported timeline format. It is open source, supported by major video editing softwares (including Davinci Resolve, Adobe Premiere, and Avid Media Composer) and has adapters for the other major timeline formats, so supporting it gets several formats without writing a parser for each one. The converter is tested with native
.otio, EDL (otio-cmx3600-adapter), Final Cut Pro 7 XML (otio-fcp-adapter), Final Cut Pro X (otio-fcpx-xml-adapter) and GStreamer (otio-xges-adapter). AAF (otio-aaf-adapter) reads but is untested and carries no media, so it needs relinking first.A JSON schema plus a Python converter, rather than reading OTIO directly. OTIO only ships Python and C++ bindings. The TypeScript bindings are in progress but not complete, and the HyperFrames CLI is TypeScript. So the boundary is a JSON schema:
packages/core/schemas/timeline-import.json, mirrored todocs/schema/and served athttps://hyperframes.heygen.com/schema/timeline-import.json.skills/import-timeline/scripts/otio_flatten.py) uses the officialopentimelineiopackage for all parsing and time math, then walks the result and prints flat facts as schema-conforming JSON.init --timeline) reads only the JSON and never touches OTIO.This keeps OTIO's precision and its file-format support without reimplementing either. Because the schema is the contract, any other tool can write the file, and a future TypeScript OTIO binding could replace the Python script without touching
init.Implementation
Core (
packages/core/src/timelineImport/, exported as@hyperframes/core/timeline-import)schema.tsparses and validates the file, and enforces the rules JSON Schema can't state: unique clip ids and fps between 1 and 240.emit.tsgenerates the composition HTML.transitions.tsplans dissolves. Each one is clamped to the handles (footage beyond the trim points) that ffprobe finds. A shortened dissolve is reported, and with no handle it stays a hard cut.mediaOffset.tsmaps a clip's position on its source's own clock to seconds into the file. It tries the file's own start timecode, then the converter'smediaOriginhint, then the value as written, and uses the first that places the clip inside the file.CLI (
packages/cli/src/commands/)init.tswires up the flags. On failure it removes the directory it created.initTimeline.tsstages media and prints warnings: the first 20, then a count of the rest.initTimelineSequence.tsencodes image sequences to H.264 with ffmpeg. EXR is decoded to display light, and missing frames are held.initTimelineOrigin.tsreads each file's start timecode with ffprobe, including drop-frame timecode and WAVtime_reference.Skill (
skills/import-timeline/):SKILL.md,otio_flatten.py,otio_flatten_test.pyandscripts/tests/fixtures/.Behaviors worth knowing
30000/1001).--media-root.Not supported
These are imported as hard cuts or skipped, each with a warning: wipes and other transition types, audio transitions, effects, color corrections, markers, generated media, reverse playback, remote URLs and editor-specific
metadata.Catalog and docs
The skill is registered in
skills-manifest.json, and the published skill count goes from 9 to 10. The skill list is updated inREADME.md,AGENTS.md,CLAUDE.md,docs/guides/skills.mdxand both_sharedtemplate files. The count and catalog references are also updated inscripts/check-core-skills.mjs,scripts/sync-schemas.tsand the two skill-lifecycle files. New docs aredocs/guides/import-from-otio.mdxanddocs/reference/timeline-import.mdx, plus adocs/packages/cli.mdxentry.Test plan
Automated tests
Three small hand-written fixtures live in
skills/import-timeline/scripts/tests/fixtures/, about 18 KB in all, and none is taken from a real production.cut.otiohas a trim, a 2x speed change, a dissolve, a second lane with a 12-image sequence and an audio track.cut.edlis a two-cut EDL. Each has an expected-output file (cut.expected.json,cut-edl.expected.json) that the converter's output must match exactly. Media is generated with ffmpeg when the test runs, so no media is committed. Every other test builds its input in code or in a temp directory.packages/core/src/timelineImport/timelineImport.test.tspackages/core/src/timelineImport/transitions.test.tspackages/core/src/timelineImport/mediaOffset.test.tsmediaOriginhint and plain-seconds fallbacks, rounding uppackages/cli/src/commands/initTimeline.test.ts--link-media, warnings and their cap, duplicate audio, missing media, timecode offsetspackages/cli/src/commands/initTimelineOrigin.test.tstime_reference, including drop-framepackages/cli/src/commands/initTimelineSequence.test.tspackages/cli/src/commands/initTimelineSequence.encode.test.tspackages/cli/src/commands/init.timeline.test.tspackages/cli/src/commands/timelineImportSchema.test.tspackages/cli/src/commands/initTimelineFixtures.test.tsskills/import-timeline/scripts/otio_flatten_test.py.otio, EDL and the other adapters, output validated against the schema; the fixtures above flatten to their expected JSONThe Python tests run with
python3 -I otio_flatten_test.pyin a venv withopentimelineioand its adapters. A new CI job,Test: import-timeline converter, installs pinned versions (opentimelineio0.18.1 and the EDL, Final Cut Pro 7, Final Cut Pro X, GStreamer and ALE adapters, plusjsonschema) and runs them when a skill or the timeline schema changes. The job fails if any test is skipped, so a missing adapter can't pass silently, and the aggregateTestcheck requires it. The TypeScript fixture test runs in the existing CLI test shards.Real timelines
Checked against their reference renders:
Animal Logic ALab trailer (ASWF Digital Production Example Library): converted and imported with
init, then rendered and compared with the reference render. Cuts are exact, SSIM is 0.942 and PSNR 43.4 dB, and audio is aligned at 0 ms (6 dB hot, because the AAF lists the stems twice).AWS Picchu edit, 4096x2048 (ASWF Digital Production Example Library) (
.otiozbundle): per-clip audio offsets match the reference for 75 of 88 clips. The render was 1024x512 at draft quality. Known differences: compound clips are skipped (black near 36.5 s and 410 s), and the editor's fades and one transform are not applied. A mismatch near 100 s comes from the OTIO itself: the clip's in-point is 46 frames from where the reference shows it, and ours matches the OTIO's frame exactly.Clip placement check:
darbyjohnston/otio-oc-examples: all 12 files were flattened, and every clip's placement was compared with the OTIO library's own. All 12 place every clip identically.ASC_StEM2_239_4K_24_100nits_Rec709_Stereo_ProRes422HQSTEM2_4k_ctm_ACES_239StEM2_MultipleMediaRefs.mov, with the proxy selected; the import uses the selected one and ignores the clip colorsChimera_3840x2160_2997fps_10bit_422ElFuente_4096x2160_60fps_10bit_420ElFuente_4096x2160_60fps_10bit_420_remix1Meridian_UHD4k5994_HDR_P3PQSolLevante_Animatics_ver1_H264SolLevante_HDR10_r2020_ST2084_UHD_24fps_1000nit.movfilesollevante_lp_vdm_16b_p3d65_pq_20200218_3840x2160SPARKS_HDR10_4K_2020_PQ_1000nitsSPARKS_P3_PQ_4000nitEnd-to-end
initcheck: a small timeline of two ffmpeg test clips with a dissolve and a tone track went throughinit --timeline, thenhyperframes lint(0 errors, 0 warnings) andhyperframes check(passed). The dissolve's incoming clip starts early on its handle and the fade tween matches it.