|
15 | 15 | current command-line context, as well as the processing result (scenes and cuts). |
16 | 16 | """ |
17 | 17 |
|
| 18 | +import json |
18 | 19 | import logging |
| 20 | +import os.path |
19 | 21 | import typing as ty |
20 | 22 | import webbrowser |
21 | 23 | from datetime import datetime |
@@ -401,12 +403,12 @@ def _save_xml_fcp( |
401 | 403 |
|
402 | 404 | rate = ElementTree.SubElement(sequence, "rate") |
403 | 405 | ElementTree.SubElement(rate, "timebase").text = str(context.video_stream.frame_rate) |
404 | | - ElementTree.SubElement(rate, "ntsc").text = "FALSE" |
| 406 | + ElementTree.SubElement(rate, "ntsc").text = "False" |
405 | 407 |
|
406 | 408 | timecode = ElementTree.SubElement(sequence, "timecode") |
407 | 409 | tc_rate = ElementTree.SubElement(timecode, "rate") |
408 | 410 | ElementTree.SubElement(tc_rate, "timebase").text = str(context.video_stream.frame_rate) |
409 | | - ElementTree.SubElement(tc_rate, "ntsc").text = "FALSE" |
| 411 | + ElementTree.SubElement(tc_rate, "ntsc").text = "False" |
410 | 412 | ElementTree.SubElement(timecode, "frame").text = "0" |
411 | 413 | ElementTree.SubElement(timecode, "displayformat").text = "NDF" |
412 | 414 |
|
@@ -478,3 +480,100 @@ def save_xml( |
478 | 480 | _save_xml_fcp(context, scenes, filename, output) |
479 | 481 | else: |
480 | 482 | logger.error(f"Unknown format: {format}") |
| 483 | + |
| 484 | + |
| 485 | +def save_otio( |
| 486 | + context: CliContext, |
| 487 | + scenes: SceneList, |
| 488 | + cuts: CutList, |
| 489 | + filename: str, |
| 490 | + output: str, |
| 491 | + name: str, |
| 492 | +): |
| 493 | + """Saves scenes in OTIO format.""" |
| 494 | + |
| 495 | + del cuts # We only use scene information |
| 496 | + |
| 497 | + video_name = context.video_stream.name |
| 498 | + video_path = os.path.abspath(context.video_stream.path) |
| 499 | + video_base_name = os.path.basename(context.video_stream.path) |
| 500 | + frame_rate = context.video_stream.frame_rate |
| 501 | + |
| 502 | + # List of track mapping to resource type. |
| 503 | + # TODO(#497): Allow exporting without an audio track. |
| 504 | + track_list = {"Video 1": "Video", "Audio 1": "Audio"} |
| 505 | + |
| 506 | + otio = { |
| 507 | + "OTIO_SCHEMA": "Timeline.1", |
| 508 | + "name": Template(name).safe_substitute(VIDEO_NAME=video_name), |
| 509 | + "global_start_time": { |
| 510 | + "OTIO_SCHEMA": "RationalTime.1", |
| 511 | + "rate": frame_rate, |
| 512 | + "value": 0.0, |
| 513 | + }, |
| 514 | + "tracks": { |
| 515 | + "OTIO_SCHEMA": "Stack.1", |
| 516 | + "enabled": True, |
| 517 | + "children": [ |
| 518 | + { |
| 519 | + "OTIO_SCHEMA": "Track.1", |
| 520 | + "name": track_name, |
| 521 | + "enabled": True, |
| 522 | + "children": [ |
| 523 | + { |
| 524 | + "OTIO_SCHEMA": "Clip.2", |
| 525 | + "name": video_base_name, |
| 526 | + "source_range": { |
| 527 | + "OTIO_SCHEMA": "TimeRange.1", |
| 528 | + "duration": { |
| 529 | + "OTIO_SCHEMA": "RationalTime.1", |
| 530 | + "rate": frame_rate, |
| 531 | + "value": float((end - start).get_frames()), |
| 532 | + }, |
| 533 | + "start_time": { |
| 534 | + "OTIO_SCHEMA": "RationalTime.1", |
| 535 | + "rate": frame_rate, |
| 536 | + "value": float(start.get_frames()), |
| 537 | + }, |
| 538 | + }, |
| 539 | + "enabled": True, |
| 540 | + "media_references": { |
| 541 | + "DEFAULT_MEDIA": { |
| 542 | + "OTIO_SCHEMA": "ExternalReference.1", |
| 543 | + "name": video_base_name, |
| 544 | + "available_range": { |
| 545 | + "OTIO_SCHEMA": "TimeRange.1", |
| 546 | + "duration": { |
| 547 | + "OTIO_SCHEMA": "RationalTime.1", |
| 548 | + "rate": frame_rate, |
| 549 | + "value": 1980.0, |
| 550 | + }, |
| 551 | + "start_time": { |
| 552 | + "OTIO_SCHEMA": "RationalTime.1", |
| 553 | + "rate": frame_rate, |
| 554 | + "value": 0.0, |
| 555 | + }, |
| 556 | + }, |
| 557 | + "available_image_bounds": None, |
| 558 | + "target_url": video_path, |
| 559 | + } |
| 560 | + }, |
| 561 | + "active_media_reference_key": "DEFAULT_MEDIA", |
| 562 | + } |
| 563 | + for (start, end) in scenes |
| 564 | + ], |
| 565 | + "kind": track_type, |
| 566 | + } |
| 567 | + for (track_name, track_type) in track_list.items() |
| 568 | + ], |
| 569 | + }, |
| 570 | + } |
| 571 | + |
| 572 | + otio_path = get_and_create_path( |
| 573 | + Template(filename).safe_substitute(VIDEO_NAME=context.video_stream.name), |
| 574 | + output, |
| 575 | + ) |
| 576 | + logger.info(f"Writing scenes in OTIO format to {otio_path}") |
| 577 | + with open(otio_path, "w") as f: |
| 578 | + json.dump(otio, f, indent=4) |
| 579 | + f.write("\n") |
0 commit comments