|
36 | 36 | # python
|
37 | 37 | import sys
|
38 | 38 | import os
|
39 |
| - |
40 |
| -# otio |
41 |
| -import opentimelineio as otio |
| 39 | +import json |
42 | 40 |
|
43 | 41 | # rv import
|
44 | 42 | sys.path += [os.path.join(os.environ["OTIO_RV_PYTHON_LIB"], "rvSession")]
|
45 | 43 | import rvSession # noqa
|
46 | 44 |
|
47 | 45 |
|
| 46 | +_RV_TYPE_MAP = { |
| 47 | + "rvSession.gto.FLOAT": rvSession.gto.FLOAT, |
| 48 | + "rvSession.gto.STRING": rvSession.gto.STRING, |
| 49 | +} |
| 50 | + |
| 51 | + |
| 52 | +# because json.loads returns a unicode type |
| 53 | +_UNICODE_TYPE = type(u"") |
| 54 | + |
| 55 | + |
48 | 56 | def main():
|
49 | 57 | """ entry point, should be called from the rv adapter in otio """
|
50 | 58 |
|
51 | 59 | session_file = rvSession.Session()
|
52 | 60 |
|
53 | 61 | output_fname = sys.argv[1]
|
54 | 62 |
|
55 |
| - # read the input OTIO off stdin |
56 |
| - input_otio = otio.adapters.read_from_string(sys.stdin.read(), 'otio_json') |
| 63 | + simplified_data = _remove_unicode(json.loads(sys.stdin.read())) |
| 64 | + |
| 65 | + result = execute_rv_commands(simplified_data, session_file) |
57 | 66 |
|
58 |
| - result = write_otio(input_otio, session_file) |
59 | 67 | session_file.setViewNode(result)
|
60 | 68 | session_file.write(output_fname)
|
61 | 69 |
|
62 | 70 |
|
63 |
| -# exception class @{ |
64 |
| -class NoMappingForOtioTypeError(otio.exceptions.OTIOError): |
65 |
| - pass |
66 |
| -# @} |
67 |
| - |
68 |
| - |
69 |
| -def write_otio(otio_obj, to_session, track_kind=None): |
70 |
| - WRITE_TYPE_MAP = { |
71 |
| - otio.schema.Timeline: _write_timeline, |
72 |
| - otio.schema.Stack: _write_stack, |
73 |
| - otio.schema.Track: _write_track, |
74 |
| - otio.schema.Clip: _write_item, |
75 |
| - otio.schema.Gap: _write_item, |
76 |
| - otio.schema.Transition: _write_transition, |
77 |
| - otio.schema.SerializableCollection: _write_collection, |
78 |
| - } |
79 |
| - |
80 |
| - if type(otio_obj) in WRITE_TYPE_MAP: |
81 |
| - return WRITE_TYPE_MAP[type(otio_obj)](otio_obj, to_session, track_kind) |
82 |
| - |
83 |
| - raise NoMappingForOtioTypeError( |
84 |
| - str(type(otio_obj)) + " on object: {}".format(otio_obj) |
85 |
| - ) |
86 |
| - |
87 |
| - |
88 |
| -def _write_dissolve(pre_item, in_dissolve, post_item, to_session, track_kind=None): |
89 |
| - rv_trx = to_session.newNode("CrossDissolve", str(in_dissolve.name)) |
90 |
| - |
91 |
| - rate = pre_item.trimmed_range().duration.rate |
92 |
| - rv_trx.setProperty( |
93 |
| - "CrossDissolve", |
94 |
| - "", |
95 |
| - "parameters", |
96 |
| - "startFrame", |
97 |
| - rvSession.gto.FLOAT, |
98 |
| - 1.0 |
99 |
| - ) |
100 |
| - rv_trx.setProperty( |
101 |
| - "CrossDissolve", |
102 |
| - "", |
103 |
| - "parameters", |
104 |
| - "numFrames", |
105 |
| - rvSession.gto.FLOAT, |
106 |
| - int( |
107 |
| - ( |
108 |
| - in_dissolve.in_offset |
109 |
| - + in_dissolve.out_offset |
110 |
| - ).rescaled_to(rate).value |
111 |
| - ) |
112 |
| - ) |
113 |
| - |
114 |
| - rv_trx.setProperty( |
115 |
| - "CrossDissolve", |
116 |
| - "", |
117 |
| - "output", |
118 |
| - "fps", |
119 |
| - rvSession.gto.FLOAT, |
120 |
| - rate |
121 |
| - ) |
122 |
| - |
123 |
| - pre_item_rv = write_otio(pre_item, to_session, track_kind) |
124 |
| - rv_trx.addInput(pre_item_rv) |
125 |
| - |
126 |
| - post_item_rv = write_otio(post_item, to_session, track_kind) |
127 |
| - |
128 |
| - node_to_insert = post_item_rv |
129 |
| - |
130 |
| - if ( |
131 |
| - hasattr(pre_item, "media_reference") |
132 |
| - and pre_item.media_reference |
133 |
| - and pre_item.media_reference.available_range |
134 |
| - and hasattr(post_item, "media_reference") |
135 |
| - and post_item.media_reference |
136 |
| - and post_item.media_reference.available_range |
137 |
| - and ( |
138 |
| - post_item.media_reference.available_range.start_time.rate != |
139 |
| - pre_item.media_reference.available_range.start_time.rate |
140 |
| - ) |
141 |
| - ): |
142 |
| - # write a retime to make sure post_item is in the timebase of pre_item |
143 |
| - rt_node = to_session.newNode("Retime", "transition_retime") |
144 |
| - rt_node.setTargetFps( |
145 |
| - pre_item.media_reference.available_range.start_time.rate |
146 |
| - ) |
147 |
| - |
148 |
| - post_item_rv = write_otio(post_item, to_session, track_kind) |
149 |
| - |
150 |
| - rt_node.addInput(post_item_rv) |
151 |
| - node_to_insert = rt_node |
152 |
| - |
153 |
| - rv_trx.addInput(node_to_insert) |
154 |
| - |
155 |
| - return rv_trx |
156 |
| - |
157 |
| - |
158 |
| -def _write_transition( |
159 |
| - pre_item, |
160 |
| - in_trx, |
161 |
| - post_item, |
162 |
| - to_session, |
163 |
| - track_kind=None |
164 |
| -): |
165 |
| - trx_map = { |
166 |
| - otio.schema.TransitionTypes.SMPTE_Dissolve: _write_dissolve, |
167 |
| - } |
168 |
| - |
169 |
| - if in_trx.transition_type not in trx_map: |
170 |
| - return |
171 |
| - |
172 |
| - return trx_map[in_trx.transition_type]( |
173 |
| - pre_item, |
174 |
| - in_trx, |
175 |
| - post_item, |
176 |
| - to_session, |
177 |
| - track_kind |
178 |
| - ) |
179 |
| - |
180 |
| - |
181 |
| -def _write_stack(in_stack, to_session, track_kind=None): |
182 |
| - new_stack = to_session.newNode("Stack", str(in_stack.name) or "tracks") |
183 |
| - |
184 |
| - for seq in in_stack: |
185 |
| - result = write_otio(seq, to_session, track_kind) |
186 |
| - if result: |
187 |
| - new_stack.addInput(result) |
188 |
| - |
189 |
| - return new_stack |
190 |
| - |
191 |
| - |
192 |
| -def _write_track(in_seq, to_session, _=None): |
193 |
| - new_seq = to_session.newNode("Sequence", str(in_seq.name) or "track") |
194 |
| - |
195 |
| - items_to_serialize = otio.algorithms.track_with_expanded_transitions( |
196 |
| - in_seq |
197 |
| - ) |
198 |
| - |
199 |
| - track_kind = in_seq.kind |
200 |
| - |
201 |
| - for thing in items_to_serialize: |
202 |
| - if isinstance(thing, tuple): |
203 |
| - result = _write_transition(*thing, to_session=to_session, |
204 |
| - track_kind=track_kind) |
205 |
| - elif thing.duration().value == 0: |
206 |
| - continue |
207 |
| - else: |
208 |
| - result = write_otio(thing, to_session, track_kind) |
209 |
| - |
210 |
| - if result: |
211 |
| - new_seq.addInput(result) |
212 |
| - |
213 |
| - return new_seq |
214 |
| - |
215 |
| - |
216 |
| -def _write_timeline(tl, to_session, _=None): |
217 |
| - result = write_otio(tl.tracks, to_session) |
218 |
| - return result |
219 |
| - |
220 |
| - |
221 |
| -def _write_collection(collection, to_session, track_kind=None): |
222 |
| - results = [] |
223 |
| - for item in collection: |
224 |
| - result = write_otio(item, to_session, track_kind) |
225 |
| - if result: |
226 |
| - results.append(result) |
227 |
| - |
228 |
| - if results: |
229 |
| - return results[0] |
230 |
| - |
231 |
| - |
232 |
| -def _create_media_reference(item, src, track_kind=None): |
233 |
| - if hasattr(item, "media_reference") and item.media_reference: |
234 |
| - if isinstance(item.media_reference, otio.schema.ExternalReference): |
235 |
| - media = [str(item.media_reference.target_url)] |
236 |
| - |
237 |
| - if track_kind == otio.schema.TrackKind.Audio: |
238 |
| - # Create blank video media to accompany audio for valid source |
239 |
| - blank = "{},start={},end={},fps={}.movieproc".format( |
240 |
| - "blank", |
241 |
| - item.available_range().start_time.value, |
242 |
| - item.available_range().end_time_inclusive().value, |
243 |
| - item.available_range().duration.rate |
244 |
| - ) |
245 |
| - # Inserting blank media here forces all content to only |
246 |
| - # produce audio. We do it twice in case we look at this in |
247 |
| - # stereo |
248 |
| - media = [blank, blank] + media |
249 |
| - |
250 |
| - src.setMedia(media) |
251 |
| - return True |
252 |
| - |
253 |
| - elif isinstance(item.media_reference, otio.schema.ImageSequenceReference): |
254 |
| - frame_sub = "%0{n}d".format( |
255 |
| - n=item.media_reference.frame_zero_padding |
256 |
| - ) |
257 |
| - |
258 |
| - media = [ |
259 |
| - str(item.media_reference.abstract_target_url(symbol=frame_sub)) |
260 |
| - ] |
261 |
| - |
262 |
| - src.setMedia(media) |
263 |
| - |
264 |
| - return True |
265 |
| - |
266 |
| - elif isinstance(item.media_reference, otio.schema.GeneratorReference): |
267 |
| - if item.media_reference.generator_kind == "SMPTEBars": |
268 |
| - kind = "smptebars" |
269 |
| - src.setMedia( |
270 |
| - [ |
271 |
| - "{},start={},end={},fps={}.movieproc".format( |
272 |
| - kind, |
273 |
| - item.available_range().start_time.value, |
274 |
| - item.available_range().end_time_inclusive().value, |
275 |
| - item.available_range().duration.rate |
276 |
| - ) |
277 |
| - ] |
278 |
| - ) |
279 |
| - return True |
280 |
| - |
281 |
| - return False |
282 |
| - |
283 |
| - |
284 |
| -def _write_item(it, to_session, track_kind=None): |
285 |
| - src = to_session.newNode("Source", str(it.name) or "clip") |
286 |
| - |
287 |
| - if it.metadata: |
288 |
| - src.setProperty( |
289 |
| - "RVSourceGroup", |
290 |
| - "source", |
291 |
| - "otio", |
292 |
| - "metadata", |
293 |
| - rvSession.gto.STRING, |
294 |
| - # Serialize to a string as it seems gto has issues with unicode |
295 |
| - str(otio.core.serialize_json_to_string(it.metadata, indent=-1)) |
296 |
| - ) |
297 |
| - |
298 |
| - range_to_read = it.trimmed_range() |
299 |
| - |
300 |
| - if not range_to_read: |
301 |
| - raise otio.exceptions.OTIOError( |
302 |
| - "No valid range on clip: {0}.".format( |
303 |
| - str(it) |
304 |
| - ) |
305 |
| - ) |
306 |
| - |
307 |
| - in_frame = out_frame = None |
308 |
| - if hasattr(it, "media_reference") and it.media_reference: |
309 |
| - if isinstance(it.media_reference, otio.schema.ImageSequenceReference): |
310 |
| - in_frame, out_frame = it.media_reference.frame_range_for_time_range( |
311 |
| - range_to_read |
312 |
| - ) |
313 |
| - |
314 |
| - if not in_frame and not out_frame: |
315 |
| - # because OTIO has no global concept of FPS, the rate of the duration |
316 |
| - # is used as the rate for the range of the source. |
317 |
| - in_frame = otio.opentime.to_frames( |
318 |
| - range_to_read.start_time, |
319 |
| - rate=range_to_read.duration.rate |
320 |
| - ) |
321 |
| - out_frame = otio.opentime.to_frames( |
322 |
| - range_to_read.end_time_inclusive(), |
323 |
| - rate=range_to_read.duration.rate |
324 |
| - ) |
325 |
| - |
326 |
| - src.setCutIn(in_frame) |
327 |
| - src.setCutOut(out_frame) |
328 |
| - src.setFPS(range_to_read.duration.rate) |
329 |
| - |
330 |
| - # if the media reference is missing |
331 |
| - if not _create_media_reference(it, src, track_kind): |
332 |
| - kind = "smptebars" |
333 |
| - if isinstance(it, otio.schema.Gap): |
334 |
| - kind = "blank" |
335 |
| - src.setMedia( |
336 |
| - [ |
337 |
| - "{},start={},end={},fps={}.movieproc".format( |
338 |
| - kind, |
339 |
| - range_to_read.start_time.value, |
340 |
| - range_to_read.end_time_inclusive().value, |
341 |
| - range_to_read.duration.rate |
342 |
| - ) |
343 |
| - ] |
344 |
| - ) |
345 |
| - |
346 |
| - return src |
| 71 | +def execute_rv_commands(simplified_data, to_session): |
| 72 | + rv_nodes = [] |
| 73 | + for node in simplified_data["nodes"]: |
| 74 | + new_node = to_session.newNode(str(node["kind"]), str(node["name"])) |
| 75 | + rv_node_index = len(rv_nodes) |
| 76 | + |
| 77 | + # make sure that node order lines up |
| 78 | + assert(rv_node_index == node["node_index"]) |
| 79 | + |
| 80 | + rv_nodes.append(new_node) |
| 81 | + node["rv_node"] = new_node |
| 82 | + |
| 83 | + for prop in node["properties"]: |
| 84 | + args = prop |
| 85 | + # the fourth argument is the type |
| 86 | + args[4] = _RV_TYPE_MAP[args[4]] |
| 87 | + |
| 88 | + new_node.setProperty(*args) |
| 89 | + |
| 90 | + for (fn, args) in node["commands"]: |
| 91 | + getattr(new_node, fn)(args) |
| 92 | + |
| 93 | + # inputs done as a second pass now that all nodes are created |
| 94 | + for node in simplified_data["nodes"]: |
| 95 | + for input in node["inputs"]: |
| 96 | + node["rv_node"].addInput(rv_nodes[input]) |
| 97 | + |
| 98 | + # return the first node created. |
| 99 | + return rv_nodes[0] |
| 100 | + |
| 101 | + |
| 102 | +def _remove_unicode(blob): |
| 103 | + if _UNICODE_TYPE == type(blob): |
| 104 | + return blob.encode('utf-8') |
| 105 | + |
| 106 | + if isinstance(blob, dict): |
| 107 | + result = {} |
| 108 | + for key, val in blob.items(): |
| 109 | + result[_remove_unicode(key)] = _remove_unicode(val) |
| 110 | + return result |
| 111 | + |
| 112 | + if isinstance(blob, list): |
| 113 | + return [_remove_unicode(i) for i in blob] |
| 114 | + |
| 115 | + return blob |
347 | 116 |
|
348 | 117 |
|
349 | 118 | if __name__ == "__main__":
|
|
0 commit comments