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