First CRAN release: a pure-R OpenTimelineIO document model, with
constructors for the OTIO object model, functional builders, the OTIO edit
algorithms, schema upgrade/downgrade, and canonical .otio / OTIOD bundle
IO through jsonlite. The optional RcppOTIO package (cornball-ai drat
repository) validates output against the OpenTimelineIO C++ library.
Additional_repositories (cornball-ai drat) so the suggested RcppOTIO
oracle is installable.%||% definition in favour of base R's (new floor:
R >= 4.4.0).target_schema_versions is validated (named, unique, whole numbers) like RcppOTIO.enabled becoming NULL.target_schema_versions on
to_json_string/to_json_file; TRUE/FALSE registration semantics; fill
Fit emits a base Item. Validated against RcppOTIO.schema_name, schema_version, is_unknown_schema, type_version_map,
register_upgrade_function, register_downgrade_function. Completes phases 1-6.overwrite and fill added; all 10 edit algorithms validated against RcppOTIO,
including replicating OTIO 0.18.1's remove_child-by-pointer behaviour.slice, insert, remove ported from the OTIO source and validated against
RcppOTIO.slip, ripple, slide, trim, roll ported from the OTIO source and
validated against RcppOTIO (confirmed via treesitR to delegate 1:1 to upstream).trimmed_range_in_parent errors on exact boundary contact with the parent
source range (no overlap), matching opentime. Validated against RcppOTIO.flatten_stack pads a shorter top track so longer tracks below show through;
filters disabled tracks. track_trimmed_to_range mirrors the OTIO source. Found
by reading OpenTimelineIO C++ source directly.track_trimmed_to_range keeps/drops/errors transitions correctly; flatten_stack
is gap-filling and preserves transitions. Validated against RcppOTIO.end_frame correct for non-divisible durations; frame_for_time errors out of
range; negative image numbers extrapolate (no error); signed zero-padding.range_in_parent/visible_range handle Transitions; available_range/
trimmed_range work for tracks; track_trimmed_to_range is rate-faithful;
flatten_stack honours enabled; visible/overlapping corrected. Validated
against RcppOTIO.end_frame (correct for frame_step>1 and no available_range), out-of-range
errors for target_url_for_image_number/presentation_time_for_image_number,
no zero-padding when frame_zero_padding == 0, and frame_for_time (the
ImageSequenceReference method). Validated against RcppOTIO.range_in_parent, trimmed_range, trimmed_range_in_parent, visible_range,
item available_range, video_tracks/audio_tracks, global_start_time,
is_equivalent_to, visible, overlapping, track_trimmed_to_range, and
flatten_stack -- positions from gaps + child order, validated against RcppOTIO.range_from_start_end_time/extended_by keep the start rate; clamped uses
opentime operator- rate; end_time_inclusive returns the start for spans <= 1
frame; almost_equal rescales to the second arg; to_timecode/from_timecode
are faithful for non-integer and drop-frame rates; from_time_string keeps the
fractional value. Validated against RcppOTIO (value AND rate).MediaReference, GeneratorReference,
ImageSequenceReference with its computed frame/url methods), Marker,
Transition (composable), and TimeEffect/FreezeFrame -- JSON-shape and
behavior matched to RcppOTIO.almost_equal, end_time_exclusive/inclusive,
range_from_start_end_time, contains, intersects, overlaps, extended_by,
clamped, to_timecode/from_timecode, to_time_string/from_time_string,
and TimeTransform..parent pointers, never serialized), the foundation for full RcppOTIO parity
(see PLAN.md / OTIO_PARITY.md). Mutating tree ops mirror RcppOTIO: append_child,
insert_child, set_child, set_children, remove_child, clear_children
(attaching an already-parented child errors); plus parent, children,
has_child, has_clips, is_parent_of, index_of_child, find_clips,
clone (deep, parent-aware), color, kind<-, media_reference<-,
media-reference keys, and SerializableCollection.add_child/add_track/add_effect) remain as
value-semantics sugar over the mutating core, so existing callers (licuadora)
are unaffected. Serialized OTIO JSON is unchanged (still RcppOTIO-equivalent).Effect() and LinearTimeWarp() constructors (mirroring RcppOTIO's JSON shapes
and defaults), plus functional add_effect() and the accessors effects(),
effect_name()/<-, time_scalar()/<-, and enabled()/<- (a disabled
clip is muted; a disabled effect is bypassed). Effects round-trip through JSON
and are accepted by the real OTIO library via RcppOTIO. This is the surface the
blendR bridge uses to carry speed (LinearTimeWarp) and Blender compositing.Rcpp, no LinkingTo, no SystemRequirements,
no libopentimelineio. Imports: jsonlite only; RcppOTIO moves to Suggests.
(Reason: RcppOTIO's OTIO C++ is hard to get on CRAN; rotio becomes the
CRAN-clean layer the cornyverse can depend on.)Timeline(), Track(), Stack(),
Clip(), Gap(), ExternalReference(), MissingReference(),
RationalTime(), TimeRange() — names and JSON shapes match RcppOTIO so output
is interchangeable.add_child() and add_track() return a
new object rather than mutating in place. Accessors metadata()/metadata<-,
name()/name<-, kind(), children(), tracks(), source_range(),
media_reference(), target_url(), plus time helpers value(), rate(),
start_time(), duration(), to_seconds()/from_seconds(),
to_frames()/from_frames(), rescaled_to().to_json_string() / to_json_file() emit canonical OTIO JSON
via jsonlite; from_json_string() / from_json_file() parse it back.
Verified byte-equivalent against RcppOTIO (and thus real libopentimelineio).read_otiod() / write_otiod() for content.otio + media/
directory bundles.validate_with_rotio() round-trips emitted JSON through the real OTIO
library when RcppOTIO is installed; unverified otherwise.nle_timeline verb layer (clip_*/track_*/ripple_delete/shift_after),
the driver registry, the timeline.md Markdown carrier, and the old otio_*
Rcpp object/ time wrappers.clip_effect_add(), clip_effects(), clip_effect_params(),
clip_effect_remove() — attach, list, read, and remove arbitrary OTIO
Effects on a clip. An effect is an effect_name plus a metadata dictionary,
modelled directly on OTIO's Effect schema; parameters are stored as
individual OTIO metadata entries. A driver maps effect_name + params to its
engine (e.g. Kdenlive/MLT filters). Transform, crop, colour, etc. are generic
effects via this API rather than fixed fields.clone()
copies every clip/gap/effect/metadata), mutates the target clip, and returns
the clone, leaving the input untouched. Effects added this way survive
subsequent structural edits — the scalar-table rebuild clones non-time
effects forward by clip id — and survive JSON round-trips.clip_transform/clip_crop/clip_set stubs;
compositing is now generic OTIO effects via clip_effect_add(). clip_speed
(an OTIO LinearTimeWarp) is unchanged and lists alongside generic effects in
clip_effects().clip_speed() is implemented, modelled
on OTIO's own effect schema. Speed is recorded as a LinearTimeWarp
(time_scalar = speed, >1 faster) on the clip. Per OTIO's model the warp is an
annotation, so it does not change the clip's timeline footprint — the
source range still defines what is shown and a player/driver applies the rate.
clip_add() accepts a speed, and timeline$clips gains a speed column.
Speed survives serialization and structural edits.LinearTimeWarp (the speed effect). The effect set OTIO codes
is Effect -> TimeEffect -> LinearTimeWarp -> FreezeFrame; OTIO has no
spatial-transform or crop schema, so those will be generic Effect objects.clip_transform, clip_crop, clip_set — these become a generic OTIO
Effect API (clip_effect_add/clip_effects), modelled on OTIO rather than
Blender-shaped fixed fields. That needs a clone-based mutation path (the
scalar-table rebuild can't carry arbitrary per-clip effects), which lands in
its own PR. They error with a pointer for now.Structural edit verbs that round out the surface (no new C++; all expressible in the gap model):
track_delete() - remove a track and its clips.track_move() - reorder a track in the compositing stack.ripple_delete() - delete a clip and close the gap (vs clip_delete, which
leaves one).clip_slip() - shift a clip's source in/out without moving its timeline
position.clip_duplicate() - copy a clip to a new position (defaults to right after
the original).sequence vocabulary to OTIO's timeline throughout. OTIO's
top-level object is a Timeline, so the wrapper now speaks the format's own
language. Exported renames: new_sequence -> new_timeline, nle_sequence
(class) -> nle_timeline, is_sequence -> is_timeline, seq_fps ->
timeline_fps, seq_duration_frames -> timeline_duration_frames,
sequence_summary -> timeline_summary, read_sequence/write_sequence ->
read_timeline/write_timeline, sequence_to_json/sequence_from_json ->
timeline_to_json/timeline_from_json, validate_sequence ->
validate_timeline, extract_sequence_state_md/replace_sequence_state_md
-> *_timeline_state_md, dump_sequence/apply_sequence ->
dump_timeline/apply_timeline.timeline.md with a <!-- timeline:state otio --> state block; inst/schema/SEQUENCE_SCHEMA.md -> TIMELINE_SCHEMA.md.new_sequence()
returns an nle_sequence wrapping a live OTIO Timeline; seq$clips and
seq$tracks materialise data.frame views from C++ on read, and edits go
through the verbs only.track_add, clip_add,
clip_delete, clip_move, clip_trim, clip_split, shift_after. Each
reads the current state, applies the edit, and rebuilds the Timeline.Gaps computed from tl_in. Overlapping clips on one track are now
an error (use separate tracks). fps/canvas/sample_rate live in the Timeline
metadata and survive serialization.sequence.md now carries raw OTIO JSON in a <!-- sequence:state otio -->
block (was cornball.sequence.v1). read_sequence/write_sequence and
sequence_to_json/sequence_from_json go through OTIO's serializer. Old
cornball.sequence.v1 files no longer load.otio_external_reference(), otio_target_url(),
otio_set_target_url(), otio_set_name(), otio_set_kind().tools/otio_codegen.R now emits trivial constructors and
scalar setters (in addition to getters), each validated against the headers
via treesitR. The gap-model rebuild/materialize is bespoke and hand-written.R/json.R and R/coords.R removed (OTIO owns serialization; coordinate
transforms return with effects). inst/schema/TIMELINE_SCHEMA.md rewritten
to describe the carrier and point at the OTIO docs.clip_speed, clip_transform, clip_crop, clip_set, and clip_add's
speed != 1 error with a pointer to PR 4; they migrate to OTIO Effects /
LinearTimeWarp / per-clip metadata.otio_add_track, otio_add_clip, otio_remove_clip), data.frame
views (otio_tracks, otio_clips), and JSON (de)serialization through OTIO's
own serializer (otio_to_json / otio_from_json). A timeline can be built,
serialized to canonical .otio JSON, and parsed back entirely through the
wrapped C++ — no R-side schema code in the data path.SerializableObject::Retainer)
held through Rcpp::XPtr; OTIO objects are never deleted directly.tools/otio_introspect.R parses the OTIO C++ headers
with treesitR to extract each class's API; tools/otio_codegen.R validates a
binding manifest against those headers and emits the trivial scalar field
getters (src/otio_gen.cpp). The lifetime-sensitive code is hand-written.rational_time()/new_sequence() model and otio_time() are
unchanged. The user-facing verb layer migrates onto these objects in PR 3./usr/local (it is not packaged for apt and ships no
pkg-config file); src/Makevars hard-codes the include and link paths.otio_time(), an OTIO-backed time value wrapping
opentime::RationalTime as an external pointer, with otio_value(),
otio_rate(), otio_to_seconds(), otio_to_frames(), otio_rescaled_to(),
and otio_timecode(). All conversions are computed by the OTIO library
rather than reimplemented in R. otio_version() reports the linked version.rational_time() and the existing sequence model are unchanged;
otio_time() is additive. The data model migrates onto the C++ objects in
later PRs (Timeline/Clip in PR 2).Imports: Rcpp and LinkingTo: Rcpp.SystemRequirements: C++17 and libopentimelineio (>= 0.17), built from
source. See PLAN.md for the recipe.