238
238
Sequence )
239
239
from aaf2 .mobs import MasterMob , SourceMob
240
240
from aaf2 .misc import VaryingValue
241
+ from aaf2 .mobid import MobID
241
242
could_import_aaf = True
242
243
except (ImportError ):
243
244
could_import_aaf = False
@@ -1795,6 +1796,42 @@ def test_aaf_writer_nesting(self):
1795
1796
def test_aaf_writer_nested_stack (self ):
1796
1797
self ._verify_aaf (NESTED_STACK_EXAMPLE_PATH )
1797
1798
1799
+ def test_aaf_writer_external_reference (self ):
1800
+ target_url = "file:///C%3A/Avid%20MediaFiles/MXF/1/7003_Vi48896FA0V.mxf"
1801
+
1802
+ mob_id = MobID (int = 10 )
1803
+ metadata = {"AAF" : {"SourceID" : str (mob_id )}}
1804
+
1805
+ tl = otio .schema .Timeline ()
1806
+ cl = otio .schema .Clip ("clip0" , metadata = metadata )
1807
+
1808
+ cl .source_range = otio .opentime .TimeRange (
1809
+ otio .opentime .RationalTime (0 , 24 ),
1810
+ otio .opentime .RationalTime (100 , 24 ),
1811
+ )
1812
+ tl .tracks .append (otio .schema .Track (kind = 'Video' ))
1813
+ tl .tracks [0 ].append (cl )
1814
+ cl .media_reference = otio .schema .ExternalReference (target_url ,
1815
+ cl .source_range )
1816
+
1817
+ fd , tmp_aaf_path = tempfile .mkstemp (suffix = '.aaf' )
1818
+ otio .adapters .write_to_file (tl , tmp_aaf_path )
1819
+
1820
+ self ._verify_aaf (tmp_aaf_path )
1821
+
1822
+ with aaf2 .open (tmp_aaf_path ) as dest :
1823
+ mastermob = dest .content .mobs .get (mob_id , None )
1824
+ self .assertNotEqual (mastermob , None )
1825
+ self .assertEqual (cl .name , mastermob .name )
1826
+ self .assertEqual (mob_id , mastermob .mob_id )
1827
+ self .assertEqual (len (mastermob .slots ), 1 )
1828
+ source_clip = mastermob .slots [0 ].segment
1829
+ self .assertEqual (source_clip .media_kind , "Picture" )
1830
+ filemob = source_clip .mob
1831
+ self .assertEqual (len (filemob .descriptor ['Locator' ]), 1 )
1832
+ locator = filemob .descriptor ['Locator' ].value [0 ]
1833
+ self .assertEqual (locator ['URLString' ].value , target_url )
1834
+
1798
1835
def test_generator_reference (self ):
1799
1836
tl = otio .schema .Timeline ()
1800
1837
cl = otio .schema .Clip ()
@@ -1869,6 +1906,37 @@ def test_aaf_writer_user_comments(self):
1869
1906
self .assertEqual (dict (master_mob .comments .items ()), expected_comments )
1870
1907
self .assertEqual (dict (comp_mob .comments .items ()), expected_comments )
1871
1908
1909
+ def test_aaf_writer_global_start_time (self ):
1910
+ for tc , rate in [("01:00:00:00" , 23.97 ),
1911
+ ("01:00:00:00" , 24 ),
1912
+ ("01:00:00:00" , 25 ),
1913
+ ("01:00:00:00" , 29.97 ),
1914
+ ("01:00:00:00" , 30 ),
1915
+ ("01:00:00:00" , 59.94 ),
1916
+ ("01:00:00:00" , 60 )]:
1917
+
1918
+ otio_timeline = otio .schema .Timeline ()
1919
+ otio_timeline .global_start_time = otio .opentime .from_timecode (tc , rate )
1920
+ fd , tmp_aaf_path = tempfile .mkstemp (suffix = '.aaf' )
1921
+ otio .adapters .write_to_file (otio_timeline , tmp_aaf_path )
1922
+
1923
+ self ._verify_aaf (tmp_aaf_path )
1924
+
1925
+ for frame , rate in [(100 , 12.97 ),
1926
+ (100 , 3.0 ),
1927
+ (100 , 26.5 ),
1928
+ (100 , 31 ),
1929
+ (100 , 45 ),
1930
+ (100 , 120.0 ),
1931
+ (100 , 90.0 )]:
1932
+
1933
+ otio_timeline = otio .schema .Timeline ()
1934
+ otio_timeline .global_start_time = otio .opentime .RationalTime (frame , rate )
1935
+ fd , tmp_aaf_path = tempfile .mkstemp (suffix = '.aaf' )
1936
+ otio .adapters .write_to_file (otio_timeline , tmp_aaf_path )
1937
+
1938
+ self ._verify_aaf (tmp_aaf_path )
1939
+
1872
1940
def _verify_aaf (self , aaf_path ):
1873
1941
otio_timeline = otio .adapters .read_from_file (aaf_path , simplify = True )
1874
1942
fd , tmp_aaf_path = tempfile .mkstemp (suffix = '.aaf' )
@@ -1884,7 +1952,9 @@ def _verify_aaf(self, aaf_path):
1884
1952
compositionmobs = list (dest .content .compositionmobs ())
1885
1953
self .assertEqual (1 , len (compositionmobs ))
1886
1954
compositionmob = compositionmobs [0 ]
1887
- self .assertEqual (len (otio_timeline .tracks ), len (compositionmob .slots ))
1955
+
1956
+ # + 1 is for the timecode track
1957
+ self .assertEqual (len (otio_timeline .tracks ) + 1 , len (compositionmob .slots ))
1888
1958
1889
1959
for otio_track , aaf_timeline_mobslot in zip (otio_timeline .tracks ,
1890
1960
compositionmob .slots ):
@@ -1926,7 +1996,8 @@ def _verify_aaf(self, aaf_path):
1926
1996
type_mapping [type (otio_child )])
1927
1997
1928
1998
if isinstance (aaf_component , SourceClip ):
1929
- self ._verify_compositionmob_sourceclip_structure (aaf_component )
1999
+ self ._verify_compositionmob_sourceclip_structure (otio_child ,
2000
+ aaf_component )
1930
2001
1931
2002
if isinstance (aaf_component , aaf2 .components .OperationGroup ):
1932
2003
nested_aaf_segments = aaf_component .segments
@@ -1937,6 +2008,16 @@ def _verify_aaf(self, aaf_path):
1937
2008
else :
1938
2009
self ._is_otio_aaf_same (otio_child , aaf_component )
1939
2010
2011
+ # check the global_start_time and timecode slot
2012
+ for slot in compositionmob .slots :
2013
+ if isinstance (slot .segment , Timecode ):
2014
+ self .assertEqual (otio_timeline .global_start_time .rate ,
2015
+ float (slot .edit_rate ))
2016
+ self .assertEqual (otio_timeline .global_start_time .value ,
2017
+ slot .segment .start )
2018
+ self .assertTrue (slot .segment .fps in [24 , 25 , 30 , 60 ])
2019
+ self .assertTrue (slot ['PhysicalTrackNumber' ].value == 1 )
2020
+
1940
2021
# Inspect the OTIO -> AAF -> OTIO file
1941
2022
roundtripped_otio = otio .adapters .read_from_file (tmp_aaf_path , simplify = True )
1942
2023
@@ -1946,7 +2027,7 @@ def _verify_aaf(self, aaf_path):
1946
2027
self .assertEqual (otio_timeline .duration ().rate ,
1947
2028
roundtripped_otio .duration ().rate )
1948
2029
1949
- def _verify_compositionmob_sourceclip_structure (self , compmob_clip ):
2030
+ def _verify_compositionmob_sourceclip_structure (self , otio_child , compmob_clip ):
1950
2031
self .assertTrue (isinstance (compmob_clip , SourceClip ))
1951
2032
self .assertTrue (isinstance (compmob_clip .mob , MasterMob ))
1952
2033
mastermob = compmob_clip .mob
@@ -1956,6 +2037,12 @@ def _verify_compositionmob_sourceclip_structure(self, compmob_clip):
1956
2037
self .assertTrue (isinstance (mastermob_clip .mob , SourceMob ))
1957
2038
filemob = mastermob_clip .mob
1958
2039
2040
+ if (otio_child .media_reference ):
2041
+ self .assertEqual (len (filemob .descriptor ['Locator' ]), 1 )
2042
+ locator = filemob .descriptor ['Locator' ].value [0 ]
2043
+ self .assertEqual (locator ['URLString' ].value ,
2044
+ otio_child .media_reference .target_url )
2045
+
1959
2046
self .assertEqual (1 , len (filemob .slots ))
1960
2047
filemob_clip = filemob .slots [0 ].segment
1961
2048
@@ -1964,6 +2051,12 @@ def _verify_compositionmob_sourceclip_structure(self, compmob_clip):
1964
2051
tapemob = filemob_clip .mob
1965
2052
self .assertTrue (len (tapemob .slots ) >= 2 )
1966
2053
2054
+ if (otio_child .media_reference ):
2055
+ self .assertEqual (len (tapemob .descriptor ['Locator' ]), 1 )
2056
+ locator = tapemob .descriptor ['Locator' ].value [0 ]
2057
+ self .assertEqual (locator ['URLString' ].value ,
2058
+ otio_child .media_reference .target_url )
2059
+
1967
2060
timecode_slots = [tape_slot for tape_slot in tapemob .slots
1968
2061
if isinstance (tape_slot .segment ,
1969
2062
Timecode )]
0 commit comments