@@ -56,60 +56,111 @@ def fixUrl(path: str, resolve: bool) -> str:
56
56
def fcpXML (myInput : str , temp : str , output , ffprobe , clips , chunks , tracks : int ,
57
57
sampleRate , audioFile , fps , log ):
58
58
59
- duration = chunks [len (chunks ) - 1 ][1 ]
60
- pathurl = 'file:///' + os .path .abspath (myInput )
59
+ pathurl = 'file://' + os .path .abspath (myInput )
61
60
name = os .path .splitext (os .path .basename (myInput ))[0 ]
62
61
63
- def fraction (inp : float ) -> str :
64
- num , dem = inp .as_integer_ratio ()
65
- if (num == 0 ):
62
+ def fraction (inp , fps ) -> str :
63
+ from fractions import Fraction
64
+
65
+ if (inp == 0 ):
66
66
return '0s'
67
- num *= 1000
68
- dem *= 1000
67
+
68
+ if (isinstance (inp , float )):
69
+ inp = Fraction (inp )
70
+ if (isinstance (fps , float )):
71
+ fps = Fraction (fps )
72
+
73
+
74
+ frac = Fraction (inp , fps ).limit_denominator ()
75
+ num = frac .numerator
76
+ dem = frac .denominator
77
+
78
+ if (dem < 3000 ):
79
+ factor = int (3000 / dem )
80
+ print (3000 / dem )
81
+ print (f'{ num } /{ dem } ' )
82
+ num *= factor
83
+ dem *= factor
84
+ print (f'{ num } /{ dem } ' )
85
+
69
86
return f'{ num } /{ dem } s'
70
87
71
88
if (not audioFile ):
72
89
width , height = ffprobe .getResolution (myInput ).split ('x' )
90
+ total_dur = ffprobe .getDuration (myInput )
91
+ if (total_dur == 'N/A' ):
92
+ total_dur = ffprobe .pipe (['-show_entries' , 'format=duration' , '-of' ,
93
+ 'default=noprint_wrappers=1:nokey=1' , myInput ]).strip ()
73
94
else :
74
95
width , height = '1920' , '1080'
96
+ total_dur = ffprobe .getAudioDuration (myInput )
97
+ total_dur = float (total_dur ) * fps
75
98
76
99
with open (output , 'w' , encoding = 'utf-8' ) as outfile :
100
+
101
+ frame_duration = fraction (1 , fps )
102
+ new_duration = fraction (chunks [len (chunks ) - 1 ][1 ], fps )
103
+
77
104
outfile .write ('<?xml version="1.0" encoding="UTF-8"?>\n ' )
78
105
outfile .write ('<!DOCTYPE fcpxml>\n \n ' )
79
106
outfile .write ('<fcpxml version="1.9">\n ' )
80
107
outfile .write ('\t <resources>\n ' )
81
108
outfile .write (f'\t \t <format id="r1" name="FFVideoFormat{ height } p{ fps } " ' \
82
- f'frameDuration="100/3000s " width="{ width } " height="{ height } "' \
109
+ f'frameDuration="{ frame_duration } " width="{ width } " height="{ height } "' \
83
110
' colorSpace="1-1-1 (Rec. 709)"/>\n ' )
84
111
85
112
outfile .write (f'\t \t <asset id="r2" name="{ name } " start="0s" ' \
86
- 'duration="3816000/90000s" hasVideo="1" format="r1" hasAudio="1" ' \
113
+ 'hasVideo="1" format="r1" hasAudio="1" ' \
87
114
f'audioSources="1" audioChannels="2" audioRate="{ sampleRate } ">\n ' )
88
115
89
116
outfile .write (f'\t \t \t <media-rep kind="original-media" ' \
90
117
f'src="{ pathurl } "></media-rep>\n ' )
91
118
outfile .write ('\t \t </asset>\n ' )
92
119
outfile .write ('\t </resources>\n ' )
93
- outfile .write ('\t <library location="file:///Users/wyattblue/Movies/Untitled.fcpbundle/" >\n ' )
94
- outfile .write ('\t \t <event name="3-16-21 ">\n ' )
120
+ outfile .write ('\t <library>\n ' )
121
+ outfile .write ('\t \t <event name="auto-editor output ">\n ' )
95
122
outfile .write (f'\t \t \t <project name="{ name } ">\n ' )
96
123
outfile .write (formatXML (4 ,
97
- f'<sequence duration="{ fraction ( duration ) } " format="r1" tcStart="0s" tcFormat="NDF" ' \
124
+ f'<sequence duration="{ new_duration } " format="r1" tcStart="0s" tcFormat="NDF" ' \
98
125
'audioLayout="stereo" audioRate="48k">' ,
99
- '\t <spine>' ))
126
+ '\t <spine>' )
127
+ )
128
+
129
+ last_dur = 0
100
130
101
- total = 0
102
131
for j , clip in enumerate (clips ):
103
- total + = (clip [1 ] - clip [0 ])
104
- off = fraction (clip [ 0 ] + total )
132
+ clip_dur = (clip [1 ] - clip [0 ]) / ( clip [ 2 ] / 100 )
133
+ dur = fraction (clip_dur , fps )
105
134
106
- dur = fraction (clip [1 ] - clip [0 ])
107
- if (j == 0 ):
108
- outfile .write (formatXML (6 , f'<asset-clip name="{ name } " offset="{ total } " ref="r2"' \
109
- f' duration="{ dur } " audioRole="dialogue" tcFormat="NDF"/>' ))
135
+ close = '/' if clip [2 ] == 100 else ''
136
+
137
+ if (last_dur == 0 ):
138
+ outfile .write (formatXML (6 , f'<asset-clip name="{ name } " offset="0s" ref="r2"' \
139
+ f' duration="{ dur } " audioRole="dialogue" tcFormat="NDF"{ close } >' ))
110
140
else :
111
- outfile .write (formatXML (6 , f'<asset-clip name="{ name } " offset="{ total } " ref="r2"' \
112
- f' duration="{ dur } " audioRole="dialogue" tcFormat="NDF"/>' ))
141
+ start = fraction (clip [0 ], fps )
142
+ off = fraction (last_dur , fps )
143
+ outfile .write (formatXML (6 ,
144
+ f'<asset-clip name="{ name } " offset="{ off } " ref="r2"' \
145
+ f' duration="{ dur } " start="{ start } " audioRole="dialogue" tcFormat="NDF"{ close } >' ,
146
+ ))
147
+
148
+ if (clip [2 ] != 100 ):
149
+ # See "Time Maps" in developer.apple.com/library/archive/documentation/FinalCutProX/Reference/FinalCutProXXMLFormat/StoryElements/StoryElements.html
150
+
151
+ frac_total = fraction (total_dur , fps )
152
+ total_dur_divided_by_speed = fraction ((total_dur ) / (clip [2 ] / 100 ), fps )
153
+
154
+
155
+ outfile .write (formatXML (6 ,
156
+ '\t <timeMap>' ,
157
+ '\t \t <timept time="0s" value="0s" interp="smooth2"/>' ,
158
+ f'\t \t <timept time="{ total_dur_divided_by_speed } " value="{ frac_total } " interp="smooth2"/>' ,
159
+ '\t </timeMap>' ,
160
+ '</asset-clip>'
161
+ ))
162
+
163
+ last_dur += clip_dur
113
164
114
165
outfile .write ('\t \t \t \t \t </spine>\n ' )
115
166
outfile .write ('\t \t \t \t </sequence>\n ' )
0 commit comments