Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reel name updates for the cmx_3600 adapter #392

Merged
merged 17 commits into from
Mar 12, 2019
Merged
Show file tree
Hide file tree
Changes from 15 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
95 changes: 72 additions & 23 deletions opentimelineio/adapters/cmx_3600.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,23 +104,18 @@ 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
if clip_handler.reel:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is preserving even the 'AX' cases that @mikekoetter had pruned. Do you think the strategy to ignore the AX ones is better? (see the comment in the deleted block).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, when I look at it I see no reason for adding AX to the metadata. It should still be there in the event line if no clip is present.
I'll add the test for it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you add the test, can you add back the comment? I thought that was a good explanation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ssteinbach I added the pruning of 'AX', but and issue with the test_cdl.test_cdl_round_trip might have reviled some poor design choices from my end. I'm going to dig a bit deeper to come up with a solution, but I'm afraid I need until next week as I need to sleep now and I'm away from my computer this weekend. Sorry about that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you add the test, can you add back the comment? I thought that was a good explanation.

Sure, no problem.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No rush! Definitely get your rest!

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
Expand Down Expand Up @@ -746,7 +741,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

Expand Down Expand Up @@ -778,17 +773,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(
Expand Down Expand Up @@ -862,7 +859,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):
Expand All @@ -872,7 +870,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):
Expand Down Expand Up @@ -926,9 +925,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()

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

Expand Down Expand Up @@ -995,7 +997,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).
Expand All @@ -1013,6 +1016,7 @@ def __init__(
clip=a_side_event.clip,
style=style,
edl_rate=rate,
reelname_len=reelname_len,
from_or_to='FROM'
)
else:
Expand All @@ -1024,7 +1028,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(
Expand All @@ -1040,6 +1048,7 @@ def __init__(
clip=b_side_clip,
style=style,
edl_rate=rate,
reelname_len=reelname_len,
from_or_to='TO'
)

Expand Down Expand Up @@ -1135,7 +1144,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

Expand Down Expand Up @@ -1195,6 +1210,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))
))

cdl = clip.metadata.get('cdl')
if cdl:
asc_sop = cdl.get('asc_sop')
Expand Down Expand Up @@ -1240,9 +1260,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
85 changes: 83 additions & 2 deletions tests/test_cmx_3600_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand Down