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
102 changes: 71 additions & 31 deletions src/otio_ale_adapter/ale.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

DEFAULT_VIDEO_FORMAT = '1080'
ASC_SOP_REGEX = re.compile(r'(-*\d+\.\d+)')
ALE_SECTION_DESIGNATORS = {"Heading", "Column", "Data"}


def AVID_VIDEO_FORMAT_FROM_WIDTH_HEIGHT(width, height):
Expand Down Expand Up @@ -182,6 +183,70 @@ def _video_format_from_metadata(clips):
return AVID_VIDEO_FORMAT_FROM_WIDTH_HEIGHT(max_width, max_height)


def _read_heading_lines(lines):
"""
Consumes all the Header information from lines returning a dictionary
mapping header keys to header values.
"""
header = {}
# read until we run out of lines or the next section starts, not consuming
# the next section designator
while len(lines) and lines[0] not in ALE_SECTION_DESIGNATORS:

Choose a reason for hiding this comment

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

lines[0] not in ALE_SECTION_DESIGNATORS

Good, this means we skip over as many blank lines at the end as we need.

line = lines.pop(0)

Choose a reason for hiding this comment

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

I'm not a huge fan of mutating input arguments, but it's a private func, we're fine.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I don't love that either - I've renamed it to _pop_columns so it gives a little more of a hint about the mutation.

if line.strip() == "":
continue

if "\t" not in line:
raise ALEParseError("Invalid Heading line: " + line)

segments = line.split("\t")
while len(segments) >= 2:
Copy link
Member

Choose a reason for hiding this comment

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

Segments beyond the 2nd are lost here. Should that be an error condition, or should those be part of val?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the check on line 206 handles that behavior, right?

Choose a reason for hiding this comment

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

It only gets to line 207 and raises an ALEParseError if there's an odd number of values in the row. Otherwise it handles even pairs fine and they all get added to the header dictionary, no key value pairs lost.

WACK_HEADER = "FIELD_DELIM\tTABS\n\nVIDEO_FORMAT\t108\tBANANA\tAPPLE\tGRAPE\tJUICE\n\nAUDIO_FORMAT\t48kh\tCESSNA\tDIAMOND\n\nFPS\t25\tWRONGFPS\t23"
lines = WACK_HEADER.splitlines()
header = _read_heading_lines(lines)
header
>>> {'FIELD_DELIM': 'TABS', 'VIDEO_FORMAT': '108', 'BANANA': 'APPLE', 'GRAPE': 'JUICE', 'AUDIO_FORMAT': '48kh', 'CESSNA': 'DIAMOND', 'FPS': '25', 'WRONGFPS': '23'}

key, val = segments.pop(0), segments.pop(0)
header[key] = val
if len(segments) != 0:
raise ALEParseError("Invalid Heading line: " + line)

return header


def _pop_columns(lines):
"""
Consumes the Column information from the ALE and returns the list of
columns.
"""
try:
line = lines.pop(0)
# skip blank lines
while not line.strip():

Choose a reason for hiding this comment

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

Please add a "skip blank lines" comment here for readability.

line = lines.pop(0)
except IndexError:
raise ALEParseError("Unexpected end of file after 'Column'")

columns = line.split("\t")

return columns


def _read_data(lines, columns, fps, ale_name_column_key):
"""
Generator consuming the Data section of the ALE yielding Clips.
"""
while len(lines):
line = lines.pop(0)

if line.strip() == "":
continue

clip = _parse_data_line(
line,
columns,
fps,
ale_name_column_key=ale_name_column_key
)

yield clip


def read_from_string(input_str, fps=24, **adapter_argument_map):
ale_name_column_key = adapter_argument_map.get(
"ale_name_column_key", "Name"
Expand All @@ -203,21 +268,7 @@ def nextline(lines):
continue

if line.strip() == "Heading":
while len(lines):
line = nextline(lines)

if line.strip() == "":
break

if "\t" not in line:
raise ALEParseError("Invalid Heading line: " + line)

segments = line.split("\t")
while len(segments) >= 2:
key, val = segments.pop(0), segments.pop(0)
header[key] = val
if len(segments) != 0:
raise ALEParseError("Invalid Heading line: " + line)
header.update(_read_heading_lines(lines))

if "FPS" in header:
read_fps = float(header["FPS"])
Expand All @@ -234,24 +285,13 @@ def nextline(lines):
if len(lines) == 0:
raise ALEParseError("Unexpected end of file after: " + line)

line = nextline(lines)
columns = line.split("\t")
columns = _pop_columns(lines)

if line.strip() == "Data":
while len(lines):
line = nextline(lines)

if line.strip() == "":
continue

clip = _parse_data_line(
line,
columns,
fps,
ale_name_column_key=ale_name_column_key
)

collection.append(clip)
clip_generator = _read_data(
lines, columns, fps, ale_name_column_key
)
collection.extend(clip_generator)

Choose a reason for hiding this comment

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

We don't actually need to create the Collection until here, since we're no longer appending to it. Do we even need to extend it or can we just generate it in one line?

Copy link
Member Author

Choose a reason for hiding this comment

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

I did it above because if, for some reason, there's no data, we still can populate any other parsed header and columns.
Admittedly that's a corner case, but I think it's a little more robust.


collection.metadata["ALE"] = {
"header": header,
Expand Down
22 changes: 22 additions & 0 deletions tests/sample_data/sample_blanks.ale
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Heading

FIELD_DELIM TABS

VIDEO_FORMAT 1080

AUDIO_FORMAT 48khz

FPS 25



Column

Name Tracks Start End Take Tape UNC FPS Reel Scene Shoot date Manufacturer Source Resolution Source Bit Depth DESCRIPT Comments Audio SR Audio Bit Depth Auxiliary TC1 KN Start Source File Path Filter Camera ID ISO Lens Number Camera Serial # Camera Type White Point (Kelvin) Angle Camera Clip Number Stereoscopic Eye Gamma Space LUT 1 ASC_SOP ASC_SAT RESOLVE_SIZING



Data

A020C003_150905_E2XZ.mov V 05:42:12:20 05:42:30:12 A020E2XZ Y:\Pennrand_CompWorkflow_XXXXXX\04_Compositing\11_Render_Compositing\PennComp_Za250\PennComp_Za250_Raw\A020C003_150905_E2XZ.mov 25 A020E2XZ 20150905 DaVinci Resolve 2048x1152 16 Y:\Pennrand_CompWorkflow_XXXXXX\04_Compositing\11_Render_Compositing\PennComp_Za250\PennComp_Za250_Raw\A020C003_150905_E2XZ.mov 0 E2XZ 800 0 3120 Alexa Plus 4700 1800 A A020C003_150905_E2XZ SINGLE LOG-C None (1.1822 1.2183 1.2284)(-0.2429 -0.2823 -0.2849)(0.7283 0.7096 0.7054) 1.0680 (0.0000 0.0000 1.0000 0.0000 0.0000 0 0)

Loading