Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions src/py-opentimelineio/opentimelineio/adapters/fcp_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,21 +196,14 @@ def _name_from_element(element):
return ""


def _rate_for_element(element):
def _otio_rate(timebase, ntsc):
"""
Takes an FCP rate element and returns a rate to use with otio.

:param element: An FCP rate element.

:return: The float rate.
Given an FCP XML timebase and NTSC boolean, returns the float framerate.
"""
# rate is encoded as a timebase (int) which can be drop-frame
base = float(element.find("./timebase").text)
ntsc = element.find("./ntsc")
if ntsc is not None and _bool_value(ntsc):
base *= 1000.0 / 1001
if not ntsc:
return timebase

return base
return (timebase * 1000.0 / 1001)


def _rate_from_context(context):
Expand All @@ -221,12 +214,16 @@ def _rate_from_context(context):

:return: The rate value or ``None`` if no rate is available in the context.
"""
try:
rate_element = context["./rate"]
except KeyError:
timebase = context.get("./rate/timebase")
ntsc = context.get("./rate/ntsc")

if timebase is None:
return None

return _rate_for_element(rate_element)
return _otio_rate(
float(timebase.text),
_bool_value(ntsc) if ntsc is not None else None,
)


def _time_from_timecode_element(tc_element, context=None):
Expand Down Expand Up @@ -875,7 +872,9 @@ def media_reference_for_file_element(self, file_element, context):
# Find the timing
timecode_element = file_element.find("./timecode")
if timecode_element is not None:
start_time = _time_from_timecode_element(timecode_element)
start_time = _time_from_timecode_element(
timecode_element, local_context
)
start_time = start_time.rescaled_to(media_ref_rate)
else:
start_time = opentime.RationalTime(0, media_ref_rate)
Expand Down Expand Up @@ -1065,7 +1064,7 @@ def clip_for_element(
timecode_element = file_element.find("./timecode")
if timecode_element is not None:
media_start_time = _time_from_timecode_element(
timecode_element
timecode_element, local_context
)
elif generator_effect_element is not None:
media_reference = self.media_reference_for_effect_element(
Expand Down Expand Up @@ -1890,9 +1889,10 @@ def _build_timecode_from_metadata(time, tc_metadata=None):
tc_metadata = {}

try:

# Parse the rate in the preserved metadata, if available
tc_rate = _rate_for_element(
_dict_to_xml_tree(tc_metadata["rate"], "rate")
tc_rate = _otio_rate(
tc_metadata["timebase"], _bool_value(tc_metadata["ntsc"])
)
except KeyError:
# Default to the rate in the start time
Expand Down
107 changes: 83 additions & 24 deletions tests/test_fcp7_xml_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,64 +186,84 @@ def test_name_from_element(self):
def test_rate_for_element_ntsc_conversion_23976(self):
rate_element = cElementTree.fromstring(
"""
<rate>
<timebase>24</timebase>
<ntsc>TRUE</ntsc>
</rate>
<clipitem>
<rate>
<timebase>24</timebase>
<ntsc>TRUE</ntsc>
</rate>
</clipitem>
"""
)
rate = self.adapter._rate_for_element(rate_element)
rate = self.adapter._rate_from_context(
self.adapter._Context(rate_element)
)

self.assertEqual(rate, (24000 / 1001.0))

def test_rate_for_element_ntsc_conversion_24(self):
rate_element = cElementTree.fromstring(
"""
<rate>
<timebase>24</timebase>
<ntsc>FALSE</ntsc>
</rate>
<clipitem>
<rate>
<timebase>24</timebase>
<ntsc>FALSE</ntsc>
</rate>
</clipitem>
"""
)
rate = self.adapter._rate_for_element(rate_element)
rate = self.adapter._rate_from_context(
self.adapter._Context(rate_element)
)

self.assertEqual(rate, 24)

def test_rate_for_element_ntsc_conversion_2997(self):
rate_element = cElementTree.fromstring(
"""
<rate>
<timebase>30</timebase>
<ntsc>TRUE</ntsc>
</rate>
<clipitem>
<rate>
<timebase>30</timebase>
<ntsc>TRUE</ntsc>
</rate>
</clipitem>
"""
)
rate = self.adapter._rate_for_element(rate_element)
rate = self.adapter._rate_from_context(
self.adapter._Context(rate_element)
)

self.assertEqual(rate, (30000 / 1001.0))

def test_rate_for_element_ntsc_conversion_30(self):
rate_element = cElementTree.fromstring(
"""
<rate>
<timebase>30</timebase>
<ntsc>FALSE</ntsc>
</rate>
<clipitem>
<rate>
<timebase>30</timebase>
<ntsc>FALSE</ntsc>
</rate>
</clipitem>
"""
)
rate = self.adapter._rate_for_element(rate_element)
rate = self.adapter._rate_from_context(
self.adapter._Context(rate_element)
)

self.assertEqual(rate, 30)

def test_rate_for_element_no_ntsc(self):
rate_element = cElementTree.fromstring(
"""
<rate>
<timebase>30</timebase>
</rate>
<clipitem>
<rate>
<timebase>30</timebase>
</rate>
</clipitem>
"""
)
rate = self.adapter._rate_for_element(rate_element)
rate = self.adapter._rate_from_context(
self.adapter._Context(rate_element)
)

self.assertEqual(rate, 30)

Expand Down Expand Up @@ -351,6 +371,45 @@ def test_time_from_timecode_element_ntsc_non_drop_frame(self):
time, opentime.RationalTime(107892, (30000 / 1001.0))
)

def test_time_from_timecode_element_implicit_ntsc(self):
clipitem_element = cElementTree.fromstring(
"""
<clipitem>
<duration>767</duration>
<rate>
<ntsc>TRUE</ntsc>
<timebase>24</timebase>
</rate>
<in>447</in>
<out>477</out>
<start>264</start>
<end>294</end>
<file>
<rate>
<timebase>24</timebase>
<ntsc>TRUE</ntsc>
</rate>
<duration>767</duration>
<timecode>
<rate>
<timebase>24</timebase>
</rate>
<string>14:11:44:09</string>
<frame>1226505</frame>
<displayformat>NDF</displayformat>
<source>source</source>
</timecode>
</file>
</clipitem>
"""
)
context = self.adapter._Context(clipitem_element)
timecode_element = clipitem_element.find("./file/timecode")
time = self.adapter._time_from_timecode_element(
timecode_element, context
)
self.assertEqual(time, opentime.RationalTime(1226505, 24000.0 / 1001))

def test_track_kind_from_element(self):
video_element = cElementTree.fromstring("<video/>")
video_kind = self.adapter._track_kind_from_element(video_element)
Expand Down