text: #!/usr/bin/env python
  #
  # Copyright Contributors to the OpenTimelineIO project
  #
  # Licensed under the Apache License, Version 2.0 (the "Apache License")
  # with the following modification; you may not use this file except in
  # compliance with the Apache License and the following modification to it:
  # Section 6. Trademarks. is deleted and replaced with:
  #
  # 6. Trademarks. This License does not grant permission to use the trade
  #    names, trademarks, service marks, or product names of the Licensor
  #    and its affiliates, except as required to comply with Section 4(c) of
  #    the License and to reproduce the content of the NOTICE file.
  #
  # You may obtain a copy of the Apache License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the Apache License with the above modification is
  # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  # KIND, either express or implied. See the Apache License for the specific
  # language governing permissions and limitations under the Apache License.
  #
  
  import unittest
  import os
  import copy
  
  import opentimelineio as otio
  import opentimelineio.test_utils as otio_test_utils
  
  SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data")
  TRANSITION_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "transition_test.otio")
  
  
  class CompositionTests(unittest.TestCase, otio_test_utils.OTIOAssertions):
  
      def test_cons(self):
          it = otio.core.Item()
          co = otio.core.Composition(name="test", children=[it])
          self.assertEqual(co.name, "test")
          self.assertEqual(list(co), [it])
          self.assertEqual(co.composition_kind, "Composition")
  
      def test_iterable(self):
          it = otio.core.Item()
          co = otio.core.Composition(children=[it])
          self.assertIs(co[0], it)
          self.assertEqual([i for i in co], [it])
          self.assertEqual(len(co), 1)
  
          self.assertEqual(list(co.each_child()), [it])
          self.assertEqual(
              list(co.each_child(descended_from_type=otio.schema.Clip)),
              []
          )
  
      def test_equality(self):
          co0 = otio.core.Composition()
          co00 = otio.core.Composition()
          self.assertIsOTIOEquivalentTo(co0, co00)
  
          a = otio.core.Item(name="A")
          b = otio.core.Item(name="B")
          c = otio.core.Item(name="C")
          co1 = otio.core.Composition(children=[a, b, c])
  
          x = otio.core.Item(name="X")
          y = otio.core.Item(name="Y")
          z = otio.core.Item(name="Z")
          co2 = otio.core.Composition(children=[x, y, z])
  
          a2 = otio.core.Item(name="A")
          b2 = otio.core.Item(name="B")
          c2 = otio.core.Item(name="C")
          co3 = otio.core.Composition(children=[a2, b2, c2])
  
          self.assertTrue(co1 is not co2)
          self.assertNotEqual(co1, co2)
  
          self.assertTrue(co1 is not co3)
          self.assertIsOTIOEquivalentTo(co1, co3)
  
      def test_truthiness(self):
          # An empty Composition is False (since it behaves like a list)
          o = otio.core.Composition()
          self.assertFalse(o)
  
          # A Composition with anything in it is True
          o.append(otio.core.Composition())
          self.assertTrue(o)
  
      def test_replacing_children(self):
          a = otio.core.Item(name="A")
          b = otio.core.Item(name="B")
          c = otio.core.Item(name="C")
          co = otio.core.Composition(children=[a, b])
          self.assertEqual(2, len(co))
          self.assertIs(co[0], a)
          self.assertIs(co[1], b)
          del co[1]
          self.assertEqual(1, len(co))
          self.assertIs(co[0], a)
          co[0] = c
          self.assertEqual(1, len(co))
          self.assertIs(co[0], c)
          co[:] = [a, b]
          self.assertEqual(2, len(co))
          self.assertIs(co[0], a)
          self.assertIs(co[1], b)
          co[0:2] = [c]
          self.assertEqual(1, len(co))
          self.assertIs(co[0], c)
          co[:] = [c, b, a]
          self.assertEqual(3, len(co))
          self.assertIs(co[0], c)
          self.assertIs(co[1], b)
          self.assertIs(co[2], a)
  
      def test_is_parent_of(self):
          co = otio.core.Composition()
          co_2 = otio.core.Composition()
  
          self.assertFalse(co.is_parent_of(co_2))
          co.append(co_2)
          self.assertTrue(co.is_parent_of(co_2))
  
      def test_parent_manip(self):
          it = otio.core.Item()
          co = otio.core.Composition(children=[it])
          self.assertIs(it.parent(), co)
  
      def test_move_child(self):
          it = otio.core.Item()
          co = otio.core.Composition(children=[it])
          self.assertIs(it.parent(), co)
  
          co2 = otio.core.Composition()
          with self.assertRaises(ValueError):
              co2.append(it)
  
          del co[0]
          co2.append(it)
          self.assertIs(it.parent(), co2)
  
      def test_each_child_recursion(self):
          tl = otio.schema.Timeline(name="TL")
  
          tr1 = otio.schema.Track(name="tr1")
          tl.tracks.append(tr1)
          c1 = otio.schema.Clip(name="c1")
          tr1.append(c1)
          c2 = otio.schema.Clip(name="c2")
          tr1.append(c2)
          c3 = otio.schema.Clip(name="c3")
          tr1.append(c3)
  
          tr2 = otio.schema.Track(name="tr2")
          tl.tracks.append(tr2)
          c4 = otio.schema.Clip(name="c4")
          tr2.append(c4)
          c5 = otio.schema.Clip(name="c5")
          tr2.append(c5)
  
          st = otio.schema.Stack(name="st")
          tr2.append(st)
          c6 = otio.schema.Clip(name="c6")
          st.append(c6)
          tr3 = otio.schema.Track(name="tr3")
          c7 = otio.schema.Clip(name="c7")
          tr3.append(c7)
          c8 = otio.schema.Clip(name="c8")
          tr3.append(c8)
          st.append(tr3)
  
          self.assertEqual(2, len(tl.tracks))
          self.assertEqual(3, len(tr1))
          self.assertEqual(3, len(tr2))
          self.assertEqual(2, len(st))
          self.assertEqual(2, len(tr3))
  
          clips = list(tl.each_clip())
          self.assertListEqual(
              [c1, c2, c3, c4, c5, c6, c7, c8],
              clips
          )
  
          all_tracks = list(tl.each_child(
              descended_from_type=otio.schema.Track
          ))
          self.assertListEqual(
              [tr1, tr2, tr3],
              all_tracks
          )
  
          all_stacks = list(tl.each_child(
              descended_from_type=otio.schema.Stack
          ))
          self.assertListEqual(
              [st],
              all_stacks
          )
  
          all_children = list(tl.each_child())
          self.assertListEqual(
              [tr1, c1, c2, c3, tr2, c4, c5, st, c6, tr3, c7, c8],
              all_children
          )
  
      def test_remove_actually_removes(self):
          """Test that removed item is no longer 'in' composition."""
          tr = otio.schema.Track()
          cl = otio.schema.Clip()
  
          # test inclusion
          tr.append(cl)
          self.assertIn(cl, tr)
  
          # delete by index
          del tr[0]
          self.assertNotIn(cl, tr)
  
          # delete by slice
          tr = otio.schema.Track()
          tr.append(cl)
          del tr[:]
          self.assertNotIn(cl, tr)
  
          # delete by setting over item
          tr = otio.schema.Track()
          tr.append(cl)
          cl2 = otio.schema.Clip()
          tr[0] = cl2
          self.assertNotIn(cl, tr)
  
          # delete by pop
          tr = otio.schema.Track()
          tr.insert(0, cl)
          tr.pop()
          self.assertNotIn(cl, tr)
  
      def test_insert_slice(self):
          """Test that inserting by slice actually correctly inserts"""
  
          st = otio.schema.Stack()
          cl = otio.schema.Clip()
  
          st[:] = [cl]
  
          self.assertEqual(cl, st[0])
  
          del st[0]
  
          self.assertEqual(len(st), 0)
  
          # again, this time deleting with a slice as well.
          st[:] = [cl]
  
          self.assertEqual(cl, st[0])
  
          del st[:]
  
          self.assertEqual(len(st), 0)
  
          st = otio.schema.Stack()
          cl = otio.schema.Clip()
          cl2 = otio.schema.Clip()
  
          st[:] = [cl]
          st[:] = [cl2]
  
          self.assertNotIn(cl, st)
          self.assertIn(cl2, st)
  
  
  class StackTest(unittest.TestCase, otio_test_utils.OTIOAssertions):
  
      def test_cons(self):
          st = otio.schema.Stack(name="test")
          self.assertEqual(st.name, "test")
  
      def test_serialize(self):
          st = otio.schema.Stack(
              name="test",
              children=[otio.schema.Clip(name="testClip")]
          )
  
          encoded = otio.adapters.otio_json.write_to_string(st)
          decoded = otio.adapters.otio_json.read_from_string(encoded)
          self.assertIsOTIOEquivalentTo(st, decoded)
  
          self.assertIsNotNone(decoded[0].parent())
  
      def test_str(self):
          st = otio.schema.Stack(name="foo", children=[])
          self.assertMultiLineEqual(
              str(st),
              "Stack(" +
              str(st.name) + ", " +
              str(list(st)) + ", " +
              str(st.source_range) + ", " +
              str(st.metadata) +
              ")"
          )
  
      def test_repr(self):
          st = otio.schema.Stack(name="foo", children=[])
          self.assertMultiLineEqual(
              repr(st),
              "otio.schema.Stack(" +
              "name=" + repr(st.name) + ", " +
              "children=" + repr(list(st)) + ", " +
              "source_range=" + repr(st.source_range) + ", " +
              "metadata=" + repr(st.metadata) +
              ")"
          )
  
      def test_trim_child_range(self):
          for st in [
              otio.schema.Track(name="foo"),
              otio.schema.Stack(name="foo")
          ]:
              st.source_range = otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(value=100, rate=24),
                  duration=otio.opentime.RationalTime(value=50, rate=24)
              )
              r = otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(value=110, rate=24),
                  duration=otio.opentime.RationalTime(value=30, rate=24)
              )
              self.assertEqual(st.trim_child_range(r), r)
              r = otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(value=0, rate=24),
                  duration=otio.opentime.RationalTime(value=30, rate=24)
              )
              self.assertEqual(st.trim_child_range(r), None)
              r = otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(value=1000, rate=24),
                  duration=otio.opentime.RationalTime(value=30, rate=24)
              )
              self.assertEqual(st.trim_child_range(r), None)
              r = otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(value=90, rate=24),
                  duration=otio.opentime.RationalTime(value=30, rate=24)
              )
              self.assertEqual(
                  st.trim_child_range(r),
                  otio.opentime.TimeRange(
                      start_time=otio.opentime.RationalTime(value=100, rate=24),
                      duration=otio.opentime.RationalTime(value=20, rate=24)
                  )
              )
              r = otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(value=110, rate=24),
                  duration=otio.opentime.RationalTime(value=50, rate=24)
              )
              self.assertEqual(
                  st.trim_child_range(r),
                  otio.opentime.TimeRange(
                      start_time=otio.opentime.RationalTime(value=110, rate=24),
                      duration=otio.opentime.RationalTime(value=40, rate=24)
                  )
              )
              r = otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(value=90, rate=24),
                  duration=otio.opentime.RationalTime(value=1000, rate=24)
              )
              self.assertEqual(
                  st.trim_child_range(r),
                  st.source_range
              )
  
      def test_range_of_child(self):
          st = otio.schema.Stack(name="foo", children=[
              otio.schema.Clip(
                  name="clip1",
                  source_range=otio.opentime.TimeRange(
                      start_time=otio.opentime.RationalTime(value=100, rate=24),
                      duration=otio.opentime.RationalTime(value=50, rate=24)
                  )
              ),
              otio.schema.Clip(
                  name="clip2",
                  source_range=otio.opentime.TimeRange(
                      start_time=otio.opentime.RationalTime(value=101, rate=24),
                      duration=otio.opentime.RationalTime(value=50, rate=24)
                  )
              ),
              otio.schema.Clip(
                  name="clip3",
                  source_range=otio.opentime.TimeRange(
                      start_time=otio.opentime.RationalTime(value=102, rate=24),
                      duration=otio.opentime.RationalTime(value=50, rate=24)
                  )
              )
          ])
  
          # The Stack should be as long as the longest child
          self.assertEqual(
              st.duration(),
              otio.opentime.RationalTime(value=50, rate=24)
          )
  
          # Stacked items should all start at time zero
          self.assertEqual(
              st.range_of_child_at_index(0).start_time,
              otio.opentime.RationalTime()
          )
          self.assertEqual(
              st.range_of_child_at_index(1).start_time,
              otio.opentime.RationalTime()
          )
          self.assertEqual(
              st.range_of_child_at_index(2).start_time,
              otio.opentime.RationalTime()
          )
  
          self.assertEqual(
              st.range_of_child_at_index(0).duration,
              otio.opentime.RationalTime(value=50, rate=24)
          )
          self.assertEqual(
              st.range_of_child_at_index(1).duration,
              otio.opentime.RationalTime(value=50, rate=24)
          )
          self.assertEqual(
              st.range_of_child_at_index(2).duration,
              otio.opentime.RationalTime(value=50, rate=24)
          )
  
          self.assertEqual(
              st.range_of_child_at_index(2),
              st.range_of_child(st[2])
          )
  
      def test_range_of_child_with_duration(self):
  
          st_sourcerange = otio.opentime.TimeRange(
              start_time=otio.opentime.RationalTime(5, 24),
              duration=otio.opentime.RationalTime(5, 24),
          )
          st = otio.schema.Stack(
              name="foo",
              children=[
                  otio.schema.Clip(
                      name="clip1",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=100,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip2",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=101,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip3",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=102,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  )
              ],
              source_range=st_sourcerange
          )
  
          # range always returns the pre-trimmed range.  To get the post-trim
          # range, call .trimmed_range()
          self.assertEqual(
              # get the pre-trimmed range in the reference space of the parent
              st.range_of_child(st[0], reference_space=st),
              otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(0, 24),
                  duration=otio.opentime.RationalTime(50, 24)
              )
          )
  
          self.assertEqual(
              st.transformed_time(otio.opentime.RationalTime(25, 24), st[0]),
              otio.opentime.RationalTime(125, 24)
          )
          self.assertEqual(
              (st[0]).transformed_time(otio.opentime.RationalTime(125, 24), st),
              otio.opentime.RationalTime(25, 24)
          )
  
          # # in the space of the child
          # self.assertEqual(
          #     # get the pre-trimmed range in the reference space of the parent
          #     st.range_of_child(st[0], reference_space=st[0]),
          #     otio.opentime.TimeRange(
          #         start_time=otio.opentime.RationalTime(105,24),
          #         duration=otio.opentime.RationalTime(5,24)
          #     )
          # )
  
          # trimmed_ functions take into account the source_range
          self.assertEqual(
              st.trimmed_range_of_child_at_index(0),
              st.source_range
          )
  
          self.assertEqual(
              st.trimmed_range_of_child(st[0], reference_space=st),
              otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(5, 24),
                  duration=otio.opentime.RationalTime(5, 24)
              )
          )
  
          # get the trimmed range in the parent
          self.assertEqual(
              st[0].trimmed_range_in_parent(),
              st.trimmed_range_of_child(st[0], reference_space=st),
          )
  
          # same test but via iteration
          for i, c in enumerate(st):
              self.assertEqual(
                  st[i].trimmed_range_in_parent(),
                  st.trimmed_range_of_child(st[i], reference_space=st),
              )
  
          with self.assertRaises(otio.exceptions.NotAChildError):
              otio.schema.Clip().trimmed_range_in_parent()
  
      def test_transformed_time(self):
          st = otio.schema.Stack(
              name="foo",
              children=[
                  otio.schema.Clip(
                      name="clip1",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=100,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip2",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=101,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip3",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=102,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  )
              ],
              source_range=otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(5, 24),
                  duration=otio.opentime.RationalTime(5, 24),
              )
          )
  
          clip1 = st[0]
          clip2 = st[1]
          clip3 = st[2]
          self.assertEqual(clip1.name, "clip1")
          self.assertEqual(clip2.name, "clip2")
          self.assertEqual(clip3.name, "clip3")
  
          test_time = otio.opentime.RationalTime(0, 24)
          self.assertEqual(
              st.transformed_time(test_time, clip1),
              otio.opentime.RationalTime(100, 24)
          )
  
          # ensure that transformed_time does not edit in place
          self.assertEqual(test_time, otio.opentime.RationalTime(0, 24))
  
          self.assertEqual(
              st.transformed_time(otio.opentime.RationalTime(0, 24), clip2),
              otio.opentime.RationalTime(101, 24)
          )
          self.assertEqual(
              st.transformed_time(otio.opentime.RationalTime(0, 24), clip3),
              otio.opentime.RationalTime(102, 24)
          )
  
          self.assertEqual(
              st.transformed_time(otio.opentime.RationalTime(50, 24), clip1),
              otio.opentime.RationalTime(150, 24)
          )
          self.assertEqual(
              st.transformed_time(otio.opentime.RationalTime(50, 24), clip2),
              otio.opentime.RationalTime(151, 24)
          )
          self.assertEqual(
              st.transformed_time(otio.opentime.RationalTime(50, 24), clip3),
              otio.opentime.RationalTime(152, 24)
          )
  
          self.assertEqual(
              clip1.transformed_time(otio.opentime.RationalTime(100, 24), st),
              otio.opentime.RationalTime(0, 24)
          )
          self.assertEqual(
              clip2.transformed_time(otio.opentime.RationalTime(101, 24), st),
              otio.opentime.RationalTime(0, 24)
          )
          self.assertEqual(
              clip3.transformed_time(otio.opentime.RationalTime(102, 24), st),
              otio.opentime.RationalTime(0, 24)
          )
  
          self.assertEqual(
              clip1.transformed_time(otio.opentime.RationalTime(150, 24), st),
              otio.opentime.RationalTime(50, 24)
          )
          self.assertEqual(
              clip2.transformed_time(otio.opentime.RationalTime(151, 24), st),
              otio.opentime.RationalTime(50, 24)
          )
          self.assertEqual(
              clip3.transformed_time(otio.opentime.RationalTime(152, 24), st),
              otio.opentime.RationalTime(50, 24)
          )
  
  
  class TrackTest(unittest.TestCase, otio_test_utils.OTIOAssertions):
  
      def test_serialize(self):
          sq = otio.schema.Track(name="foo", children=[])
  
          encoded = otio.adapters.otio_json.write_to_string(sq)
          decoded = otio.adapters.otio_json.read_from_string(encoded)
          self.assertIsOTIOEquivalentTo(sq, decoded)
  
      def test_str(self):
          sq = otio.schema.Track(name="foo", children=[])
          self.assertMultiLineEqual(
              str(sq),
              "Track(" +
              str(sq.name) + ", " +
              str(list(sq)) + ", " +
              str(sq.source_range) + ", " +
              str(sq.metadata) +
              ")"
          )
  
      def test_repr(self):
          sq = otio.schema.Track(name="foo", children=[])
          self.assertMultiLineEqual(
              repr(sq),
              "otio.schema.Track(" +
              "name=" + repr(sq.name) + ", " +
              "children=" + repr(list(sq)) + ", " +
              "source_range=" + repr(sq.source_range) + ", " +
              "metadata=" + repr(sq.metadata) +
              ")"
          )
  
      def test_instancing(self):
          length = otio.opentime.RationalTime(5, 1)
          tr = otio.opentime.TimeRange(otio.opentime.RationalTime(), length)
          it = otio.core.Item(source_range=tr)
          sq = otio.schema.Track(children=[it])
          self.assertEqual(sq.range_of_child_at_index(0), tr)
  
          # Can't put item on a composition if it's already in one
          with self.assertRaises(ValueError):
              otio.schema.Track(children=[it])
  
          # Instancing is not allowed
          with self.assertRaises(ValueError):
              otio.schema.Track(children=[it, it, it])
  
          # inserting duplicates should raise and have no
          # side effects
          self.assertEqual(len(sq), 1)
          with self.assertRaises(ValueError):
              sq.append(it)
          self.assertEqual(len(sq), 1)
  
          self.assertEqual(len(sq), 1)
          with self.assertRaises(ValueError):
              sq[:] = [it, it]
          self.assertEqual(len(sq), 1)
  
          self.assertEqual(len(sq), 1)
          with self.assertRaises(ValueError):
              sq.insert(1, it)
          self.assertEqual(len(sq), 1)
  
          sq[0] = it
          self.assertEqual(len(sq), 1)
  
          sq[:] = [it]
          self.assertEqual(len(sq), 1)
  
          sq.append(copy.deepcopy(it))
          self.assertEqual(len(sq), 2)
          with self.assertRaises(ValueError):
              sq[1:] = [it, copy.deepcopy(it)]
          self.assertEqual(len(sq), 2)
  
      def test_delete_parent_container(self):
          # deleting the parent container should null out the parent pointer
          it = otio.core.Item()
          sq = otio.schema.Track(children=[it])
          del sq
          self.assertIsNone(it.parent())
  
      def test_transactional(self):
          item = otio.core.Item()
          trackA = otio.core.Track()
          trackB = otio.core.Track()
  
          trackA.extend([item.clone(), item.clone(), item.clone()])
          self.assertEqual(len(trackA), 3)
  
          trackB.extend([item.clone(), item.clone(), item.clone()])
          self.assertEqual(len(trackB), 3)
  
          cached_contents = list(trackA)
  
          with self.assertRaises(ValueError):
              trackA[1:] = [
                  item.clone(),
                  item.clone(),
                  item.clone(),
                  item.clone(),
                  trackB[0]
              ]
          self.assertEqual(len(trackA), 3)
  
          with self.assertRaises(ValueError):
              trackA[-1:] = [item.clone(), item.clone(), trackB[0]]
          self.assertEqual(len(trackA), 3)
          self.assertEqual(cached_contents, list(trackA))
  
      def test_range(self):
          length = otio.opentime.RationalTime(5, 1)
          tr = otio.opentime.TimeRange(otio.opentime.RationalTime(), length)
          it = otio.core.Item(source_range=tr)
          sq = otio.schema.Track(children=[it])
          self.assertEqual(sq.range_of_child_at_index(0), tr)
  
          # It is an error to add an item to composition if it is already in
          # another composition.  This clears out the old test composition
          # (and also clears out its parent pointers).
          del sq[0]
          sq = otio.schema.Track(
              children=[it, it.clone(), it.clone(), it.clone()],
          )
          self.assertEqual(
              sq.range_of_child_at_index(index=1),
              otio.opentime.TimeRange(
                  otio.opentime.RationalTime(5, 1),
                  otio.opentime.RationalTime(5, 1)
              )
          )
          self.assertEqual(
              sq.range_of_child_at_index(index=0),
              otio.opentime.TimeRange(
                  otio.opentime.RationalTime(0, 1),
                  otio.opentime.RationalTime(5, 1)
              )
          )
          self.assertEqual(
              sq.range_of_child_at_index(index=-1),
              otio.opentime.TimeRange(
                  otio.opentime.RationalTime(15, 1),
                  otio.opentime.RationalTime(5, 1)
              )
          )
          with self.assertRaises(IndexError):
              sq.range_of_child_at_index(index=11)
          self.assertEqual(sq.duration(), length + length + length + length)
  
          # add a transition to either side
          in_offset = otio.opentime.RationalTime(10, 24)
          out_offset = otio.opentime.RationalTime(12, 24)
          range_of_item = sq.range_of_child_at_index(index=3)
          trx = otio.schema.Transition()
          trx.in_offset = in_offset
          trx.out_offset = out_offset
          sq.insert(0, copy.deepcopy(trx))
          sq.insert(3, copy.deepcopy(trx))
          sq.append(copy.deepcopy(trx))
  
          # range of Transition
          self.assertEqual(
              sq.range_of_child_at_index(index=3),
              otio.opentime.TimeRange(
                  otio.opentime.RationalTime(230, 24),
                  otio.opentime.RationalTime(22, 24)
              )
          )
          self.assertEqual(
              sq.range_of_child_at_index(index=-1),
              otio.opentime.TimeRange(
                  otio.opentime.RationalTime(470, 24),
                  otio.opentime.RationalTime(22, 24)
              )
          )
  
          # range of Item is not altered by insertion of transitions
          self.assertEqual(
              sq.range_of_child_at_index(index=5),
              range_of_item
          )
  
          # in_offset and out_offset for the beginning and ending
          self.assertEqual(
              sq.duration(),
              in_offset + length + length + length + length + out_offset
          )
  
      def test_range_of_child(self):
          sq = otio.schema.Track(
              name="foo",
              children=[
                  otio.schema.Clip(
                      name="clip1",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=100,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip2",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=101,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip3",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=102,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  )
              ]
          )
  
          # The Track should be as long as the children summed up
          self.assertEqual(
              sq.duration(),
              otio.opentime.RationalTime(value=150, rate=24)
          )
  
          # @TODO: should include time transforms
  
          # Sequenced items should all land end-to-end
          self.assertEqual(
              sq.range_of_child_at_index(0).start_time,
              otio.opentime.RationalTime()
          )
          self.assertEqual(
              sq.range_of_child_at_index(1).start_time,
              otio.opentime.RationalTime(value=50, rate=24)
          )
          self.assertEqual(
              sq.range_of_child_at_index(2).start_time,
              otio.opentime.RationalTime(value=100, rate=24)
          )
          self.assertEqual(
              sq.range_of_child(sq[2]),
              sq.range_of_child_at_index(2)
          )
  
          self.assertEqual(
              sq.range_of_child_at_index(0).duration,
              otio.opentime.RationalTime(value=50, rate=24)
          )
          self.assertEqual(
              sq.range_of_child_at_index(1).duration,
              otio.opentime.RationalTime(value=50, rate=24)
          )
          self.assertEqual(
              sq.range_of_child_at_index(2).duration,
              otio.opentime.RationalTime(value=50, rate=24)
          )
  
          # should trim 5 frames off the front, and 5 frames off the back
          sq_sourcerange = otio.opentime.TimeRange(
              start_time=otio.opentime.RationalTime(5, 24),
              duration=otio.opentime.RationalTime(140, 24),
          )
          sq.source_range = sq_sourcerange
          self.assertEqual(
              sq.trimmed_range_of_child_at_index(0),
              otio.opentime.TimeRange(
                  otio.opentime.RationalTime(5, 24),
                  otio.opentime.RationalTime(45, 24)
              )
          )
          self.assertEqual(
              sq.trimmed_range_of_child_at_index(1),
              sq.range_of_child_at_index(1)
          )
          self.assertEqual(
              sq.trimmed_range_of_child_at_index(2),
              otio.opentime.TimeRange(
                  otio.opentime.RationalTime(100, 24),
                  otio.opentime.RationalTime(45, 24),
              )
          )
  
          # get the trimmed range in the parent
          self.assertEqual(
              sq[0].trimmed_range_in_parent(),
              sq.trimmed_range_of_child(sq[0], reference_space=sq),
          )
  
          # same tesq but via iteration
          for i, c in enumerate(sq):
              self.assertEqual(
                  c.trimmed_range_in_parent(),
                  sq.trimmed_range_of_child_at_index(i)
              )
  
          with self.assertRaises(otio.exceptions.NotAChildError):
              otio.schema.Clip().trimmed_range_in_parent()
  
      def test_range_trimmed_out(self):
          track = otio.schema.Track(
              name="top_track",
              children=[
                  otio.schema.Clip(
                      name="clip1",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=100,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip2",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=101,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
              ],
              # should trim out clip 1
              source_range=otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(60, 24),
                  duration=otio.opentime.RationalTime(10, 24)
              )
          )
  
          # should be trimmed out, at the moment, the sentinel for that is None
          with self.assertRaises(ValueError):
              track.trimmed_range_of_child_at_index(0)
  
          not_nothing = track.trimmed_range_of_child_at_index(1)
          self.assertEqual(not_nothing, track.source_range)
  
          # should trim out second clip
          track.source_range = otio.opentime.TimeRange(
              start_time=otio.opentime.RationalTime(0, 24),
              duration=otio.opentime.RationalTime(10, 24)
          )
  
          with self.assertRaises(ValueError):
              track.trimmed_range_of_child_at_index(1)
  
          with self.assertRaises(ValueError):
              track[1].trimmed_range_in_parent()
  
          not_nothing = track.trimmed_range_of_child_at_index(0)
          self.assertEqual(not_nothing, track.source_range)
  
      def test_range_nested(self):
          track = otio.schema.Track(
              name="inner",
              children=[
                  otio.schema.Clip(
                      name="clip1",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=100,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip2",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=101,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip3",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=102,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  )
              ]
          )
  
          self.assertEqual(len(track), 3)
          self.assertEqual(len(track.deepcopy()), 3)
  
          # make a nested track with 3 sub-tracks, each with 3 clips
          outer_track = otio.schema.Track(name="outer", children=[
              track.deepcopy(),
              track.deepcopy(),
              track.deepcopy()
          ])
  
          # make one long track with 9 clips
          long_track = otio.schema.Track(name="long", children=(
              track.deepcopy()[:] + track.deepcopy()[:] + track.deepcopy()[:]
          ))
  
          # the original track's children should have been copied
          with self.assertRaises(otio.exceptions.NotAChildError):
              outer_track.range_of_child(track[1])
  
          with self.assertRaises(otio.exceptions.NotAChildError):
              long_track.range_of_child(track[1])
  
          # the nested and long tracks should be the same length
          self.assertEqual(
              outer_track.duration(),
              long_track.duration()
          )
  
          # the 9 clips within both compositions should have the same
          # overall timing, even though the nesting is different.
          self.assertListEqual(
              [
                  outer_track.range_of_child(clip)
                  for clip in outer_track.each_clip()
              ],
              [
                  long_track.range_of_child(clip)
                  for clip in long_track.each_clip()
              ]
          )
  
      def test_setitem(self):
          seq = otio.schema.Track()
          it = otio.schema.Clip()
          it_2 = otio.schema.Clip()
  
          seq.append(it)
          self.assertEqual(len(seq), 1)
  
          seq[0] = it_2
          self.assertEqual(len(seq), 1)
  
      def test_transformed_time(self):
          sq = otio.schema.Track(
              name="foo",
              children=[
                  otio.schema.Clip(
                      name="clip1",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=100,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip2",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=101,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Clip(
                      name="clip3",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=102,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=50,
                              rate=24
                          )
                      )
                  )
              ]
          )
          fl = otio.schema.Gap(
              name="GAP",
              source_range=otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(
                      value=0,
                      rate=24
                  ),
                  duration=otio.opentime.RationalTime(
                      value=50,
                      rate=24
                  )
              )
          )
          self.assertFalse(fl.visible())
          clip1 = sq[0]
          clip2 = sq[1]
          clip3 = sq[2]
          self.assertEqual(clip1.name, "clip1")
          self.assertEqual(clip2.name, "clip2")
          self.assertEqual(clip3.name, "clip3")
  
          self.assertEqual(
              list(
                  sq.each_clip(
                      otio.opentime.TimeRange(
                          otio.opentime.RationalTime(-1, 24)
                      )
                  )
              ),
              []
          )
          self.assertEqual(
              list(
                  sq.each_clip(
                      otio.opentime.TimeRange(
                          otio.opentime.RationalTime(0, 24)
                      )
                  )
              ),
              [clip1]
          )
          self.assertEqual(
              list(
                  sq.each_clip(
                      otio.opentime.TimeRange(
                          otio.opentime.RationalTime(49, 24)
                      )
                  )
              ),
              [clip1]
          )
          self.assertEqual(
              list(
                  sq.each_clip(
                      otio.opentime.TimeRange(
                          otio.opentime.RationalTime(50, 24)
                      )
                  )
              ),
              [clip2]
          )
          self.assertEqual(
              list(
                  sq.each_clip(
                      otio.opentime.TimeRange(
                          otio.opentime.RationalTime(99, 24)
                      )
                  )
              ),
              [clip2]
          )
          self.assertEqual(
              list(
                  sq.each_clip(
                      otio.opentime.TimeRange(
                          otio.opentime.RationalTime(100, 24)
                      )
                  )
              ),
              [clip3]
          )
          self.assertEqual(
              list(
                  sq.each_clip(
                      otio.opentime.TimeRange(
                          otio.opentime.RationalTime(149, 24)
                      )
                  )
              ),
              [clip3]
          )
          self.assertEqual(
              list(
                  sq.each_clip(
                      otio.opentime.TimeRange(
                          otio.opentime.RationalTime(150, 24)
                      )
                  )
              ),
              []
          )
  
          self.assertEqual(
              sq.transformed_time(otio.opentime.RationalTime(0, 24), clip1),
              otio.opentime.RationalTime(100, 24)
          )
          self.assertEqual(
              sq.transformed_time(otio.opentime.RationalTime(0, 24), clip2),
              otio.opentime.RationalTime(51, 24)
          )
          self.assertEqual(
              sq.transformed_time(otio.opentime.RationalTime(0, 24), clip3),
              otio.opentime.RationalTime(2, 24)
          )
  
          self.assertEqual(
              sq.transformed_time(otio.opentime.RationalTime(50, 24), clip1),
              otio.opentime.RationalTime(150, 24)
          )
          self.assertEqual(
              sq.transformed_time(otio.opentime.RationalTime(50, 24), clip2),
              otio.opentime.RationalTime(101, 24)
          )
          self.assertEqual(
              sq.transformed_time(otio.opentime.RationalTime(50, 24), clip3),
              otio.opentime.RationalTime(52, 24)
          )
  
          self.assertEqual(
              clip1.transformed_time(otio.opentime.RationalTime(100, 24), sq),
              otio.opentime.RationalTime(0, 24)
          )
          self.assertEqual(
              clip2.transformed_time(otio.opentime.RationalTime(101, 24), sq),
              otio.opentime.RationalTime(50, 24)
          )
          self.assertEqual(
              clip3.transformed_time(otio.opentime.RationalTime(102, 24), sq),
              otio.opentime.RationalTime(100, 24)
          )
  
          self.assertEqual(
              clip1.transformed_time(otio.opentime.RationalTime(150, 24), sq),
              otio.opentime.RationalTime(50, 24)
          )
          self.assertEqual(
              clip2.transformed_time(otio.opentime.RationalTime(151, 24), sq),
              otio.opentime.RationalTime(100, 24)
          )
          self.assertEqual(
              clip3.transformed_time(otio.opentime.RationalTime(152, 24), sq),
              otio.opentime.RationalTime(150, 24)
          )
  
      def test_neighbors_of_simple(self):
          seq = otio.schema.Track()
          trans = otio.schema.Transition(
              in_offset=otio.opentime.RationalTime(10, 24),
              out_offset=otio.opentime.RationalTime(10, 24)
          )
          seq.append(trans)
  
          # neighbors of first transition
          neighbors = seq.neighbors_of(
              seq[0],
              otio.schema.NeighborGapPolicy.never
          )
          self.assertEqual(neighbors, (None, None))
  
          # test with the neighbor filling policy on
          neighbors = seq.neighbors_of(
              seq[0],
              otio.schema.NeighborGapPolicy.around_transitions
          )
          fill = otio.schema.Gap(
              source_range=otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(0, trans.in_offset.rate),
                  duration=trans.in_offset
              )
          )
          self.assertJsonEqual(neighbors, (fill, fill.clone()))
  
      def test_neighbors_of_no_expand(self):
          seq = otio.schema.Track()
          seq.append(otio.schema.Clip())
          n = seq.neighbors_of(seq[0])
          self.assertEqual(n, (None, None))
          self.assertIs(n[0], (None))
          self.assertIs(n[1], (None))
  
      def test_neighbors_of_from_data(self):
          self.maxDiff = None
  
          edl_path = TRANSITION_EXAMPLE_PATH
          timeline = otio.adapters.read_from_file(edl_path)
  
          # track is [t, clip, t, clip, clip, t]
          seq = timeline.tracks[0]
  
          # neighbors of first transition
          neighbors = seq.neighbors_of(
              seq[0],
              otio.schema.NeighborGapPolicy.never
          )
          self.assertEqual(neighbors, (None, seq[1]))
  
          fill = otio.schema.Gap(
              source_range=otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(0, seq[0].in_offset.rate),
                  duration=seq[0].in_offset
              )
          )
  
          neighbors = seq.neighbors_of(
              seq[0],
              otio.schema.NeighborGapPolicy.around_transitions
          )
          self.assertJsonEqual(neighbors, (fill, seq[1]))
  
          # neighbor around second transition
          neighbors = seq.neighbors_of(
              seq[2],
              otio.schema.NeighborGapPolicy.never
          )
          self.assertEqual(neighbors, (seq[1], seq[3]))
  
          # no change w/ different policy
          neighbors = seq.neighbors_of(
              seq[2],
              otio.schema.NeighborGapPolicy.around_transitions
          )
          self.assertEqual(neighbors, (seq[1], seq[3]))
  
          # neighbor around third transition
          neighbors = seq.neighbors_of(
              seq[5],
              otio.schema.NeighborGapPolicy.never
          )
          self.assertEqual(neighbors, (seq[4], None))
  
          fill = otio.schema.Gap(
              source_range=otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(0, seq[5].out_offset.rate),
                  duration=seq[5].out_offset
              )
          )
  
          neighbors = seq.neighbors_of(
              seq[5],
              otio.schema.NeighborGapPolicy.around_transitions
          )
          self.assertJsonEqual(neighbors, (seq[4], fill))
  
      def test_track_range_of_all_children(self):
          edl_path = TRANSITION_EXAMPLE_PATH
          timeline = otio.adapters.read_from_file(edl_path)
          tr = timeline.tracks[0]
          mp = tr.range_of_all_children()
  
          # fetch all the valid children that should be in the map
          vc = list(tr.each_clip())
  
          self.assertEqual(mp[vc[0]].start_time.value, 0)
          self.assertEqual(mp[vc[1]].start_time, mp[vc[0]].duration)
  
          for track in timeline.tracks:
              for child in track:
                  self.assertEqual(child.range_in_parent(), mp[child])
  
          track = otio.schema.Track()
          self.assertEqual(track.range_of_all_children(), {})
  
  
  class EdgeCases(unittest.TestCase):
  
      def test_empty_compositions(self):
          timeline = otio.schema.Timeline()
  
          self.assertEqual(len(timeline.tracks), 0)
          self.assertEqual(
              timeline.tracks.duration(),
              otio.opentime.RationalTime(0, 24)
          )
  
      def test_iterating_over_dupes(self):
          timeline = otio.schema.Timeline()
          track = otio.schema.Track()
          timeline.tracks.append(track)
          clip = otio.schema.Clip(
              name="Dupe",
              source_range=otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(10, 30),
                  duration=otio.opentime.RationalTime(15, 30)
              )
          )
  
          # make several identical copies
          for i in range(10):
              dupe = copy.deepcopy(clip)
              track.append(dupe)
          self.assertEqual(len(track), 10)
          self.assertEqual(
              otio.opentime.TimeRange(
                  start_time=otio.opentime.RationalTime(0, 30),
                  duration=otio.opentime.RationalTime(150, 30)
              ),
              track.trimmed_range()
          )
  
          # test normal iteration
          previous = None
          for item in track:
              self.assertEqual(
                  track.range_of_child(item),
                  item.range_in_parent()
              )
              self.assertNotEqual(
                  item.range_in_parent(),
                  previous
              )
              previous = item.range_in_parent()
  
          # test recursive iteration
          previous = None
          for item in track.each_clip():
              self.assertEqual(
                  track.range_of_child(item),
                  item.range_in_parent()
              )
              self.assertNotEqual(
                  item.range_in_parent(),
                  previous
              )
              previous = item.range_in_parent()
  
          # compare to iteration by index
          previous = None
          for i, item in enumerate(track):
              self.assertEqual(
                  track.range_of_child(item),
                  track.range_of_child_at_index(i)
              )
              self.assertEqual(
                  track.range_of_child(item),
                  item.range_in_parent()
              )
              self.assertNotEqual(
                  item.range_in_parent(),
                  previous
              )
              previous = item.range_in_parent()
  
          # compare recursive to iteration by index
          previous = None
          for i, item in enumerate(track.each_clip()):
              self.assertEqual(
                  track.range_of_child(item),
                  track.range_of_child_at_index(i)
              )
              self.assertEqual(
                  track.range_of_child(item),
                  item.range_in_parent()
              )
              self.assertNotEqual(
                  item.range_in_parent(),
                  previous
              )
              previous = item.range_in_parent()
  
  
  class NestingTest(unittest.TestCase):
  
      def test_deeply_nested(self):
          # Take a single clip of media (frames 100-200) and nest it into a bunch
          # of layers
          # Nesting it should not shift the media at all.
          # At one level:
          # Timeline:
          #  Stack: [0-99]
          #   Track: [0-99]
          #    Clip: [100-199]
          #     Media Reference: [100-199]
  
          # here are some times in the top-level coordinate system
          zero = otio.opentime.RationalTime(0, 24)
          one = otio.opentime.RationalTime(1, 24)
          fifty = otio.opentime.RationalTime(50, 24)
          ninetynine = otio.opentime.RationalTime(99, 24)
          onehundred = otio.opentime.RationalTime(100, 24)
          top_level_range = otio.opentime.TimeRange(
              start_time=zero, duration=onehundred)
  
          # here are some times in the media-level coordinate system
          first_frame = otio.opentime.RationalTime(100, 24)
          middle = otio.opentime.RationalTime(150, 24)
          last = otio.opentime.RationalTime(199, 24)
          media_range = otio.opentime.TimeRange(
              start_time=first_frame, duration=onehundred)
  
          timeline = otio.schema.Timeline()
          stack = timeline.tracks
          track = otio.schema.Track()
          clip = otio.schema.Clip()
          media = otio.schema.MissingReference()
          media.available_range = media_range
          clip.media_reference = media
          track.append(clip)
          stack.append(track)
  
          self.assertIs(track, clip.parent())
          self.assertIs(stack, track.parent())
  
          # the clip and track should auto-size to fit the media, since we
          # haven't trimmed anything
          self.assertEqual(clip.duration(), onehundred)
          self.assertEqual(track.duration(), onehundred)
          self.assertEqual(stack.duration(), onehundred)
  
          # the ranges should match our expectations...
          self.assertEqual(clip.trimmed_range(), media_range)
          self.assertEqual(track.trimmed_range(), top_level_range)
          self.assertEqual(stack.trimmed_range(), top_level_range)
  
          # verify that the media is where we expect
          self.assertEqual(stack.transformed_time(zero, clip), first_frame)
          self.assertEqual(stack.transformed_time(fifty, clip), middle)
          self.assertEqual(stack.transformed_time(ninetynine, clip), last)
  
          def _nest(self, item):
  
              self.assertIsNotNone(item)
              self.assertIsNotNone(item.parent())
  
              parent = item.parent()
              index = parent.index(item)
              wrapper = otio.schema.Stack()
  
              self.assertIs(parent[index], item)
  
              # swap out the item for the wrapper
              parent[index] = wrapper
  
              self.assertIs(parent[index], wrapper)
              self.assertIsNot(parent[index], item)
  
              self.assertIs(wrapper.parent(), parent)
  
              # now put the item inside the wrapper
              wrapper.append(item)
  
              self.assertIs(item.parent(), wrapper)
  
              return wrapper
  
          # now nest it many layers deeper
          wrappers = []
          num_wrappers = 10
          for i in range(num_wrappers):
              # print "Nesting", (i+1), "levels deep"
              wrapper = _nest(self, clip)
              wrappers.append(wrapper)
  
          # nothing should have shifted at all
  
          # print otio.adapters.otio_json.write_to_string(timeline)
  
          # the clip and track should auto-size to fit the media, since we
          # haven't trimmed anything
          self.assertEqual(clip.duration(), onehundred)
          self.assertEqual(track.duration(), onehundred)
          self.assertEqual(stack.duration(), onehundred)
  
          # the ranges should match our expectations...
          self.assertEqual(clip.trimmed_range(), media_range)
          self.assertEqual(track.trimmed_range(), top_level_range)
          self.assertEqual(stack.trimmed_range(), top_level_range)
  
          # verify that the media is where we expect
          self.assertEqual(stack.transformed_time(zero, clip), first_frame)
          self.assertEqual(stack.transformed_time(fifty, clip), middle)
          self.assertEqual(stack.transformed_time(ninetynine, clip), last)
  
          # now trim them all by one frame at each end
          self.assertEqual(ninetynine, ninetynine)
          self.assertEqual(ninetynine + one, onehundred)
          trim = otio.opentime.TimeRange(
              start_time=one,
              duration=(ninetynine - one)
          )
          self.assertEqual(trim.duration, otio.opentime.RationalTime(98, 24))
          for wrapper in wrappers:
              wrapper.source_range = trim
  
          # print otio.adapters.otio_json.write_to_string(timeline)
  
          # the clip should be the same
          self.assertEqual(clip.duration(), onehundred)
  
          # the parents should have shrunk by only 2 frames
          self.assertEqual(track.duration(), otio.opentime.RationalTime(98, 24))
          self.assertEqual(stack.duration(), otio.opentime.RationalTime(98, 24))
  
          # but the media should have shifted over by 1 one frame for each level
          # of nesting
          ten = otio.opentime.RationalTime(num_wrappers, 24)
          self.assertEqual(
              stack.transformed_time(zero, clip),
              first_frame + ten
          )
          self.assertEqual(stack.transformed_time(fifty, clip), middle + ten)
          self.assertEqual(stack.transformed_time(ninetynine, clip), last + ten)
  
      def test_child_at_time_with_children(self):
          sq = otio.schema.Track(
              name="foo",
              children=[
                  otio.schema.Clip(
                      name="leader",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=100,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=10,
                              rate=24
                          )
                      )
                  ),
                  otio.schema.Track(
                      name="body",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=9,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=12,
                              rate=24
                          )
                      ),
                      children=[
                          otio.schema.Clip(
                              name="clip1",
                              source_range=otio.opentime.TimeRange(
                                  start_time=otio.opentime.RationalTime(
                                      value=100,
                                      rate=24
                                  ),
                                  duration=otio.opentime.RationalTime(
                                      value=10,
                                      rate=24
                                  )
                              )
                          ),
                          otio.schema.Clip(
                              name="clip2",
                              source_range=otio.opentime.TimeRange(
                                  start_time=otio.opentime.RationalTime(
                                      value=101,
                                      rate=24
                                  ),
                                  duration=otio.opentime.RationalTime(
                                      value=10,
                                      rate=24
                                  )
                              )
                          ),
                          otio.schema.Clip(
                              name="clip3",
                              source_range=otio.opentime.TimeRange(
                                  start_time=otio.opentime.RationalTime(
                                      value=102,
                                      rate=24
                                  ),
                                  duration=otio.opentime.RationalTime(
                                      value=10,
                                      rate=24
                                  )
                              )
                          )
                      ]
                  ),
                  otio.schema.Clip(
                      name="credits",
                      source_range=otio.opentime.TimeRange(
                          start_time=otio.opentime.RationalTime(
                              value=102,
                              rate=24
                          ),
                          duration=otio.opentime.RationalTime(
                              value=10,
                              rate=24
                          )
                      )
                  )
              ]
          )
  
          """
          Looks like this:
          [ leader ][ body ][ credits ]
          10 f       12f     10f
  
          body: (source range starts: 9f duration: 12f)
          [ clip1 ][ clip2 ][ clip 3]
          1f       11f
          """
  
          leader = sq[0]
          body = sq[1]
          credits = sq[2]
          clip1 = body[0]
          clip2 = body[1]
          clip3 = body[2]
          self.assertEqual(leader.name, "leader")
          self.assertEqual(body.name, "body")
          self.assertEqual(credits.name, "credits")
          self.assertEqual(clip1.name, "clip1")
          self.assertEqual(clip2.name, "clip2")
          self.assertEqual(clip3.name, "clip3")
  
          expected = [
              ('leader', 100),
              ('leader', 101),
              ('leader', 102),
              ('leader', 103),
              ('leader', 104),
              ('leader', 105),
              ('leader', 106),
              ('leader', 107),
              ('leader', 108),
              ('leader', 109),
              ('clip1', 109),
              ('clip2', 101),
              ('clip2', 102),
              ('clip2', 103),
              ('clip2', 104),
              ('clip2', 105),
              ('clip2', 106),
              ('clip2', 107),
              ('clip2', 108),
              ('clip2', 109),
              ('clip2', 110),
              ('clip3', 102),
              ('credits', 102),
              ('credits', 103),
              ('credits', 104),
              ('credits', 105),
              ('credits', 106),
              ('credits', 107),
              ('credits', 108),
              ('credits', 109),
              ('credits', 110),
              ('credits', 111)
          ]
  
          for frame, expected_val in enumerate(expected):
              # first test child_at_time
              playhead = otio.opentime.RationalTime(frame, 24)
              item = sq.child_at_time(playhead)
              mediaframe = sq.transformed_time(playhead, item)
  
              measured_val = (item.name, otio.opentime.to_frames(mediaframe, 24))
  
              self.assertEqual(
                  measured_val,
                  expected_val,
                  msg="Error with Search Time: {}, expected: {}, "
                  "got {}".format(playhead, expected_val, measured_val)
              )
  
              # then test each_child
              search_range = otio.opentime.TimeRange(
                  otio.opentime.RationalTime(frame, 24),
                  # with a 0 duration, should have the same result as above
              )
  
              item = list(sq.each_clip(search_range))[0]
              mediaframe = sq.transformed_time(playhead, item)
  
              measured_val = (item.name, otio.opentime.to_frames(mediaframe, 24))
  
              self.assertEqual(
                  measured_val,
                  expected_val,
                  msg="Error with Search Time: {}, expected: {}, "
                  "got {}".format(playhead, expected_val, measured_val)
              )
  
  
  class MembershipTest(unittest.TestCase, otio_test_utils.OTIOAssertions):
  
      def test_remove_actually_removes(self):
          """Test that removed item is no longer 'in' composition."""
          tr = otio.schema.Track()
          cl = otio.schema.Clip()
  
          # test inclusion
          tr.append(cl)
          self.assertIn(cl, tr)
  
          # delete by index
          del tr[0]
          self.assertNotIn(cl, tr)
  
          # delete by slice
          tr = otio.schema.Track()
          tr.append(cl)
          del tr[:]
          self.assertNotIn(cl, tr)
  
          # delete by setting over item
          tr = otio.schema.Track()
          tr.append(cl)
          cl2 = otio.schema.Clip()
          tr[0] = cl2
          self.assertNotIn(cl, tr)
  
          # delete by pop
          tr = otio.schema.Track()
          tr.insert(0, cl)
          tr.pop()
          self.assertNotIn(cl, tr)
  
  
  if __name__ == '__main__':
      unittest.main()
  