Skip to content
Permalink
Browse files

Reel name updates for the cmx_3600 adapter (#392)

* reel name based on clip name or external media reference.
* Also added a `reelname_len` argument to `_reel_from_clip()`
* Support windows slashes, fixed faulty logic in stripping down paths. 
* Added reel name to metadata of a clip when parsing an EDL. 
* Added comment to EDL when truncating reel name.
  • Loading branch information...
apetrynet authored and ssteinbach committed Mar 12, 2019
1 parent e46a5cf commit 9ffd252a7731e8dc5c66f1bd992156f8e227aa72
Showing with 170 additions and 26 deletions.
  1. +77 −23 opentimelineio/adapters/cmx_3600.py
  2. +10 −1 tests/test_cdl.py
  3. +83 −2 tests/test_cmx_3600_adapter.py
@@ -104,23 +104,22 @@ def add_clip(self, line, comments, rate=24):
comment_handler = CommentHandler(comments)
clip_handler = ClipHandler(line, comment_handler.handled, rate=rate)
clip = clip_handler.clip
reel = clip_handler.reel

# A reel name of `AX` represents an unknown or auxilary source
# We don't currently track these sources outside of this adapter
# So lets skip adding AX reels as metadata for now,
# as that would dirty json outputs with non-relevant information
if reel != 'AX':
clip.metadata.setdefault("cmx_3600", {})
clip.metadata['cmx_3600']['reel'] = reel

if comment_handler.unhandled:
clip.metadata.setdefault("cmx_3600", {})
clip.metadata['cmx_3600'].setdefault("comments", [])
clip.metadata['cmx_3600']['comments'] += (
comment_handler.unhandled
)

# Add reel name to metadata
# A reel name of `AX` represents an unknown or auxilary source
# We don't currently track these sources outside of this adapter
# So lets skip adding AX reels as metadata for now,
# as that would dirty json outputs with non-relevant information
if clip_handler.reel and clip_handler.reel != 'AX':
clip.metadata.setdefault("cmx_3600", {})
clip.metadata['cmx_3600']['reel'] = clip_handler.reel

# each edit point between two clips is a transition. the default is a
# cut in the edl format the transition codes are for the transition
# into the clip
@@ -746,7 +745,7 @@ def read_from_string(input_str, rate=24, ignore_timecode_mismatch=False):
return result


def write_to_string(input_otio, rate=None, style='avid'):
def write_to_string(input_otio, rate=None, style='avid', reelname_len=8):
# TODO: We should have convenience functions in Timeline for this?
# also only works for a single video track at the moment

@@ -778,17 +777,19 @@ def write_to_string(input_otio, rate=None, style='avid'):
tracks=input_otio.tracks,
# Assume all rates are the same as the 1st track's
rate=rate or input_otio.tracks[0].duration().rate,
style=style
style=style,
reelname_len=reelname_len
)

return writer.get_content_for_track_at_index(0, title=input_otio.name)


class EDLWriter(object):
def __init__(self, tracks, rate, style):
def __init__(self, tracks, rate, style, reelname_len=8):
self._tracks = tracks
self._rate = rate
self._style = style
self._reelname_len = reelname_len

if style not in VALID_EDL_STYLES:
raise otio.exceptions.NotSupportedError(
@@ -862,7 +863,8 @@ def get_content_for_track_at_index(self, idx, title):
self._tracks,
track.kind,
self._rate,
self._style
self._style,
self._reelname_len
)
)
elif isinstance(child, otio.schema.Clip):
@@ -872,7 +874,8 @@ def get_content_for_track_at_index(self, idx, title):
self._tracks,
track.kind,
self._rate,
self._style
self._style,
self._reelname_len
)
)
elif isinstance(child, otio.schema.Gap):
@@ -926,9 +929,11 @@ def __init__(
tracks,
kind,
rate,
style
style,
reelname_len
):
line = EventLine(kind, rate, reel=_reel_from_clip(clip))

line = EventLine(kind, rate, reel=_reel_from_clip(clip, reelname_len))
line.source_in = clip.source_range.start_time
line.source_out = clip.source_range.end_time_exclusive()

@@ -957,6 +962,7 @@ def __init__(
clip=clip,
style=style,
edl_rate=rate,
reelname_len=reelname_len,
from_or_to='FROM'
)

@@ -995,7 +1001,8 @@ def __init__(
tracks,
kind,
rate,
style
style,
reelname_len
):
# Note: We don't make the A-Side event line here as it is represented
# by its own event (edit number).
@@ -1013,6 +1020,7 @@ def __init__(
clip=a_side_event.clip,
style=style,
edl_rate=rate,
reelname_len=reelname_len,
from_or_to='FROM'
)
else:
@@ -1024,7 +1032,11 @@ def __init__(

self.cut_line = cut_line

dslve_line = EventLine(kind, rate, reel=_reel_from_clip(b_side_clip))
dslve_line = EventLine(
kind,
rate,
reel=_reel_from_clip(b_side_clip, reelname_len)
)
dslve_line.source_in = b_side_clip.source_range.start_time
dslve_line.source_out = b_side_clip.source_range.end_time_exclusive()
range_in_timeline = b_side_clip.transformed_time_range(
@@ -1040,6 +1052,7 @@ def __init__(
clip=b_side_clip,
style=style,
edl_rate=rate,
reelname_len=reelname_len,
from_or_to='TO'
)

@@ -1135,7 +1148,13 @@ def is_dissolve(self):
return self.dissolve_length.value > 0


def _generate_comment_lines(clip, style, edl_rate, from_or_to='FROM'):
def _generate_comment_lines(
clip,
style,
edl_rate,
reelname_len,
from_or_to='FROM'
):
lines = []
url = None

@@ -1150,6 +1169,7 @@ def _generate_comment_lines(clip, style, edl_rate, from_or_to='FROM'):
if clip.media_reference:
if hasattr(clip.media_reference, 'target_url'):
url = clip.media_reference.target_url

else:
url = clip.name

@@ -1195,6 +1215,11 @@ def _generate_comment_lines(clip, style, edl_rate, from_or_to='FROM'):
url=url
))

if reelname_len and not clip.metadata.get('cmx_3600', {}).get('reel'):
lines.append("* OTIO TRUNCATED REEL NAME FROM: {url}".format(
url=os.path.basename(_flip_windows_slashes(url or clip.name))
))

cdl = clip.metadata.get('cdl')
if cdl:
asc_sop = cdl.get('asc_sop')
@@ -1240,9 +1265,38 @@ def _generate_comment_lines(clip, style, edl_rate, from_or_to='FROM'):
return lines


def _reel_from_clip(clip):
if (isinstance(clip, otio.schema.Gap)):
def _flip_windows_slashes(path):
return re.sub(r'\\', '/', path)


def _reel_from_clip(clip, reelname_len):
if isinstance(clip, otio.schema.Gap):
return 'BL'

elif clip.metadata.get('cmx_3600', {}).get('reel'):
return clip.metadata.get('cmx_3600').get('reel')
return 'AX'

_reel = clip.name or 'AX'

if isinstance(clip.media_reference, otio.schema.ExternalReference):
_reel = clip.media_reference.name or os.path.basename(
clip.media_reference.target_url
)

# Flip Windows slashes
_reel = os.path.basename(_flip_windows_slashes(_reel))

# Strip extension
reel = re.sub(r'([.][a-zA-Z]+)$', '', _reel)

if reelname_len:
# Remove non valid characters
reel = re.sub(r'[^ a-zA-Z0-9]+', '', reel)

if len(reel) > reelname_len:
reel = reel[:reelname_len]

elif len(reel) < reelname_len:
reel += ' ' * (reelname_len - len(reel))

return reel
@@ -77,10 +77,19 @@ def test_cdl_round_trip(self):
*ASC_SOP (0.1 0.2 0.3) (1.0 -0.0122 0.0305) (1.0 0.0 1.0)
*ASC_SAT 0.9
* SOURCE FILE: ZZ100_501.LAY3.01
"""
expected = """TITLE: Example_Screening.01
001 ZZ100501 V C 01:00:04:05 01:00:05:12 00:00:00:00 00:00:01:07
* FROM CLIP NAME: ZZ100_501 (LAY3)
* OTIO TRUNCATED REEL NAME FROM: ZZ100_501 (LAY3)
*ASC_SOP (0.1 0.2 0.3) (1.0 -0.0122 0.0305) (1.0 0.0 1.0)
*ASC_SAT 0.9
* SOURCE FILE: ZZ100_501.LAY3.01
"""
timeline = otio.adapters.read_from_string(original, "cmx_3600")
output = otio.adapters.write_to_string(timeline, "cmx_3600")
self.assertMultiLineEqual(original, output)
self.assertMultiLineEqual(expected, output)


if __name__ == '__main__':
@@ -143,11 +143,85 @@ def test_edl_read(self):
otio.opentime.from_timecode("00:00:10:17", fps)
)

def test_reelname_length(self):
track = otio.schema.Track()
tl = otio.schema.Timeline("test_timeline", tracks=[track])
rt = otio.opentime.RationalTime(5.0, 24.0)

long_mr = otio.schema.ExternalReference(
target_url="/var/tmp/test_a_really_really_long_filename.mov"
)

tr = otio.opentime.TimeRange(
start_time=otio.opentime.RationalTime(0.0, 24.0),
duration=rt
)

cl = otio.schema.Clip(
name="test clip1",
media_reference=long_mr,
source_range=tr,
)

track.name = "V1"
track.append(cl)

# Test default behavior
result = otio.adapters.write_to_string(tl, adapter_name="cmx_3600")

expected = '''TITLE: test_timeline
001 testarea V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
* FROM CLIP NAME: test clip1
* FROM CLIP: /var/tmp/test_a_really_really_long_filename.mov
* OTIO TRUNCATED REEL NAME FROM: test_a_really_really_long_filename.mov
'''

self.assertMultiLineEqual(result, expected)

# Keep full filename (minus extension) as reelname
result = otio.adapters.write_to_string(
tl,
adapter_name="cmx_3600",
reelname_len=None
)
expected = '''TITLE: test_timeline
001 test_a_really_really_long_filename \
V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
* FROM CLIP NAME: test clip1
* FROM CLIP: /var/tmp/test_a_really_really_long_filename.mov
'''

self.assertMultiLineEqual(result, expected)

# Keep full filename (minus extension) as reelname
result = otio.adapters.write_to_string(
tl,
adapter_name="cmx_3600",
reelname_len=12
)
expected = '''TITLE: test_timeline
001 testareallyr V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
* FROM CLIP NAME: test clip1
* FROM CLIP: /var/tmp/test_a_really_really_long_filename.mov
* OTIO TRUNCATED REEL NAME FROM: test_a_really_really_long_filename.mov
'''

self.assertMultiLineEqual(result, expected)

def test_edl_round_trip_mem2disk2mem(self):
track = otio.schema.Track()
tl = otio.schema.Timeline("test_timeline", tracks=[track])
rt = otio.opentime.RationalTime(5.0, 24.0)
mr = otio.schema.ExternalReference(target_url="/var/tmp/test.mov")
md = {
"cmx_3600": {
"reel": "test",
"comments": ["OTIO TRUNCATED REEL NAME FROM: test.mov"]
}
}

tr = otio.opentime.TimeRange(
start_time=otio.opentime.RationalTime(0.0, 24.0),
@@ -158,27 +232,32 @@ def test_edl_round_trip_mem2disk2mem(self):
name="test clip1",
media_reference=mr,
source_range=tr,
metadata=md
)
cl2 = otio.schema.Clip(
name="test clip2",
media_reference=mr,
source_range=tr,
metadata=md
)
cl3 = otio.schema.Clip(
name="test clip3",
media_reference=mr,
source_range=tr,
metadata=md
)
cl4 = otio.schema.Clip(
name="test clip3_ff",
media_reference=mr,
source_range=tr,
metadata=md
)
cl4.effects = [otio.schema.FreezeFrame()]
cl5 = otio.schema.Clip(
name="test clip5 (speed)",
media_reference=mr,
source_range=tr,
metadata=md
)
cl5.effects = [otio.schema.LinearTimeWarp(time_scalar=2.0)]
track.name = "V"
@@ -494,12 +573,14 @@ def test_nucoda_edl_write(self):

expected = r'''TITLE: test_nucoda_timeline
001 AX V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
001 test V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
* FROM CLIP NAME: test clip1
* FROM FILE: S:\var\tmp\test.exr
002 AX V C 00:00:00:00 00:00:00:05 00:00:01:05 00:00:01:10
* OTIO TRUNCATED REEL NAME FROM: test.exr
002 test V C 00:00:00:00 00:00:00:05 00:00:01:05 00:00:01:10
* FROM CLIP NAME: test clip2
* FROM FILE: S:\var\tmp\test.exr
* OTIO TRUNCATED REEL NAME FROM: test.exr
'''

self.assertMultiLineEqual(result, expected)

0 comments on commit 9ffd252

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