Skip to content
Permalink
Browse files

Replace OTIO Core with python-wrapped C++ (#540)

Initial C++ integration with master.

Full notes on the branch:
This PR replaces the pure-python core of OpenTimelineIO with python wrapped C++. The functionality should be the same. There are some differences:

RationalTime enforces float types for its values. Serialized otio files will see a lot of .0s added if files are compared before/after this change.
the Name field defaults to "" instead of None
The .metadata dictionaries are of type AnyDictionary, so OrderedDicts and other similar classes will lose their type when being added to the metadata immediately (instead of being lost on serialization).
There is no longer a .children attribute on container classes.
The tox.ini file was collapsed into the setup.cfg file, along with some tuning of the lint settings.
Note that the C++ core does not include support for any of the plugins. Those are still entirely implemented in python. Non-plugin features are present in C++:

Reading/writing .otio files
Iterating over objects
Manipulating OTIO objects
Much like the core python project, the C++ is split into two libraries: opentime for manipulating time objects, and opentimelineio, which has all the functionality of the OpenTimelineIO data structures.

This adds some dependencies to the project (managed through Git submodules in the src/deps directory). When you sync after this change you'll need to use the git submodule init command to sync the dependencies. These dependencies include:

CMake (note that pip install . should still work and invoke cmake correctly under the hood for python only users)
Rapidjson
AnyDictionary
Optional-lite
pybind11
This PR also includes some preliminary Swift bindings as well (see src/swift-opentimelineio).

Huge thanks to @davidbaraff who did the port to C++, as well as the python and swift bindings.
  • Loading branch information...
ssteinbach committed Jul 22, 2019
1 parent b822704 commit ba0495174db2cd3cd17d9cf6de889817c9f84e33
Showing 352 changed files with 19,749 additions and 5,136 deletions.
@@ -5,12 +5,12 @@ codecov:
coverage:
precision: 2
round: down
range: "70...100"
range: "70...95"

status:
project: yes
patch: yes
changes: no
changes: yes

comment:
layout: "reach, diff, flags, files, footer"
@@ -22,3 +22,11 @@ ignore:
- "opentimelineio_contrib/adapters/tests/test_maya_sequencer.py"
- "opentimelineio_contrib/adapters/tests/test_burnins.py"
- "opentimelineio_contrib/adapters/burnins.py"
- "*aaf2*"
- "*pkg_resources*"
- "*pbr*"
- "*mock*"
- "*PIL*"
- "*funcsigs*"
- "/usr/*"
- "*/deps/*"
@@ -4,6 +4,7 @@ build*
dist*
*.egg-info
.coverage
coverage.info
.DS_store
htmlcov

@@ -0,0 +1,12 @@
[submodule "src/deps/optional-lite"]
path = src/deps/optional-lite
url = https://github.com/martinmoene/optional-lite
[submodule "src/deps/pybind11"]
path = src/deps/pybind11
url = https://github.com/pybind/pybind11.git
[submodule "src/deps/rapidjson"]
path = src/deps/rapidjson
url = https://github.com/Tencent/rapidjson.git
[submodule "src/deps/any"]
path = src/deps/any
url = https://github.com/thelink2012/any.git
@@ -1,3 +1,7 @@
addons:
apt:
packages: lcov

language: python
dist: xenial
python:
@@ -22,4 +26,5 @@ script:
after_success:
# Documentation for codecov uploader
# https://docs.codecov.io/docs/about-the-codecov-bash-uploader
- bash <(curl -s https://codecov.io/bash)
# run it twice, once to just print out data for debugging
- bash <(curl -s https://codecov.io/bash) -F $TOX_ENV -f .tox/build-$TOX_ENV/coverage.info -f .coverage
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 2.8.12)

add_subdirectory(src)
@@ -1,21 +1,22 @@
include README.md CHANGELOG.md LICENSE.txt NOTICE.txt
include README.md CHANGELOG.md LICENSE.txt NOTICE.txt CMakeLists.txt
recursive-include examples *
recursive-include opentimelineio *
recursive-include opentimelineio_contrib *.py
recursive-include contrib *
recursive-include src *

recursive-exclude docs *
prune docs
exclude .gitmodules
recursive-exclude src *.git
exclude .readthedocs.yml
exclude .codecov.yml
exclude .gitlab-ci.yml
exclude .travis.yml
exclude *.pdf
exclude CODE_OF_CONDUCT.md
exclude CONTRIBUTING.md
exclude opentimelineio_contrib/adapters/Makefile
exclude contrib/opentimelineio_contrib/adapters/Makefile
exclude Makefile

recursive-exclude opentimelineio_contrib/adapters/tests *
recursive-exclude **/__pycache__ *
global-exclude *.py[co]
recursive-exclude tests *
prune contrib/opentimelineio_contrib/adapters/tests
prune tests
prune src/deps/pybind11/tools/clang
prune src/deps/rapidjson/thirdparty
@@ -42,15 +42,15 @@ test-core: python-version

test-contrib: python-version
@echo "$(ccgreen)Running Contrib tests...$(ccend)"
@make -C opentimelineio_contrib/adapters test VERBOSE=$(VERBOSE)
@make -C contrib/opentimelineio_contrib/adapters test VERBOSE=$(VERBOSE)

python-version:
@python --version

coverage: coverage-core coverage-contrib coverage-report

coverage-report:
@${COV_PROG} combine .coverage opentimelineio_contrib/adapters/.coverage
@${COV_PROG} combine .coverage contrib/opentimelineio_contrib/adapters/.coverage
@${COV_PROG} report -m

coverage-core: python-version
@@ -62,7 +62,7 @@ endif
@${COV_PROG} run --source=opentimelineio -m unittest discover tests

coverage-contrib: python-version
@make -C opentimelineio_contrib/adapters coverage VERBOSE=$(VERBOSE)
@make -C contrib/opentimelineio_contrib/adapters coverage VERBOSE=$(VERBOSE)

# run all the unit tests, stopping at the first failure
test_first_fail: python-version
@@ -100,10 +100,10 @@ endif
@python -m flake8

doc-model:
@python opentimelineio/console/autogen_serialized_datamodel.py --dryrun
@python src/py-opentimelineio/opentimelineio/console/autogen_serialized_datamodel.py --dryrun

doc-model-update:
@python opentimelineio/console/autogen_serialized_datamodel.py -o docs/tutorials/otio-serialized-schema.md
@python src/py-opentimelineio/opentimelineio/console/autogen_serialized_datamodel.py -o docs/tutorials/otio-serialized-schema.md

# generate documentation in html
doc-html:
@@ -1,3 +1,5 @@
OpenTimelineIO
=======
[![OpenTimelineIO](docs/_static/OpenTimelineIO@3xDark.png)](http://opentimeline.io)
==============

@@ -69,6 +71,39 @@ You can install OpenTimelineIO via

For more details, including how to run the included viewer program, see: https://opentimelineio.readthedocs.io/en/latest/tutorials/quickstart.html

C++ Installation Instructions
-----------------------------

0. Get the source and deal with submodules:
+ git clone git@github.com:PixarAnimationStudios/OpenTimelineIO.git
+ cd otio
+ git checkout cxx
+ git submodule init
+ git submodule update

1. If you want to only build for C++ development (i.e. produce the OTIO C++ libraries and header files), then use cmake:
+ mkdir build
+ cd build
+ ccmake ..
(configure PYTHON_EXECUTABLE, PYTHON_LIBRARY, and CMAKE_INSTALL_PREFIX)
+ make install

2. If you wish to build only for use with Python, run one of the following two commands:
+ pip install .
+ python setup.py install

3. However, if you want to build for Python but you also want to install the OTIO C++ headers and libraries, then run one of the following two commands
+ pip install . --install-option=“--cxx-install-root=/home/someone/cxx-otio-root"
+ python setup.py install --cxx-install-root=/home/someone/cxx-otio-root

To compile a C++ file, add the following -I flags:
+ c++ -c source.cpp -I/home/someone/cxx-otio-root/include -I/home/someone/cxx-otio-root/include/opentimelineio/deps

To link, add the following -L/-l flags:
+ c++ ... -L/home/someone/cxx-otio-root/lib -lopentimelineio

(If you are using only opentime, not opentimelineio, use -lopentime. Also, you could leave out the second -I flag.)

Example Usage
-------

File renamed without changes.
@@ -1,6 +1,5 @@
# Makefile for the contrib area

export PYTHONPATH:=../../
TEST_ARGS=
COV_PROG := $(shell command -v coverage 2> /dev/null)

File renamed without changes.
@@ -111,13 +111,15 @@ def _unique_tapemob(self, otio_clip):
timecode_fps=timecode_fps,
drop_frame=(edit_rate != timecode_fps)
)
timecode_start = (
otio_clip.media_reference.available_range.start_time.value)
timecode_length = (
otio_clip.media_reference.available_range.duration.value)
timecode_start = int(
otio_clip.media_reference.available_range.start_time.value
)
timecode_length = int(
otio_clip.media_reference.available_range.duration.value
)

tape_timecode_slot.segment.start = timecode_start
tape_timecode_slot.segment.length = timecode_length
tape_timecode_slot.segment.start = int(timecode_start)
tape_timecode_slot.segment.length = int(timecode_length)
self.aaf_file.content.mobs.append(tapemob)
self._unique_tapemobs[mob_id] = tapemob
return tapemob
@@ -340,7 +342,7 @@ def _transition_parameters(self):

def aaf_filler(self, otio_gap):
"""Convert an otio Gap into an aaf Filler"""
length = otio_gap.visible_range().duration.value
length = int(otio_gap.visible_range().duration.value)
filler = self.aaf_file.create.Filler(self.media_kind, length)
return filler

@@ -362,9 +364,11 @@ def aaf_sourceclip(self, otio_clip):

compmob_clip = self.compositionmob.create_source_clip(
slot_id=self.timeline_mobslot.slot_id,
start=start,
length=length,
media_kind=self.media_kind)
# XXX: Python3 requires these to be passed as explicit ints
start=int(start),
length=int(length),
media_kind=self.media_kind
)
compmob_clip.mob = mastermob
compmob_clip.slot = mastermob_slot
compmob_clip.slot_id = mastermob_slot.slot_id
@@ -373,7 +377,7 @@ def aaf_sourceclip(self, otio_clip):
def aaf_transition(self, otio_transition):
"""Convert an otio Transition into an aaf Transition"""
if (otio_transition.transition_type !=
otio.schema.transition.TransitionTypes.SMPTE_Dissolve):
otio.schema.TransitionTypes.SMPTE_Dissolve):
print(
"Unsupported transition type: {}".format(
otio_transition.transition_type))
@@ -428,7 +432,7 @@ def aaf_transition(self, otio_transition):
op_def["Description"].value = str(description)

# Create OperationGroup
length = otio_transition.duration().value
length = int(otio_transition.duration().value)
operation_group = self.aaf_file.create.OperationGroup(op_def, length)
operation_group["DataDefinition"].value = datadef
operation_group["Parameters"].append(varying_value)
@@ -492,7 +496,7 @@ def _create_tapemob(self, otio_clip):
"""
tapemob = self.root_file_transcriber._unique_tapemob(otio_clip)
tapemob_slot = tapemob.create_empty_slot(self.edit_rate, self.media_kind)
tapemob_slot.segment.length = (
tapemob_slot.segment.length = int(
otio_clip.media_reference.available_range.duration.value)
return tapemob, tapemob_slot

@@ -526,7 +530,7 @@ def _create_mastermob(self, otio_clip, filemob, filemob_slot):
Returns a tuple of (MasterMob, MasterMobSlot)
"""
mastermob = self.root_file_transcriber._unique_mastermob(otio_clip)
timecode_length = otio_clip.media_reference.available_range.duration.value
timecode_length = int(otio_clip.media_reference.available_range.duration.value)

try:
mastermob_slot = mastermob.slot_at(self._master_mob_slot_id)
@@ -648,7 +652,7 @@ def aaf_sourceclip(self, otio_clip):
"LinearInterp")
self.aaf_file.dictionary.register_def(interp_def)
# PointList
length = otio_clip.duration().value
length = int(otio_clip.duration().value)
c1 = self.aaf_file.create.ControlPoint()
c1["ControlPointSource"].value = 2
c1["Time"].value = aaf2.rational.AAFRational("0/{}".format(length))
@@ -683,7 +687,7 @@ def _create_timeline_mobslot(self):
opdef["NumberInputs"].value = 1
self.aaf_file.dictionary.register_def(opdef)
# OperationGroup
total_length = sum([t.duration().value for t in self.otio_track])
total_length = int(sum([t.duration().value for t in self.otio_track]))
opgroup = self.aaf_file.create.OperationGroup(opdef)
opgroup.media_kind = self.media_kind
opgroup.length = total_length
@@ -702,8 +706,9 @@ def default_descriptor(self, otio_clip):
descriptor["AudioSamplingRate"].value = 48000
descriptor["Channels"].value = 1
descriptor["SampleRate"].value = 48000
descriptor["Length"].value = (
otio_clip.media_reference.available_range.duration.value)
descriptor["Length"].value = int(
otio_clip.media_reference.available_range.duration.value
)
return descriptor

def _transition_parameters(self):

0 comments on commit ba04951

Please sign in to comment.
You can’t perform that action at this time.