|
32 | 32 | import uuid
|
33 | 33 | import opentimelineio as otio
|
34 | 34 | import os
|
| 35 | +import copy |
35 | 36 |
|
36 | 37 |
|
37 | 38 | AAF_PARAMETERDEF_PAN = aaf2.auid.AUID("e4962322-2267-11d3-8a4c-0050040ef7d2")
|
@@ -256,6 +257,25 @@ def _generate_empty_mobid(clip):
|
256 | 257 | return clip_mob_ids
|
257 | 258 |
|
258 | 259 |
|
| 260 | +def _stackify_nested_groups(timeline): |
| 261 | + """ |
| 262 | + Ensure that all nesting in a given timeline is in a stack container. |
| 263 | + This conforms with how AAF thinks about nesting, there needs |
| 264 | + to be an outer container, even if it's just one object. |
| 265 | + """ |
| 266 | + copied = copy.deepcopy(timeline) |
| 267 | + for track in copied.tracks: |
| 268 | + for i, child in enumerate(track.each_child()): |
| 269 | + is_nested = isinstance(child, otio.schema.Track) |
| 270 | + is_parent_in_stack = isinstance(child.parent(), otio.schema.Stack) |
| 271 | + if is_nested and not is_parent_in_stack: |
| 272 | + stack = otio.schema.Stack() |
| 273 | + track.remove(child) |
| 274 | + stack.append(child) |
| 275 | + track.insert(i, stack) |
| 276 | + return copied |
| 277 | + |
| 278 | + |
259 | 279 | class _TrackTranscriber(object):
|
260 | 280 | """
|
261 | 281 | _TrackTranscriber is the base class for the conversion of a given otio track.
|
@@ -294,20 +314,11 @@ def transcribe(self, otio_child):
|
294 | 314 | source_clip = self.aaf_sourceclip(otio_child)
|
295 | 315 | return source_clip
|
296 | 316 | elif isinstance(otio_child, otio.schema.Track):
|
297 |
| - operation_group = self.nesting_operation_group() |
298 |
| - sequence = operation_group.segments[0] |
299 |
| - length = 0 |
300 |
| - for nested_otio_child in otio_child: |
301 |
| - result = self.transcribe(nested_otio_child) |
302 |
| - sequence.components.append(result) |
303 |
| - length += result.length |
304 |
| - |
305 |
| - sequence.length = int(length) |
306 |
| - operation_group.length = length |
307 |
| - return operation_group |
| 317 | + sequence = self.aaf_sequence(otio_child) |
| 318 | + return sequence |
308 | 319 | elif isinstance(otio_child, otio.schema.Stack):
|
309 |
| - raise otio.exceptions.NotSupportedError( |
310 |
| - "Unsupported otio child type: otio.schema.Stack") |
| 320 | + operation_group = self.aaf_operation_group(otio_child) |
| 321 | + return operation_group |
311 | 322 | else:
|
312 | 323 | raise otio.exceptions.NotSupportedError(
|
313 | 324 | "Unsupported otio child type: {}".format(type(otio_child)))
|
@@ -369,7 +380,8 @@ def aaf_sourceclip(self, otio_clip):
|
369 | 380 | # We need both `start_time` and `duration`
|
370 | 381 | # Here `start` is the offset between `first` and `in` values.
|
371 | 382 |
|
372 |
| - offset = otio_clip.visible_range().start_time - otio_clip.available_range().start_time |
| 383 | + offset = (otio_clip.visible_range().start_time - |
| 384 | + otio_clip.available_range().start_time) |
373 | 385 | start = offset.value
|
374 | 386 | length = otio_clip.visible_range().duration.value
|
375 | 387 |
|
@@ -453,6 +465,49 @@ def aaf_transition(self, otio_transition):
|
453 | 465 | transition["DataDefinition"].value = datadef
|
454 | 466 | return transition
|
455 | 467 |
|
| 468 | + def aaf_sequence(self, otio_track): |
| 469 | + """Convert an otio Track into an aaf Sequence""" |
| 470 | + sequence = self.aaf_file.create.Sequence(media_kind=self.media_kind) |
| 471 | + length = 0 |
| 472 | + for nested_otio_child in otio_track: |
| 473 | + result = self.transcribe(nested_otio_child) |
| 474 | + length += result.length |
| 475 | + sequence.components.append(result) |
| 476 | + sequence.length = length |
| 477 | + return sequence |
| 478 | + |
| 479 | + def aaf_operation_group(self, otio_stack): |
| 480 | + """ |
| 481 | + Create and return an OperationGroup which will contain other AAF objects |
| 482 | + to support OTIO nesting |
| 483 | + """ |
| 484 | + # Create OperationDefinition |
| 485 | + op_def = self.aaf_file.create.OperationDef(AAF_OPERATIONDEF_SUBMASTER, |
| 486 | + "Submaster") |
| 487 | + self.aaf_file.dictionary.register_def(op_def) |
| 488 | + op_def.media_kind = self.media_kind |
| 489 | + datadef = self.aaf_file.dictionary.lookup_datadef(self.media_kind) |
| 490 | + |
| 491 | + # These values are necessary for pyaaf2 OperationDefinitions |
| 492 | + op_def["IsTimeWarp"].value = False |
| 493 | + op_def["Bypass"].value = 0 |
| 494 | + op_def["NumberInputs"].value = -1 |
| 495 | + op_def["OperationCategory"].value = "OperationCategory_Effect" |
| 496 | + op_def["DataDefinition"].value = datadef |
| 497 | + |
| 498 | + # Create OperationGroup |
| 499 | + operation_group = self.aaf_file.create.OperationGroup(op_def) |
| 500 | + operation_group.media_kind = self.media_kind |
| 501 | + operation_group["DataDefinition"].value = datadef |
| 502 | + |
| 503 | + length = 0 |
| 504 | + for nested_otio_child in otio_stack: |
| 505 | + result = self.transcribe(nested_otio_child) |
| 506 | + length += result.length |
| 507 | + operation_group.segments.append(result) |
| 508 | + operation_group.length = length |
| 509 | + return operation_group |
| 510 | + |
456 | 511 | def _create_tapemob(self, otio_clip):
|
457 | 512 | """
|
458 | 513 | Return a physical sourcemob for an otio Clip based on the MobID.
|
@@ -514,35 +569,6 @@ def _create_mastermob(self, otio_clip, filemob, filemob_slot):
|
514 | 569 | mastermob_slot.segment = mastermob_clip
|
515 | 570 | return mastermob, mastermob_slot
|
516 | 571 |
|
517 |
| - def nesting_operation_group(self): |
518 |
| - ''' |
519 |
| - Create and return an OperationGroup which will contain other AAF objects |
520 |
| - to support OTIO nesting |
521 |
| - ''' |
522 |
| - # Create OperationDefinition |
523 |
| - op_def = self.aaf_file.create.OperationDef(AAF_OPERATIONDEF_SUBMASTER, |
524 |
| - "Submaster") |
525 |
| - self.aaf_file.dictionary.register_def(op_def) |
526 |
| - op_def.media_kind = self.media_kind |
527 |
| - datadef = self.aaf_file.dictionary.lookup_datadef(self.media_kind) |
528 |
| - |
529 |
| - # These values are necessary for pyaaf2 OperationDefinitions |
530 |
| - op_def["IsTimeWarp"].value = False |
531 |
| - op_def["Bypass"].value = 0 |
532 |
| - op_def["NumberInputs"].value = -1 |
533 |
| - op_def["OperationCategory"].value = "OperationCategory_Effect" |
534 |
| - op_def["DataDefinition"].value = datadef |
535 |
| - |
536 |
| - # Create OperationGroup |
537 |
| - operation_group = self.aaf_file.create.OperationGroup(op_def) |
538 |
| - operation_group.media_kind = self.media_kind |
539 |
| - operation_group["DataDefinition"].value = datadef |
540 |
| - |
541 |
| - # Sequence |
542 |
| - sequence = self.aaf_file.create.Sequence(media_kind=self.media_kind) |
543 |
| - operation_group.segments.append(sequence) |
544 |
| - return operation_group |
545 |
| - |
546 | 572 |
|
547 | 573 | class VideoTrackTranscriber(_TrackTranscriber):
|
548 | 574 | """Video track kind specialization of TrackTranscriber."""
|
|
0 commit comments