def test_roundtrip_disk2mem2disk(self):
        # somefile.xml -> OTIO
        timeline = adapters.read_from_file(FCP7_XML_EXAMPLE_PATH)
        tmp_path = tempfile.mkstemp(suffix=".xml", text=True)[1]

        # somefile.xml -> OTIO -> tempfile.xml
        adapters.write_to_file(timeline, tmp_path)

        # somefile.xml -> OTIO -> tempfile.xml -> OTIO
        result = adapters.read_from_file(tmp_path)

        # TODO: OTIO doesn't support linking items for the moment, so the
        # adapter reads links to the metadata, but doesn't write them.
        # See _dict_to_xml_tree for more information.
        def scrub_md_dicts(timeline):
            def scrub_displayformat(md_dict):
                for ignore_key in {"link"}:
                    try:
                        del(md_dict[ignore_key])
                    except KeyError:
                        pass

                for value in list(md_dict.values()):
                    try:
                        value.items()
                        scrub_displayformat(value)
                    except AttributeError:
                        pass

            for child in timeline.tracks.each_child():
                scrub_displayformat(child.metadata)
                try:
                    scrub_displayformat(child.media_reference.metadata)
                except AttributeError:
                    pass

        # media reference bug, ensure that these match
        self.assertJsonEqual(
            result.tracks[0][1].media_reference,
            timeline.tracks[0][1].media_reference
        )

        scrub_md_dicts(result)
        scrub_md_dicts(timeline)

        self.assertJsonEqual(result, timeline)
        self.assertIsOTIOEquivalentTo(result, timeline)

        # But the xml text on disk is not identical because otio has a subset
        # of features to xml and we drop all the nle specific preferences.
        with open(FCP7_XML_EXAMPLE_PATH, "r") as original_file:
            with open(tmp_path, "r") as output_file:
                self.assertNotEqual(original_file.read(), output_file.read())
    def test_read_generators(self):
        timeline = adapters.read_from_file(GENERATOR_XML_EXAMPLE_PATH)

        video_track = timeline.tracks[0]
        audio_track = timeline.tracks[3]
        self.assertEqual(len(video_track), 6)
        self.assertEqual(len(audio_track), 3)

        # Check all video items are generators
        self.assertTrue(
            all(
                isinstance(item.media_reference, schema.GeneratorReference)
                for item in video_track
            )
        )

        # Check the video generator kinds
        self.assertEqual(
            [clip.media_reference.generator_kind for clip in video_track],
            ["Slug", "Slug", "Color", "Slug", "Slug", "GraphicAndType"],
        )

        # Check all non-gap audio items are generators
        self.assertTrue(
            all(
                isinstance(item.media_reference, schema.GeneratorReference)
                for item in video_track if not isinstance(item, schema.Gap)
            )
        )
    def test_hiero_flavored_xml(self):
        timeline = adapters.read_from_file(HIERO_XML_PATH)
        self.assertTrue(len(timeline.tracks), 1)
        self.assertTrue(timeline.tracks[0].name == 'Video 1')

        clips = [c for c in timeline.tracks[0].each_clip()]
        self.assertTrue(len(clips), 2)

        self.assertTrue(clips[0].name == 'A160C005_171213_R0MN')
        self.assertTrue(clips[1].name == '/')

        self.assertTrue(
            isinstance(
                clips[0].media_reference,
                schema.ExternalReference
            )
        )

        self.assertTrue(
            isinstance(
                clips[1].media_reference,
                schema.MissingReference
            )
        )

        source_range = opentime.TimeRange(
            start_time=opentime.RationalTime(1101071, 24),
            duration=opentime.RationalTime(1055, 24)
        )
        self.assertTrue(clips[0].source_range == source_range)

        available_range = opentime.TimeRange(
            start_time=opentime.RationalTime(1101071, 24),
            duration=opentime.RationalTime(1055, 24)
        )
        self.assertTrue(clips[0].available_range() == available_range)

        clip_1_range = clips[1].available_range()
        self.assertEqual(
            clip_1_range,
            opentime.TimeRange(
                opentime.RationalTime(),
                opentime.RationalTime(1, 24),
            )
        )

        # Test serialization
        tmp_path = tempfile.mkstemp(suffix=".xml", text=True)[1]
        adapters.write_to_file(timeline, tmp_path)

        # Similar to the test_roundtrip_disk2mem2disk above
        # the track name element among others will not be present in a new xml.
        with open(HIERO_XML_PATH, "r") as original_file:
            with open(tmp_path, "r") as output_file:
                self.assertNotEqual(original_file.read(), output_file.read())
    def test_read(self):
        timeline = adapters.read_from_file(FCP7_XML_EXAMPLE_PATH)

        self.assertTrue(timeline is not None)
        self.assertEqual(len(timeline.tracks), 8)

        video_tracks = [
            t for t in timeline.tracks if t.kind == schema.TrackKind.Video
        ]
        audio_tracks = [
            t for t in timeline.tracks if t.kind == schema.TrackKind.Audio
        ]

        self.assertEqual(len(video_tracks), 4)
        self.assertEqual(len(audio_tracks), 4)

        video_clip_names = (("", 'sc01_sh010_anim.mov'),
                            ("", 'sc01_sh010_anim.mov', "",
                             'sc01_sh020_anim.mov', 'sc01_sh030_anim.mov',
                             'Cross Dissolve', "", 'sc01_sh010_anim'),
                            ("", 'test_title'),
                            ("", 'sc01_master_layerA_sh030_temp.mov',
                             'Cross Dissolve', 'sc01_sh010_anim.mov'))

        for n, track in enumerate(video_tracks):
            self.assertTupleEqual(tuple(c.name for c in track),
                                  video_clip_names[n])

        audio_clip_names = (("", 'sc01_sh010_anim.mov', "",
                             'sc01_sh010_anim.mov'),
                            ("", 'sc01_placeholder.wav', "",
                             'sc01_sh010_anim'), ("", 'track_08.wav'),
                            ("", 'sc01_master_layerA_sh030_temp.mov',
                             'sc01_sh010_anim.mov'))

        for n, track in enumerate(audio_tracks):
            self.assertTupleEqual(tuple(c.name for c in track),
                                  audio_clip_names[n])

        video_clip_durations = (((536, 30.0), (100, 30.0)),
                                ((13, 30.0), (100, 30.0), (52, 30.0),
                                 (157, 30.0), (235, 30.0),
                                 ((19, 30.0), (0, 30.0)), (79, 30.0),
                                 (320, 30.0)), ((15, 30.0), (941, 30.0)),
                                ((956, 30.0), (208, 30.0),
                                 ((12, 30.0), (13, 30.0)), (82, 30.0)))

        for t, track in enumerate(video_tracks):
            for c, clip in enumerate(track):
                if isinstance(clip, schema.Transition):
                    self.assertEqual(
                        clip.in_offset,
                        opentime.RationalTime(*video_clip_durations[t][c][0]))
                    self.assertEqual(
                        clip.out_offset,
                        opentime.RationalTime(*video_clip_durations[t][c][1]))
                else:
                    self.assertEqual(
                        clip.source_range.duration,
                        opentime.RationalTime(*video_clip_durations[t][c]))

        audio_clip_durations = (((13, 30.0), (100, 30.0), (423, 30.0),
                                 (100, 30.0), (423, 30.0)),
                                ((335, 30.0), (170, 30.0), (131, 30.0),
                                 (294, 30.0), (34, 30.0),
                                 (124, 30.0)), ((153, 30.0), (198, 30.0)),
                                ((956, 30.0), (221, 30.0), (94, 30.0)))

        for t, track in enumerate(audio_tracks):
            for c, clip in enumerate(track):
                self.assertEqual(
                    clip.source_range.duration,
                    opentime.RationalTime(*audio_clip_durations[t][c]))

        timeline_marker_names = ('My MArker 1', 'dsf', "")

        for n, marker in enumerate(timeline.tracks.markers):
            self.assertEqual(marker.name, timeline_marker_names[n])

        timeline_marker_start_times = ((113, 30.0), (492, 30.0), (298, 30.0))

        for n, marker in enumerate(timeline.tracks.markers):
            self.assertEqual(
                marker.marked_range.start_time,
                opentime.RationalTime(*timeline_marker_start_times[n]))

        timeline_marker_comments = ('so, this happened', 'fsfsfs', None)

        for n, marker in enumerate(timeline.tracks.markers):
            self.assertEqual(
                marker.metadata.get('fcp_xml', {}).get('comment'),
                timeline_marker_comments[n])

        clip_with_marker = video_tracks[1][4]
        clip_marker = clip_with_marker.markers[0]
        self.assertEqual(clip_marker.name, "")
        self.assertEqual(clip_marker.marked_range.start_time,
                         opentime.RationalTime(73, 30.0))
        self.assertEqual(
            clip_marker.metadata.get('fcp_xml', {}).get('comment'), None)
    def test_xml_with_empty_elements(self):
        timeline = adapters.read_from_file(EMPTY_ELEMENT_XML_PATH)

        # Spot-check the EDL, this one would throw exception on load before
        self.assertEqual(len(timeline.video_tracks()), 12)
        self.assertEqual(len(timeline.video_tracks()[0]), 34)