sprocket-systems.coda.sdk 1.3.2__py3-none-any.whl → 2.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
coda/sdk.py DELETED
@@ -1,1646 +0,0 @@
1
- import os
2
- import sys
3
- import json
4
- import subprocess
5
- import requests
6
- import copy
7
- import shutil
8
- import time
9
- import urllib3
10
-
11
- from .tc_tools import time_seconds_to_vid_frames,vid_frames_to_tc, tc_to_time_seconds
12
-
13
- def make_request(func,port,route,payload=None):
14
- url = f'http://localhost:{port}'
15
- if port==38383 and os.getenv('CODA_API_BASE_URL'):
16
- url = os.getenv('CODA_API_BASE_URL')
17
- url += route
18
- #print('request',url,file=sys.stderr)
19
- auth = None
20
- if os.getenv('CODA_API_TOKEN'):
21
- auth= {'Authorization': f"Bearer {os.getenv('CODA_API_TOKEN')}"}
22
-
23
- verify = True
24
- if os.getenv('CODA_API_INSECURE_SKIP_VERIFY'):
25
- urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
26
- verify = False
27
-
28
- ret = func(url,json=payload,headers=auth,verify=verify)
29
- return ret
30
-
31
- def timingInfo(inputs,venue=None,fps=None,ffoa=None,lfoa=None,start_time=None):
32
- if not inputs:
33
- return None
34
- tinfo = {}
35
- if venue:
36
- tinfo['venue'] = venue
37
- else:
38
- tinfo['venue'] = inputs['venue']
39
- if not fps:
40
- tinfo['source_frame_rate'] = inputs['source_frame_rate']
41
- fps = tinfo['source_frame_rate']
42
- else:
43
- tinfo['source_frame_rate'] = fps
44
- if not ffoa:
45
- tinfo['ffoa_timecode'] = inputs['ffoa_timecode']
46
- else:
47
- tinfo['ffoa_timecode'] = ffoa
48
- if not lfoa:
49
- tinfo['lfoa_timecode'] = inputs['lfoa_timecode']
50
- else:
51
- tinfo['lfoa_timecode'] = lfoa
52
- startt=-1
53
- srate=-1
54
- filelen = -1
55
- sources = inputs['sources']
56
- for i in sources:
57
- if len(sources[i]):
58
- if 'programme_timing' in sources[i][0]:
59
- srate = sources[i][0]['sample_rate']
60
- filelen = sources[i][0]['frames'] / srate
61
- startt = sources[i][0]['programme_timing']['audio_programme_start_time_reference'] / srate
62
- elif 'resources' in sources[i][0]:
63
- startt = sources[i][0]['resources'][0]['bext_time_reference']/sources[i][0]['resources'][0]['sample_rate']
64
- srate = sources[i][0]['resources'][0]['sample_rate']
65
- filelen = sources[i][0]['resources'][0]['frames'] / srate
66
- break
67
- tinfo['start_time_sec'] = startt
68
- tinfo['file_duration_sec'] = filelen
69
- tinfo["file_duration"] =""
70
- if fps!="":
71
- tinfo['file_duration'] = vid_frames_to_tc(time_seconds_to_vid_frames(filelen,fps),fps)
72
- tinfo["start_timecode"] =""
73
- if fps!="":
74
- tinfo['start_timecode'] = vid_frames_to_tc(time_seconds_to_vid_frames(startt,fps),fps)
75
- tinfo["end_timecode"] =""
76
- if fps!="":
77
- tinfo['end_timecode'] = vid_frames_to_tc(time_seconds_to_vid_frames(startt+filelen,fps)-1,fps)
78
- tinfo["ffoa_seconds"] = -1
79
- tinfo["lfoa_seconds"] = -1
80
- if fps!="":
81
- tinfo["ffoa_seconds"] = tc_to_time_seconds(tinfo['ffoa_timecode'],fps)
82
- tinfo["lfoa_seconds"] = tc_to_time_seconds(tinfo['lfoa_timecode'],fps)
83
- tinfo['sample_rate'] = srate
84
-
85
- return tinfo
86
-
87
- class CodaPreset(object):
88
-
89
- routes = {
90
- 'groups': 'groups',
91
- 'jobs': 'jobs',
92
- 'workflows': 'workflows',
93
- 'naming':'naming-conventions',
94
- 'dolby': 'presets/encoding/dolby',
95
- 'dts':'presets/encoding/dts' ,
96
- 'loudness':'presets/loudness',
97
- 'timecode':'presets/timecode',
98
- 'super_session':'presets/super-session'
99
- }
100
-
101
- def __init__(self,preset_type,value):
102
- self.preset = preset_type
103
- self.value = value
104
-
105
- def register(self):
106
- # check if name exists and find preset id
107
- assert(self.preset in CodaPreset.routes)
108
- presets = CodaPreset.getPresets(self.preset)
109
- foundid =None
110
- if presets and len(presets)>0:
111
- pf = [ p for p in presets if p['name']==self.value['name'] ]
112
- if len(pf)>0:
113
- assert(len(pf)==1)
114
- if self.preset=='dolby' or self.preset=='dts':
115
- foundid = pf[0]['encoding_preset_id']
116
- elif self.preset == 'loudness':
117
- foundid = pf[0]['loudness_preset_id']
118
- elif self.preset == 'timecode':
119
- foundid = pf[0]['timecode_preset_id']
120
- elif self.preset == 'naming':
121
- foundid = pf[0]['naming_convention_id']
122
- elif self.preset == 'super_session':
123
- foundid = pf[0]['super_session_preset_id']
124
- elif self.preset == 'groups':
125
- foundid = pf[0]['group_id']
126
- if not foundid:
127
- # add preset with that name for the first time
128
- print(f"creating new preset {self.value['name']}",file=sys.stderr)
129
- #ret = requests.post(f'http://localhost:38383/interface/v1/{CodaPreset.routes[self.preset]}',json=self.value)
130
- ret = make_request(requests.post,38383,f'/interface/v1/{CodaPreset.routes[self.preset]}',self.value)
131
- else:
132
- # update found preset
133
- print(f"updating preset {self.value['name']}, id={foundid}",file=sys.stderr)
134
- #ret = requests.put(f'http://localhost:38383/interface/v1/{CodaPreset.routes[self.preset]}/{foundid}',json=self.value)
135
- ret = make_request(requests.put,38383,f'/interface/v1/{CodaPreset.routes[self.preset]}/{foundid}',self.value)
136
- J = ret.json()
137
- return J
138
-
139
- @staticmethod
140
- def getPresets(preset_type):
141
- assert(preset_type in CodaPreset.routes)
142
- #ret = requests.get(f'http://localhost:38383/interface/v1/{CodaPreset.routes[preset_type]}')
143
- ret = make_request(requests.get,38383,f'/interface/v1/{CodaPreset.routes[preset_type]}')
144
- J = ret.json()
145
- if 'error' in J:
146
- return None
147
- return J
148
-
149
- class CodaEssence(object):
150
- def __init__(self,stemformat,stemtype="audio/pm",program="program-1",description=""):
151
- self.payload = {
152
- 'type':stemtype,
153
- 'format': stemformat,
154
- 'resources':[],
155
- 'program':program,
156
- 'description':description
157
- }
158
- self.esstype = None
159
- self.stemtype = stemtype
160
-
161
- def addInterleavedResource(self,file,channel_selection,chans,samps,quant=24,srate=48000):
162
- self.esstype='interleaved'
163
- auth=None
164
- opts=None
165
- F = file
166
- if type(F)==str:
167
- f = F
168
- else:
169
- if 'auth' in F:
170
- auth = F['auth']
171
- if 'opts' in F:
172
- opts = F['opts']
173
- f = F['url']
174
-
175
- res ={
176
- 'bit_depth' : quant,
177
- 'sample_rate' : srate,
178
- 'url':f,
179
- 'channel_count':chans,
180
- 'frames':samps,
181
- 'channel_selection':channel_selection.copy()
182
- }
183
-
184
- if auth is not None:
185
- res['auth'] = auth
186
- if opts is not None:
187
- res['opts'] = opts
188
- for r in res:
189
- self.payload[r] = res[r]
190
- del self.payload['resources']
191
-
192
- def addMultiMonoResources(self,files,samps,quant=24,srate=48000):
193
- self.esstype='multi_mono'
194
- for F in files:
195
- auth=None
196
- opts=None
197
- if type(F)==str:
198
- f = F
199
- else:
200
- if 'auth' in F:
201
- auth = F['auth']
202
- if 'opts' in F:
203
- opts = F['opts']
204
- f = F['url']
205
-
206
- if 'channel_label' in F:
207
- label = F['channel_label']
208
- else:
209
- label =""
210
- chlabels = ['Lsr','Rsr','Lts','Rts','Lss','Rss','Lfe','Ls','Rs','L','C','R']
211
- for ch in chlabels:
212
- if '.'+ch.upper()+'.' in f.upper():
213
- label = ch[0:1].upper()+ch[1:].lower()
214
- if ch=='Lfe':
215
- label = 'LFE'
216
- break
217
-
218
- res ={
219
- 'bit_depth' : quant,
220
- 'sample_rate' : srate,
221
- 'url':f,
222
- 'channel_count':1,
223
- 'frames':samps,
224
- 'channel_label':label,
225
- 'bext_time_reference':0
226
- }
227
- if auth is not None:
228
- res['auth'] = auth
229
- if opts is not None:
230
- res['opts'] = opts
231
- self.payload['resources'] += [res]
232
- return
233
-
234
- def dict(self):
235
- if self.payload['format']!='atmos':
236
- if 'resources' in self.payload:
237
- assert len(self.payload['resources'])==sum([ int(e) for e in self.payload['format'].split('.')])
238
- else:
239
- assert len(self.payload['channel_selection'])==sum([ int(e) for e in self.payload['format'].split('.')])
240
- return self.payload
241
-
242
-
243
- class CodaWorkflow(object):
244
- def __init__(self,name):
245
- self.name = name
246
- self.packages = {}
247
- self.agents = {}
248
- self.processBlocks = {}
249
- self.destinations= {}
250
- self.wfparams= {}
251
-
252
- @staticmethod
253
- def getChannels(fmt):
254
- if fmt=='7.1.4':
255
- return ['L','R','C','LFE','Lss','Rss','Lsr','Rsr','Ltf','Rtf','Ltr','Rtr']
256
- elif fmt=='7.1.2':
257
- return ['L','R','C','LFE','Lss','Rss','Lsr','Rsr','Ltm','Rtm']
258
- elif fmt=='7.1':
259
- return ['L','R','C','LFE','Lss','Rss','Lsr','Rsr']
260
- elif fmt=='5.1':
261
- return ['L','R','C','LFE','Ls','Rs']
262
- elif fmt=='2.0':
263
- return ['L','R']
264
-
265
- def setParameters(self,params):
266
- self.wfparams = params.copy()
267
-
268
- def importFromPreset(self,preset):
269
- if preset and type(preset) is dict:
270
- self.processBlocks = copy.deepcopy(preset['definition']['process_blocks'])
271
- self.packages = copy.deepcopy(preset['definition']['packages'])
272
- self.agents = copy.deepcopy(preset['definition']['agents'])
273
- self.destinations = copy.deepcopy(preset['definition']['destinations'])
274
- if 'name' in preset:
275
- self.name = preset['name']
276
- return 0
277
- else:
278
- wfpresets = CodaPreset.getPresets("workflows")
279
- for J in wfpresets:
280
- if preset and type(preset) is str and J['name']==preset:
281
- self.processBlocks = copy.deepcopy(J['definition']['process_blocks'])
282
- self.packages = copy.deepcopy(J['definition']['packages'])
283
- self.agents = copy.deepcopy(J['definition']['agents'])
284
- self.destinations = copy.deepcopy(J['definition']['destinations'])
285
- self.name = J['name']
286
- print('imported workflow',self.name,'id',J['workflow_id'],file=sys.stderr)
287
- return J['workflow_id']
288
- elif preset and type(preset) is int and J['workflow_id']==preset:
289
- self.processBlocks = copy.deepcopy(J['definition']['process_blocks'])
290
- self.packages = copy.deepcopy(J['definition']['packages'])
291
- self.agents = copy.deepcopy(J['definition']['agents'])
292
- self.destinations = copy.deepcopy(J['definition']['destinations'])
293
- self.name = J['name']
294
- print('imported workflow',self.name,'id',J['workflow_id'],file=sys.stderr)
295
- return J['workflow_id']
296
- return -1
297
-
298
-
299
- def importFromJob(self,jobid,use_mne_definition=False):
300
- print(f'importing workflow from job {jobid}',file=sys.stderr)
301
- #ret = requests.get(f'http://localhost:38383/interface/v1/jobs/{jobid}')
302
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
303
- J = ret.json()
304
- assert(J['status']=='COMPLETED')
305
- if use_mne_definition and 'mne_workflow_definition' in J:
306
- self.processBlocks = copy.deepcopy(J['mne_workflow_definition']['process_blocks'])
307
- self.packages = copy.deepcopy(J['mne_workflow_definition']['packages'])
308
- self.agents = copy.deepcopy(J['mne_workflow_definition']['agents'])
309
- self.destinations = copy.deepcopy(J['mne_workflow_definition']['destinations'])
310
- else:
311
- if use_mne_definition:
312
- print('** WARNING ** Mne workflow definition was not found. using normal workflow',file=sys.stderr)
313
- self.processBlocks = copy.deepcopy(J['workflow_definition']['process_blocks'])
314
- self.packages = copy.deepcopy(J['workflow_definition']['packages'])
315
- self.agents = copy.deepcopy(J['workflow_definition']['agents'])
316
- self.destinations = copy.deepcopy(J['workflow_definition']['destinations'])
317
- return
318
-
319
-
320
- def addProcessBlock(self,name, output_venue="nearfield",loudness=None,timecode=None,input_filter="all_stems"):
321
-
322
- if not loudness:
323
- loudness= {}
324
- if not timecode:
325
- timecode= {}
326
-
327
- if timecode and type(timecode) is str:
328
- presets = CodaPreset.getPresets('timecode')
329
- pf = [ p for p in presets if p['name']==timecode ]
330
- assert(len(pf)==1)
331
- print('found timecode id',pf[0]['timecode_preset_id'],'for',timecode,file=sys.stderr)
332
- timecode = pf[0]['definition']
333
-
334
- if loudness and type(loudness) is str:
335
- presets = CodaPreset.getPresets('loudness')
336
- pf = [ p for p in presets if p['name']==loudness ]
337
- assert(len(pf)==1)
338
- print('found loudness id',pf[0]['loudness_preset_id'],'for',loudness,file=sys.stderr)
339
- loudness = pf[0]['definition']
340
-
341
- if 'tolerances' not in loudness:
342
- loudness['tolerances']= {
343
- "target_program_loudness": [
344
- -0.5,
345
- 0.4
346
- ],
347
- "target_dialog_loudness": [
348
- -0.5,
349
- 0.4
350
- ],
351
- "target_true_peak": [
352
- -0.2,
353
- 0.0
354
- ]
355
- }
356
- pblock = {
357
- "name":name,
358
- "input_filter": input_filter,
359
- "output_settings": {
360
- "loudness": loudness,
361
- "venue":output_venue,
362
- },
363
- "output_essences": {}
364
- }
365
- if timecode:
366
- pblock["output_settings"][ "timecode"]=timecode
367
-
368
- pid = f"my-process-block-{len(self.processBlocks)+1}"
369
- self.processBlocks[pid] = pblock
370
- return
371
-
372
- def addDCPPackage(self,name,process_blocks,ofps="24",double_frame_rate=False,reels=False,naming_convention=None,naming_options=None,package_wide_uuid=False):
373
-
374
- naming_convention_id=None
375
- if naming_convention and type(naming_convention) is str:
376
- presets = CodaPreset.getPresets('naming')
377
- pf = [ p for p in presets if p['name']==naming_convention ]
378
- assert(len(pf)==1)
379
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
380
- naming_convention_id = int(pf[0]['naming_convention_id'])
381
- naming_convention=None
382
- elif naming_convention and type(naming_convention) is dict:
383
- naming_convention_id=None
384
- #else:
385
- elif naming_convention is not None:
386
- naming_convention_id = int(naming_convention)
387
- naming_convention=None
388
-
389
- blist = []
390
- for b in process_blocks:
391
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
392
- if len(block)==0:
393
- print('process block not found',b,file=sys.stderr)
394
- return -1
395
- blist += block
396
- block = self.processBlocks[block[0]]
397
- assert(block['output_settings']['venue']=='theatrical')
398
- if not isinstance(ofps, list):
399
- FPS = [ofps]
400
- else:
401
- FPS = ofps
402
- fmt = ["atmos"]
403
- typ = ["printmaster"]
404
- for F in fmt:
405
- for fps in FPS:
406
- for t in typ:
407
- block['output_essences'][t+'_'+fps+'_'+F]= {
408
- 'audio_format': F,
409
- 'frame_rate': fps,
410
- 'type':t
411
- }
412
- if 'dcp_mxf' not in self.packages:
413
- self.packages['dcp_mxf'] = {}
414
- pid = f"my-dcp-mxf-package-{len(self.packages['dcp_mxf'])+1}"
415
-
416
- self.packages['dcp_mxf'][pid] = {
417
- "name": name,
418
- "double_frame_rate":double_frame_rate,
419
- "process_block_ids":blist,
420
- "include_reel_splitting" : reels,
421
- "include_package_wide_uuid" :package_wide_uuid,
422
- }
423
- if naming_convention_id:
424
- self.packages['dcp_mxf'][pid]['naming_convention_id'] = naming_convention_id
425
- if naming_convention:
426
- self.packages['dcp_mxf'][pid]['naming_convention'] = naming_convention.copy()
427
- if naming_options:
428
- self.packages['dcp_mxf'][pid]["naming_convention_options"] = naming_options
429
-
430
-
431
- def addSuperSessionPackage(self,name,process_blocks,essences,super_session_profile=None,naming_convention=None,naming_options=None,package_wide_uuid=False):
432
-
433
- naming_convention_id=None
434
- if naming_convention and type(naming_convention) is str:
435
- presets = CodaPreset.getPresets('naming')
436
- pf = [ p for p in presets if p['name']==naming_convention ]
437
- assert(len(pf)==1)
438
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
439
- naming_convention_id = int(pf[0]['naming_convention_id'])
440
- naming_convention=None
441
- elif naming_convention and type(naming_convention) is dict:
442
- naming_convention_id=None
443
- #else:
444
- elif naming_convention is not None:
445
- naming_convention_id = int(naming_convention)
446
- naming_convention=None
447
-
448
- super_session_profile_id=None
449
- if super_session_profile and type(super_session_profile) is str:
450
- presets = CodaPreset.getPresets('super_session')
451
- pf = [ p for p in presets if p['name']==super_session_profile ]
452
- assert(len(pf)==1)
453
- print('found super_session_profile id',pf[0]['super_session_preset_id'],'for',super_session_profile,file=sys.stderr)
454
- super_session_profile_id = int(pf[0]['super_session_profile_id'])
455
- super_session_profile=None
456
- elif super_session_profile and type(super_session_profile) is dict:
457
- super_session_profile_id=None
458
- elif super_session_profile:
459
- super_session_profile_id = int(super_session_profile)
460
- super_session_profile=None
461
- else:
462
- assert(super_session_profile is None)
463
- print('populating default session profile')
464
- # populate default profile with all essences from all blocks
465
- super_session_profile_id = None
466
- super_session_profile= {'session_name_template': "{{TITLE}}_{{FRAME_RATE}}", 'tracks':[] }
467
-
468
-
469
- tracks = []
470
- blist = []
471
- venues = []
472
- for idx,b in enumerate(process_blocks):
473
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
474
- if len(block)==0:
475
- print('process block not found',b,file=sys.stderr)
476
- return -1
477
- blist += block
478
- block = self.processBlocks[block[0]]
479
- venues += [ block['output_settings']['venue']]
480
- fps = [essences[0]]
481
- fmt = essences[1][idx][0]
482
- typ = essences[1][idx][1]
483
- ven = block['output_settings']['venue']
484
- for fr in fps:
485
- for F in fmt:
486
- for t in typ:
487
- block['output_essences'][t+'_'+fr+'_'+F]= {
488
- 'audio_format': F,
489
- 'frame_rate': fr,
490
- 'type':t
491
- }
492
- if ven != 'same_as_input':
493
- tracks += [ { 'element': t, 'format':F, 'venue': ven } ]
494
- else:
495
- tracks += [ { 'element': t, 'format':F, 'venue': 'theatrical' } ]
496
- tracks += [ { 'element': t, 'format':F, 'venue': 'nearfield' } ]
497
-
498
- if super_session_profile and len(super_session_profile['tracks'])==0:
499
- T = []
500
- for t in tracks:
501
- if t['element']=='wides' or t['element']=='same_as_input':
502
- stemlist = ['audio/dx','audio/fx','audio/mx','audio/vox','audio/fol','audio/fix']
503
- for k in stemlist:
504
- T += [ { 'element':k , 'format':t['format'], 'venue':t['venue']} ]
505
- elif t['element']=='dme':
506
- stemlist = ['audio/dx','audio/fx','audio/mx']
507
- for k in stemlist:
508
- T += [ { 'element':k , 'format':t['format'], 'venue':t['venue']} ]
509
- elif t['element']=='printmaster':
510
- T += [ { 'element':'audio/pm' , 'format':t['format'], 'venue':t['venue']} ]
511
- else:
512
- T += [t]
513
- super_session_profile['tracks'] = T
514
-
515
- if 'super_session' not in self.packages:
516
- self.packages['super_session'] = {}
517
- pid = f"my-super-session-package-{len(self.packages['super_session'])+1}"
518
-
519
- self.packages['super_session'][pid] = {
520
- "name": name,
521
- "process_block_ids":blist,
522
- "frame_rate" : essences[0],
523
- "include_package_wide_uuid" :package_wide_uuid,
524
- }
525
- if super_session_profile_id:
526
- self.packages['super_session'][pid]['super_session_profile_id'] = super_session_profile_id
527
- if super_session_profile:
528
- self.packages['super_session'][pid]['super_session_profile'] = super_session_profile.copy()
529
- if naming_convention_id:
530
- self.packages['super_session'][pid]['naming_convention_id'] = naming_convention_id
531
- if naming_convention:
532
- self.packages['super_session'][pid]['naming_convention'] = naming_convention.copy()
533
- if naming_options:
534
- self.packages['super_session'][pid]["naming_convention_options"] = naming_options
535
-
536
-
537
- def addMultiMonoReelsPackage(self,name,process_blocks,essences=['same_as_input'],naming_convention=None,naming_options=None,package_wide_uuid=False):
538
-
539
- naming_convention_id=None
540
- if naming_convention and type(naming_convention) is str:
541
- presets = CodaPreset.getPresets('naming')
542
- pf = [ p for p in presets if p['name']==naming_convention ]
543
- assert(len(pf)==1)
544
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
545
- naming_convention_id = int(pf[0]['naming_convention_id'])
546
- naming_convention=None
547
- elif naming_convention and type(naming_convention) is dict:
548
- naming_convention_id=None
549
- #else:
550
- elif naming_convention is not None:
551
- naming_convention_id = int(naming_convention)
552
- naming_convention=None
553
-
554
- blist = []
555
- for b in process_blocks:
556
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
557
- if len(block)==0:
558
- print('process block not found',b,file=sys.stderr)
559
- return -1
560
- blist += block
561
- block = self.processBlocks[block[0]]
562
- assert(block['output_settings']['venue']=='theatrical')
563
- fps = "24"
564
- fmt = essences
565
- typ = ["printmaster"]
566
- for F in fmt:
567
- for t in typ:
568
- block['output_essences'][t+'_'+fps+'_'+F]= {
569
- 'audio_format': F,
570
- 'frame_rate': fps,
571
- 'type':t
572
- }
573
- if 'multi_mono_reels' not in self.packages:
574
- self.packages['multi_mono_reels'] = {}
575
- pid = f"my-multi-mono-reels-package-{len(self.packages['multi_mono_reels'])+1}"
576
-
577
- #if 'same_as_input' in fmt:
578
- #fmt = ['all_from_essence']
579
-
580
- self.packages['multi_mono_reels'][pid] = {
581
- "name": name,
582
- "process_block_ids":blist,
583
- "formats" : fmt,
584
- #"include_package_wide_uuid" :package_wide_uuid,
585
- }
586
- if naming_convention_id:
587
- self.packages['multi_mono_reels'][pid]['naming_convention_id'] = naming_convention_id
588
- if naming_convention:
589
- self.packages['multi_mono_reels'][pid]['naming_convention'] = naming_convention.copy()
590
- if naming_options:
591
- self.packages['multi_mono_reels'][pid]["naming_convention_options"] = naming_options
592
-
593
- def addDolbyEncodePackage(self,name,process_blocks,encode_profile,essences=('same_as_input','same_as_input'),naming_convention=None,naming_options=None,package_wide_uuid=False):
594
-
595
- naming_convention_id=None
596
- if naming_convention and type(naming_convention) is str:
597
- presets = CodaPreset.getPresets('naming')
598
- pf = [ p for p in presets if p['name']==naming_convention ]
599
- assert(len(pf)==1)
600
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
601
- naming_convention_id = int(pf[0]['naming_convention_id'])
602
- naming_convention=None
603
- elif naming_convention and type(naming_convention) is dict:
604
- naming_convention_id=None
605
- #else:
606
- elif naming_convention is not None:
607
- naming_convention_id = int(naming_convention)
608
- naming_convention=None
609
-
610
- if type(encode_profile) is str:
611
- presets = CodaPreset.getPresets('dolby')
612
- pf = [ p for p in presets if p['name']==encode_profile and essences[1] in p['formats'] ]
613
- assert(len(pf)==1)
614
- print('found encode profile id',pf[0]['encoding_preset_id'],'for',encode_profile,file=sys.stderr)
615
- encode_profile = pf[0]['encoding_preset_id']
616
- elif type(encode_profile) is dict:
617
- assert(essences[1] in encode_profile['formats'])
618
-
619
- blist = []
620
- for b in process_blocks:
621
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
622
- if len(block)==0:
623
- print('process block not found',b,file=sys.stderr)
624
- return -1
625
- blist += block
626
- block = self.processBlocks[block[0]]
627
- assert(block['output_settings']['venue']=='nearfield')
628
- fps = essences[0]
629
- fmt = essences[1]
630
- typ = "printmaster"
631
- block['output_essences'][typ+'_'+fps+'_'+fmt]= {
632
- 'audio_format': fmt,
633
- 'frame_rate': fps,
634
- 'type':typ
635
- }
636
-
637
- if 'dolby' not in self.packages:
638
- self.packages['dolby'] = {}
639
- if fmt=='atmos':
640
- pid = f"my-dolby-atmos-package-{len(self.packages['dolby'])+1}"
641
- else:
642
- pid = f"my-dolby-package-{len(self.packages['dolby'])+1}"
643
-
644
- #if fmt=='same_as_input':
645
- #fmt = 'all_from_essence'
646
- #if fps=='same_as_input':
647
- #fps = 'all_from_essence'
648
-
649
- self.packages['dolby'][pid] = {
650
- "name": name,
651
- "process_block_ids":blist,
652
- "format" : fmt,
653
- "frame_rate": fps,
654
- "include_package_wide_uuid" :package_wide_uuid,
655
- }
656
-
657
- if type(encode_profile) is dict:
658
- self.packages['dolby'][pid]["encoding_profile"]=encode_profile
659
- else:
660
- self.packages['dolby'][pid]["encoding_profile_id"]=encode_profile
661
-
662
- if naming_convention_id:
663
- self.packages['dolby'][pid]['naming_convention_id'] = naming_convention_id
664
- if naming_convention:
665
- self.packages['dolby'][pid]['naming_convention'] = naming_convention.copy()
666
- if naming_options:
667
- self.packages['dolby'][pid]["naming_convention_options"] = naming_options
668
-
669
- def addImaxEnhancedEncodePackage(self,name,process_blocks,encode_profile,essences=('same_as_input','same_as_input'),naming_convention=None,naming_options=None,package_wide_uuid=False):
670
-
671
- assert(essences[1] in ['5.1','5.1.4','7.1.4','7.1.5','5.1.1','imax5','imax6','imax12'])
672
- if type(encode_profile) is str:
673
- presets = CodaPreset.getPresets('dts')
674
- pf = [ p for p in presets if p['name']==encode_profile and essences[1] in p['formats'] and 't1cc' in p['definition'] and p['definition']['t1cc']]
675
- assert(len(pf)==1)
676
- print('found encode profile id',pf[0]['encoding_preset_id'],'for',encode_profile,file=sys.stderr)
677
- encode_profile = pf[0]['encoding_preset_id']
678
- else:
679
- assert('t1cc' in encode_profile and encode_profile['t1cc'])
680
-
681
- self.addDtsEncodePackage(name,process_blocks,encode_profile,essences,naming_convention,naming_options)
682
-
683
- def addDtsEncodePackage(self,name,process_blocks,encode_profile,essences=('same_as_input','same_as_input'),naming_convention=None,naming_options=None,package_wide_uuid=False):
684
-
685
- naming_convention_id=None
686
- if naming_convention and type(naming_convention) is str:
687
- presets = CodaPreset.getPresets('naming')
688
- pf = [ p for p in presets if p['name']==naming_convention ]
689
- assert(len(pf)==1)
690
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
691
- naming_convention_id = int(pf[0]['naming_convention_id'])
692
- naming_convention=None
693
- elif naming_convention and type(naming_convention) is dict:
694
- naming_convention_id=None
695
- #else:
696
- elif naming_convention is not None:
697
- naming_convention_id = int(naming_convention)
698
- naming_convention=None
699
-
700
- t1cc= False
701
- if type(encode_profile) is str:
702
- presets = CodaPreset.getPresets('dts')
703
- pf = [ p for p in presets if p['name']==encode_profile and essences[1] in p['formats'] ]
704
- assert(len(pf)==1)
705
- t1cc =('t1cc' in pf[0]['definition'] and pf[0]['definition']['t1cc'])
706
- print('found encode profile id',pf[0]['encoding_preset_id'],'for',encode_profile,file=sys.stderr)
707
- encode_profile = pf[0]['encoding_preset_id']
708
- elif type(encode_profile) is dict:
709
- t1cc =('t1cc' in encode_profile and encode_profile['t1cc'])
710
-
711
- #print('t1cc is',t1cc)
712
-
713
- blist = []
714
- for b in process_blocks:
715
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
716
- if len(block)==0:
717
- print('process block not found',b,file=sys.stderr)
718
- return -1
719
- blist += block
720
- block = self.processBlocks[block[0]]
721
- #if not t1cc:
722
- assert(block['output_settings']['venue']=='nearfield')
723
- #else:
724
- #assert(block['output_settings']['venue']=='theatrical')
725
- fps = essences[0]
726
- fmt = essences[1]
727
- if t1cc and fmt!='same_as_input' and 'imax' not in fmt:
728
- fmt += ';mode=imax_enhanced'
729
- typ = "printmaster"
730
- block['output_essences'][typ+'_'+fps+'_'+fmt.replace(';','_').replace('=','_')]= {
731
- 'audio_format': fmt,
732
- 'frame_rate': fps,
733
- 'type':typ
734
- }
735
-
736
- packtype = 'dts'
737
- if t1cc:
738
- packtype = 'imax_enhanced'
739
-
740
- if packtype not in self.packages:
741
- self.packages[packtype] = {}
742
- pid = f"my-{packtype.replace('_','-')}-package-{len(self.packages[packtype])+1}"
743
-
744
- #if fmt=='same_as_input':
745
- #fmt = 'all_from_essence'
746
- #if fps=='same_as_input':
747
- #fps = 'all_from_essence'
748
-
749
- pfmt = fmt
750
- if fmt=='imax12':
751
- pfmt = '5.1.4;mode=imax_enhanced'
752
- elif fmt=='imax6':
753
- pfmt = '5.1.1;mode=imax_enhanced'
754
- elif fmt=='imax5':
755
- pfmt = '5.1;mode=imax_enhanced'
756
-
757
- self.packages[packtype][pid] = {
758
- "name": name,
759
- "process_block_ids":blist,
760
- "format" : pfmt,
761
- "frame_rate": fps,
762
- "include_package_wide_uuid" :package_wide_uuid,
763
- }
764
- if type(encode_profile) is dict:
765
- self.packages[packtype][pid]["encoding_profile"]=encode_profile
766
- else:
767
- self.packages[packtype][pid]["encoding_profile_id"]=encode_profile
768
-
769
- if naming_convention_id:
770
- self.packages[packtype][pid]['naming_convention_id'] = naming_convention_id
771
- if naming_convention:
772
- self.packages[packtype][pid]['naming_convention'] = naming_convention.copy()
773
- if naming_options:
774
- self.packages[packtype][pid]["naming_convention_options"] = naming_options
775
-
776
-
777
- def addInterleavedPackage(self,name,process_blocks,essences=('same_as_input',['same_as_input'],['same_as_input']),container='wav',streams=None,naming_convention=None,naming_options=None,package_wide_uuid=False):
778
-
779
- naming_convention_id=None
780
- if naming_convention and type(naming_convention) is str:
781
- presets = CodaPreset.getPresets('naming')
782
- pf = [ p for p in presets if p['name']==naming_convention ]
783
- assert(len(pf)==1)
784
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
785
- naming_convention_id = int(pf[0]['naming_convention_id'])
786
- naming_convention=None
787
- elif naming_convention and type(naming_convention) is dict:
788
- naming_convention_id=None
789
- #else:
790
- elif naming_convention is not None:
791
- naming_convention_id = int(naming_convention)
792
- naming_convention=None
793
-
794
- blist = []
795
- for b in process_blocks:
796
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
797
- if len(block)==0:
798
- print('process block not found',b,file=sys.stderr)
799
- return -1
800
- blist += block
801
- block = self.processBlocks[block[0]]
802
- fps = essences[0]
803
- fmt = essences[1]
804
- typ = essences[2]
805
- for F in fmt:
806
- for t in typ:
807
- block['output_essences'][t+'_'+fps+'_'+F]= {
808
- 'audio_format': F,
809
- 'frame_rate': fps,
810
- 'type':t
811
- }
812
-
813
- tag = 'interleaved'
814
- if container=='mov':
815
- tag = 'mov'
816
-
817
- if tag not in self.packages:
818
- self.packages[tag] = {}
819
- pid = f"my-{tag}-package-{len(self.packages[tag])+1}"
820
-
821
- if not streams:
822
- streams = []
823
- for t in sorted(typ):
824
- for f in fmt:
825
- if t == "printmaster":
826
- E = ['audio/pm']
827
- elif t== "dme":
828
- E = ['audio/dx','audio/fx','audio/mx']
829
- else:
830
- continue
831
- for e in E:
832
- for ch in CodaWorkflow.getChannels(f):
833
- streams += [ {'format':f, 'element':e, 'channel':ch } ]
834
-
835
- #if fps=='same_as_input':
836
- #fps = 'all_from_essence'
837
-
838
- self.packages[tag][pid] = {
839
- "name": name,
840
- "frame_rate": fps,
841
- "process_block_ids":blist,
842
- "streams": streams,
843
- "include_package_wide_uuid" :package_wide_uuid,
844
- }
845
- if naming_convention:
846
- self.packages[tag][pid]['naming_convention'] = naming_convention.copy()
847
- if naming_convention_id:
848
- self.packages[tag][pid]['naming_convention_id'] = naming_convention_id
849
- if naming_options:
850
- self.packages[tag][pid]["naming_convention_options"] = naming_options
851
-
852
- return
853
-
854
- def addMultiMonoPackage(self,name,process_blocks,essences=(['same_as_input'],['same_as_input'],['same_as_input']),naming_convention=None,naming_options=None,package_wide_uuid=False,include_pt_session=False):
855
-
856
- naming_convention_id=None
857
- if naming_convention and type(naming_convention) is str:
858
- presets = CodaPreset.getPresets('naming')
859
- pf = [ p for p in presets if p['name']==naming_convention ]
860
- assert(len(pf)==1)
861
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
862
- naming_convention_id = int(pf[0]['naming_convention_id'])
863
- naming_convention=None
864
- elif naming_convention and type(naming_convention) is dict:
865
- naming_convention_id=None
866
- #else:
867
- elif naming_convention is not None:
868
- naming_convention_id = int(naming_convention)
869
- naming_convention=None
870
-
871
- blist = []
872
- venues = []
873
- for b in process_blocks:
874
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
875
- if len(block)==0:
876
- print('process block not found',b,file=sys.stderr)
877
- return -1
878
- blist += block
879
- block = self.processBlocks[block[0]]
880
- venues += [ block['output_settings']['venue']]
881
- fps = essences[0]
882
- fmt = essences[1]
883
- typ = essences[2]
884
- for fr in fps:
885
- for F in fmt:
886
- for t in typ:
887
- block['output_essences'][t+'_'+fr+'_'+F]= {
888
- 'audio_format': F,
889
- 'frame_rate': fr,
890
- 'type':t
891
- }
892
-
893
- if 'multi_mono' not in self.packages:
894
- self.packages['multi_mono'] = {}
895
- pid = f"my-multi-mono-package-{len(self.packages['multi_mono'])+1}"
896
-
897
- #if 'same_as_input' in fps:
898
- #fps = ['all_from_essence']
899
- #if 'same_as_input' in fmt:
900
- #fmt = ['all_from_essence']
901
- #if 'same_as_input' in typ:
902
- #typ = ['all_from_essence']
903
- #if 'same_as_input' in venues:
904
- #venues = ['all_from_essence']
905
-
906
- self.packages['multi_mono'][pid] = {
907
- "name": name,
908
- "frame_rates": fps,
909
- "formats":fmt,
910
- "elements":typ,
911
- "venues": list(set(venues)),
912
- "process_block_ids":blist,
913
- "include_package_wide_uuid" :package_wide_uuid,
914
- "include_pro_tools_session" :include_pt_session,
915
- }
916
- if naming_convention:
917
- self.packages['multi_mono'][pid]['naming_convention'] = naming_convention.copy()
918
- if naming_convention_id:
919
- self.packages['multi_mono'][pid]['naming_convention_id'] = naming_convention_id
920
- if naming_options:
921
- self.packages['multi_mono'][pid]["naming_convention_options"] = naming_options
922
-
923
- return
924
-
925
- def addAdmPackage(self,name,process_blocks,essences=("same_as_input","same_as_input"),naming_convention=None,naming_options=None,package_wide_uuid=False):
926
-
927
- naming_convention_id=None
928
- if naming_convention and type(naming_convention) is str:
929
- presets = CodaPreset.getPresets('naming')
930
- pf = [ p for p in presets if p['name']==naming_convention ]
931
- assert(len(pf)==1)
932
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
933
- naming_convention_id = int(pf[0]['naming_convention_id'])
934
- naming_convention=None
935
- elif naming_convention and type(naming_convention) is dict:
936
- naming_convention_id=None
937
- #else:
938
- elif naming_convention is not None:
939
- naming_convention_id = int(naming_convention)
940
- naming_convention=None
941
-
942
- blist = []
943
- venues = []
944
- assert(len(process_blocks)==1)
945
- for b in process_blocks:
946
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
947
- if len(block)==0:
948
- print('process block not found',b,file=sys.stderr)
949
- return -1
950
- blist += block
951
- block = self.processBlocks[block[0]]
952
- venues += [ block['output_settings']['venue']]
953
- fps = essences[0]
954
- fmt = "atmos"
955
- typ = essences[1]
956
- block['output_essences'][typ+'_'+fps+'_'+fmt]= {
957
- 'audio_format': fmt,
958
- 'frame_rate': fps,
959
- 'type':typ
960
- }
961
-
962
- if 'adm' not in self.packages:
963
- self.packages['adm'] = {}
964
- pid = f"my-adm-package-{len(self.packages['adm'])+1}"
965
-
966
- #if fps=='same_as_input':
967
- #fps = 'all_from_essence'
968
- #if typ=='same_as_input':
969
- #typ = 'all_from_essence'
970
-
971
- self.packages['adm'][pid] = {
972
- "name": name,
973
- "frame_rate": fps,
974
- "format":fmt,
975
- "element":typ,
976
- "venue": list(set(venues))[0],
977
- "process_block_ids":blist,
978
- "include_package_wide_uuid" :package_wide_uuid,
979
- }
980
- if naming_convention:
981
- self.packages['adm'][pid]['naming_convention'] = naming_convention.copy()
982
- if naming_convention_id:
983
- self.packages['adm'][pid]['naming_convention_id'] = naming_convention_id
984
- if naming_options:
985
- self.packages['adm'][pid]["naming_convention_options"] = naming_options
986
-
987
- return
988
-
989
- def addDestination(self,name,url,auth=None, options=None):
990
- self.destinations[name] = {
991
- 'url':url,
992
- 'auth':auth,
993
- 'opts':options
994
- }
995
- return
996
-
997
- def sendPackagesToDestination(self,dest,packageList):
998
- assert dest in self.destinations
999
- plist = []
1000
- for pname in packageList:
1001
- found = False
1002
- for t in self.packages:
1003
- for p in self.packages[t]:
1004
- if self.packages[t][p]['name']==pname:
1005
- plist += [p]
1006
- found=True
1007
- if not found:
1008
- print(f'warning !!! package {pname} not found',file=sys.stderr)
1009
-
1010
- assert(len(plist)>0)
1011
-
1012
- for p in plist:
1013
- if 'package_ids' not in self.destinations[dest]:
1014
- self.destinations[dest]['package_ids'] =[]
1015
- self.destinations[dest]['package_ids'] += [p]
1016
- return
1017
-
1018
- def sendPackagesToAgent(self,client,packageList):
1019
-
1020
- #aid=0
1021
- #if (os.getenv('DATAIO_AGENT_ID')):
1022
- #aid = int(os.getenv('DATAIO_AGENT_ID'))
1023
- #else:
1024
- #try:
1025
- #ret = requests.get("http://localhost:38384/info")
1026
- #J = ret.json()
1027
- #if 'id' in J:
1028
- #print('got src agent from data-io client',J['id'],file=sys.stderr)
1029
- #aid= J['id']
1030
- #except:
1031
- #pass
1032
-
1033
- if type(client)== str:
1034
- if client!='Origin':
1035
- #ret = requests.get('http://localhost:38383/interface/v1/agents')
1036
- ret = make_request(requests.get,38383,'/interface/v1/agents')
1037
- allclients = ret.json()
1038
- C = [ k for k in allclients if k['hostname']==client]
1039
- #for c in C:
1040
- #print(c['hostname'],c['id'],file=sys.stderr)
1041
- assert(len(C)==1)
1042
- aid = C[0]['id']
1043
- print('fetched agent id',client,'->',aid,file=sys.stderr)
1044
- else:
1045
- aid=0
1046
- else:
1047
- aid = int(client)
1048
- #print('sending to agent',aid,file=sys.stderr)
1049
-
1050
- # check if agent already exists or use new
1051
- pid = f"my-agent-{len(self.agents)+1}"
1052
- for a in self.agents:
1053
- if self.agents[a]['agent_id']==aid:
1054
- pid = a
1055
-
1056
- plist = []
1057
- for pname in packageList:
1058
- found = False
1059
- for t in self.packages:
1060
- for p in self.packages[t]:
1061
- if self.packages[t][p]['name']==pname:
1062
- plist += [p]
1063
- found = True
1064
- if not found:
1065
- print(f'warning !!! package {pname} not found',file=sys.stderr)
1066
-
1067
- assert(len(plist)>0)
1068
-
1069
- if pid not in self.agents:
1070
- self.agents[pid] = {
1071
- "agent_id":aid,
1072
- "package_ids": plist
1073
- }
1074
- else:
1075
- self.agents[pid]['package_ids'] += plist
1076
- self.agents[pid]['package_ids'] = list(set(self.agents[pid]['package_ids'])) # enforce unique packages
1077
- return
1078
-
1079
- def getPackageList(self):
1080
- packlist= []
1081
- for t in self.packages:
1082
- for n in self.packages[t]:
1083
- p = copy.deepcopy(self.packages[t][n])
1084
- p['type'] =t
1085
- packlist += [p]
1086
- return packlist
1087
-
1088
- def dict(self):
1089
-
1090
- dests = {}
1091
- for d in self.destinations:
1092
- if len(self.destinations[d]['package_ids'])>0:
1093
- if 's3://' in self.destinations[d]['url']:
1094
- if 's3' not in dests:
1095
- dests['s3']= {}
1096
- dests['s3'][d] = self.destinations[d]
1097
- else:
1098
- dests['s3'][d] = self.destinations[d]
1099
-
1100
- _wfDef = {
1101
- "name":self.name,
1102
- "process_blocks" : copy.deepcopy(self.processBlocks),
1103
- "packages" : copy.deepcopy(self.packages),
1104
- "workflow_parameters": {
1105
- "dme_stem_mapping": {
1106
- #"audio/fx1": "audio/fx;contents=comp",
1107
- "audio/nar": "audio/dx;contents=comp",
1108
- "audio/vox": "audio/mx;contents=comp",
1109
- "audio/fx": "audio/fx;contents=comp",
1110
- #"audio/fx2": "audio/fx;contents=comp",
1111
- #"audio/dx1": "audio/dx;contents=comp",
1112
- "audio/arch": "audio/dx;contents=comp",
1113
- #"audio/dx2": "audio/dx;contents=comp",
1114
- "audio/fffx": "audio/fx;contents=comp",
1115
- #"audio/fol2": "audio/fx;contents=comp",
1116
- "audio/dx": "audio/dx;contents=comp",
1117
- #"audio/mx2": "audio/mx;contents=comp",
1118
- #"audio/fol1": "audio/fx;contents=comp",
1119
- #"audio/mx1": "audio/mx;contents=comp",
1120
- #"audio/fx3": "audio/fx;contents=comp",
1121
- #"audio/fx4": "audio/fx;contents=comp",
1122
- "audio/scr": "audio/mx;contents=comp",
1123
- "audio/adr": "audio/dx;contents=comp",
1124
- #"audio/dxcomp": "audio/dx;contents=comp",
1125
- "audio/sng": "audio/mx;contents=comp",
1126
- #"audio/wla": "audio/fx;contents=comp",
1127
- #"audio/mxcomp": "audio/mx;contents=comp",
1128
- "audio/mnemx": "audio/mx;contents=comp",
1129
- "audio/fol": "audio/fx;contents=comp",
1130
- #"audio/fxcomp": "audio/fx;contents=comp",
1131
- "audio/mx": "audio/mx;contents=comp",
1132
- "audio/pfx": "audio/fx;contents=comp",
1133
- "audio/bg": "audio/fx;contents=comp",
1134
- #"audio/fix4": "audio/fx;contents=comp",
1135
- "audio/audiodescription": "audio/dx;contents=comp",
1136
- #"audio/fix2": "audio/fx;contents=comp",
1137
- "audio/vo": "audio/dx;contents=comp",
1138
- #"audio/fix3": "audio/fx;contents=comp",
1139
- "audio/crd": "audio/fx;contents=comp",
1140
- "audio/fix": "audio/fx;contents=comp",
1141
- #"audio/fix1": "audio/fx;contents=comp",
1142
- "audio/lg": "audio/dx;contents=comp"
1143
- },
1144
- "enable_atmos_renders": [
1145
- "7.1.4",
1146
- "7.1"
1147
- ]
1148
- }
1149
- }
1150
-
1151
- for k in self.wfparams:
1152
- _wfDef['workflow_parameters'][k] = self.wfparams[k]
1153
-
1154
- if self.agents and len(self.agents):
1155
- _wfDef["agents"]= copy.deepcopy(self.agents)
1156
- if dests and len(dests):
1157
- _wfDef["destinations"]= copy.deepcopy(dests)
1158
-
1159
- return _wfDef
1160
-
1161
-
1162
- class CodaJob(object):
1163
-
1164
- def __init__(self,name,input_venue=None,input_time_options=None,sequence=None,output_language=None):
1165
-
1166
- self.programFPS = None
1167
- self.programLFOA = None
1168
- self.programFFOA = None
1169
- self.programStart = None
1170
-
1171
- if input_time_options:
1172
- inputFramerate,ffoa,lfoa,start_time = input_time_options
1173
- if inputFramerate is not None:
1174
- inputFramerate = inputFramerate.upper()
1175
- if start_time is not None:
1176
- if type(start_time) is str:
1177
- """
1178
- start_time = [float(s) for s in start_time.split(':')]
1179
- dt = {
1180
- "23": 24000./1001.,
1181
- "24" : 1./24.,
1182
- "25" : 1./25,
1183
- "29" : 24000./1001. * 1.25,
1184
- "30" :1./30.
1185
- }
1186
- hmsf = [ 3600., 60.,1.0, dt[inputFramerate] ]
1187
- start_time = sum ( [ a[0]*a[1] for a in zip(start_time,hmsf) ])
1188
- """
1189
- start_time =tc_to_time_seconds(start_time,inputFramerate)
1190
- self.programFPS = inputFramerate
1191
- self.programLFOA = lfoa
1192
- self.programFFOA = ffoa
1193
- self.programStart = start_time
1194
-
1195
- self.reference_run = None
1196
- self.programName = name
1197
- self.programVenue = input_venue
1198
- self.sequence = sequence
1199
- self.language = output_language
1200
- self.inputs = { "sources" : {"interleaved" : [], "multi_mono_groups": [] ,"adms":[] ,"iab_mxfs": [] }}
1201
- self.wfDef = None
1202
- self.edits = None
1203
- if not self.sequence:
1204
- self.sequence = ""
1205
- if not self.language:
1206
- self.language= "UND"
1207
-
1208
- self.src_agent=None
1209
- if (os.getenv('DATAIO_AGENT_ID')):
1210
- self.src_agent = int(os.getenv('DATAIO_AGENT_ID'))
1211
- else:
1212
- try:
1213
- #ret = requests.get("http://localhost:38384/api/agent")
1214
- ret = make_request(requests.get,38384,'/api/agent')
1215
- J = ret.json()
1216
- if 'id' in J:
1217
- print('got src agent from data-io client',J['id'],file=sys.stderr)
1218
- self.src_agent= J['id']
1219
- except:
1220
- pass
1221
-
1222
- if (os.getenv('CODA_GROUP_ID')):
1223
- self.groupId = int(os.getenv('CODA_GROUP_ID'))
1224
- else:
1225
- self.groupId = 1
1226
- return
1227
-
1228
- def forceImax5(self):
1229
- fmts = []
1230
- for t in self.inputs['sources']:
1231
- for k in self.inputs['sources'][t]:
1232
- fmts += [ not (k['format']=='5.0' or k['format']=='imax5') ]
1233
- if any(fmts):
1234
- return False
1235
- for t in self.inputs['sources']:
1236
- for k in self.inputs['sources'][t]:
1237
- k['format'] = 'imax5'
1238
- return True
1239
-
1240
- def setInputLanguage(self,lang):
1241
- for t in self.inputs['sources']:
1242
- for k in self.inputs['sources'][t]:
1243
- k['language'] = lang
1244
-
1245
- def setProgramForType(self,typ,prog="program-1"):
1246
- for t in self.inputs['sources']:
1247
- for k in self.inputs['sources'][t]:
1248
- if typ in k['type']:
1249
- k['program'] = prog
1250
- return
1251
-
1252
- def setProgramForFormat(self,fmt,prog="program-1"):
1253
- for t in self.inputs['sources']:
1254
- for k in self.inputs['sources'][t]:
1255
- if fmt == k['format']:
1256
- k['program'] = prog
1257
- return
1258
-
1259
- def setUniqueProgram(self,prog="program-1"):
1260
- for t in self.inputs['sources']:
1261
- for k in self.inputs['sources'][t]:
1262
- k['program'] = prog
1263
- return
1264
-
1265
- def addInputEssences(self,essences):
1266
- if len(essences)==0:
1267
- return
1268
- for e in essences:
1269
- assert(e.esstype)
1270
- self.inputs['sources']['multi_mono_groups'] += [e.dict() for e in essences if isinstance(e,CodaEssence) and e.esstype=='multi_mono']
1271
- self.inputs['sources']['interleaved'] += [e.dict() for e in essences if isinstance(e,CodaEssence) and e.esstype=='interleaved']
1272
- return
1273
-
1274
- def addInputFiles(self,files,file_info=None,program=None,force_fps=None):
1275
- alls3ins = all(['s3://' in f for f in files])
1276
- if not alls3ins and not self.src_agent:
1277
- print('** ERROR !!!! : you need a data-io agent ID to transfer local files. install data-io or export DATAIO_AGENT_ID=<id>',file=sys.stderr)
1278
- return -1 #assert(self.src_agent)
1279
-
1280
- absfiles = [os.path.abspath(f) for f in files if 's3://' not in f] ### make sure to use absolute paths for coda inspect...
1281
-
1282
- if len(absfiles)>0:
1283
- print('coda inspect scanning',len(absfiles),'files',file=sys.stderr)
1284
- codaexe = shutil.which('coda') #'/usr/local/bin/coda'
1285
- if os.getenv('CODA_CLI_EXE'):
1286
- codaexe = os.getenv('CODA_CLI_EXE')
1287
- if not codaexe:
1288
- if not file_info:
1289
- print('ERRROR ! you need coda cli installed to autoscan local files',file=sys.stderr)
1290
- return -1 #assert(codaexe)
1291
- else:
1292
- # manual essence creation from local files
1293
- essences = [ CodaEssence(file_info['format'],stemtype=file_info['type']) ]
1294
- for e in essences:
1295
- res = []
1296
- for r in absfiles:
1297
- res += [
1298
- {
1299
- 'url' : r
1300
- }
1301
- ]
1302
- e.addMultiMonoResources(res,samps=file_info['samps'])
1303
- self.addInputEssences(essences)
1304
- else:
1305
- try:
1306
- print('using coda cli', codaexe,file=sys.stderr)
1307
- try:
1308
- ret = subprocess.run([codaexe] +['checkin'],shell=False,check=True,stdout = subprocess.PIPE)
1309
- except:
1310
- print('ERRROR ! you need coda agent to be running to autoscan local files',file=sys.stderr)
1311
- return -1
1312
-
1313
- fpsflag = []
1314
- if force_fps is not None:
1315
- fpsflag = [ '--frame-rate'] + [ force_fps ]
1316
-
1317
- ret = subprocess.run([codaexe] +['inspect']+ fpsflag + ['-i'] + absfiles,shell=False,check=True,stdout = subprocess.PIPE)
1318
- j = json.loads(ret.stdout)
1319
- for t in j['sources']:
1320
- for g in j['sources'][t]:
1321
- if 'resources' in g:
1322
- g['resources'] = sorted(g['resources'], key=lambda d: d['channel_label']) ## sort file list by channel label for repeatability
1323
- if program is not None:
1324
- for t in j['sources']:
1325
- for g in j['sources'][t]:
1326
- g['program']= program
1327
- if 'multi_mono_groups' in j['sources']:
1328
- for mmgroup in j['sources']['multi_mono_groups']:
1329
- if mmgroup['format']=='7.1.5' or mmgroup['format']=='5.1.1':
1330
- mmgroup['format']+= ';mode=imax_enhanced'
1331
- for t in j['sources']:
1332
- self.inputs['sources'][t] += j['sources'][t]
1333
- self.inputs['ffoa_timecode'] = j['ffoa_timecode']
1334
- self.inputs['lfoa_timecode'] = j['lfoa_timecode']
1335
- self.inputs['source_frame_rate'] = j['source_frame_rate']
1336
- except Exception as e:
1337
- print(str(e),file=sys.stderr)
1338
- return -1
1339
-
1340
- s3files = [f for f in files if 's3://' in f]
1341
-
1342
- if len(s3files)>0:
1343
- print('adding',len(s3files),'s3 files',file=sys.stderr)
1344
- assert(file_info is not None)
1345
-
1346
- # manual essence creation from s3 bucket
1347
- essences = [ CodaEssence(file_info['format'],stemtype=file_info['type']) ]
1348
- for e in essences:
1349
- res = []
1350
- for r in s3files:
1351
- res += [
1352
- {
1353
- 'url' : r,
1354
- 'auth':file_info['s3_auth'],
1355
- 'opts':file_info['s3_options'],
1356
- }
1357
- ]
1358
- e.addMultiMonoResources(res,samps=file_info['samps'])
1359
-
1360
- self.addInputEssences(essences)
1361
-
1362
- return 0
1363
-
1364
- def addEdits(self,edit_payload):
1365
- self.edits = edit_payload
1366
- return
1367
-
1368
- def validate(self, skip_cloud_validation=True):
1369
- # we could check for errors here, like adding a reel package but having no edits, etc...
1370
- if self.wfDef:
1371
- for f in self.wfDef.packages:
1372
- if f=='dcp_mxf':
1373
- for p in self.wfDef.packages[f]:
1374
- if self.wfDef.packages[f][p]['include_reel_splitting']:
1375
- assert(self.edits)
1376
- elif f=='multi_mono_reels' and len(self.wfDef.packages[f])>0:
1377
- assert(self.edits)
1378
-
1379
- J = json.loads(self.json())
1380
-
1381
- assert( 'agents' in J['workflow_definition'] or 'destinations' in J['workflow_definition'] )
1382
- if 'agents' in J['workflow_definition']:
1383
- for a in J['workflow_definition']['agents']:
1384
- assert(len(J['workflow_definition']['agents'][a]['package_ids']) >0)
1385
-
1386
- # check that all multi monos have same bext
1387
- if 'multi_mono_groups' in J['workflow_input']['sources']:
1388
- bext =None
1389
- for t in J['workflow_input']['sources']['multi_mono_groups']:
1390
- for f in t['resources']:
1391
- if bext is None:
1392
- bext = f['bext_time_reference']
1393
- else:
1394
- assert(bext==f['bext_time_reference'])
1395
-
1396
- #ret = requests.post("http://localhost:38383/interface/v1/jobs?validate_only=true&skip_cloud_validation=true",json =J)
1397
- ret = make_request(requests.post,38383,f"/interface/v1/jobs?validate_only=true&skip_cloud_validation={skip_cloud_validation}",J)
1398
- print('validate :: ',ret.json(),file=sys.stderr)
1399
- return ret.json()
1400
-
1401
- def setWorkflow(self,wf):
1402
- self.wfDef = copy.deepcopy(wf)
1403
- return
1404
-
1405
- def useReferenceJob(self,jobid):
1406
- #ret = requests.get(f'http://localhost:38383/interface/v1/jobs/{jobid}')
1407
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
1408
- J = ret.json()
1409
- assert(J['status']=='COMPLETED')
1410
- wid = J['conductor_workflow_instance_id']
1411
- print(f'referencing cache from job {jobid} --> {wid}',file=sys.stderr)
1412
- self.reference_run = jobid
1413
- return
1414
-
1415
- def setGroup(self,gid):
1416
- if type(gid) is str:
1417
- #ret = requests.get(f'http://localhost:38383/interface/v1/groups')
1418
- ret = make_request(requests.get,38383,'/interface/v1/groups')
1419
- J = ret.json()
1420
- if 'error' in J:
1421
- return None
1422
- pf = [ p for p in J if p['name']==gid ]
1423
- assert(len(pf)==1)
1424
- print('found group id',pf[0]['group_id'],'for',gid,file=sys.stderr)
1425
- self.groupId = int(pf[0]['group_id'])
1426
- else:
1427
- self.groupId = int(gid)
1428
- return
1429
-
1430
- @staticmethod
1431
- def run_raw_payload(J):
1432
- CodaJob.validate_raw_payload(J)
1433
- if 'errors' in ret or ('success' in ret and not ret['success']):
1434
- return None
1435
- if 'source_agent_id' in J['workflow_input']:
1436
- assert(J['workflow_input']['source_agent_id']>0)
1437
- print("run raw :: launching job.",file=sys.stderr)
1438
- #ret = requests.post("http://localhost:38383/interface/v1/jobs",json =J)
1439
- ret = make_request(requests.post,38383,'/interface/v1/jobs',J)
1440
- print(ret.json(),file=sys.stderr)
1441
- J = ret.json()
1442
- if 'errors' in J:
1443
- return None
1444
- if 'job_id' not in J:
1445
- return None
1446
- return int(J['job_id'])
1447
-
1448
- @staticmethod
1449
- def validate_raw_payload(J):
1450
- #ret = requests.post("http://localhost:38383/interface/v1/jobs?validate_only=true",json =J)
1451
- ret = make_request(requests.post,38383,"/interface/v1/jobs?validate_only=true",J)
1452
- print('validate raw :: ',ret.json(),file=sys.stderr)
1453
- return ret.json()
1454
-
1455
- def get_edge_payload(self):
1456
- ret = self.validate()
1457
- if 'errors' in ret or ('success' in ret and not ret['success']):
1458
- return None
1459
- J = json.loads(self.json())
1460
- ret = make_request(requests.post,38383,"/interface/v1/jobs/edge",J)
1461
- try:
1462
- J = ret.json()
1463
- except:
1464
- print('get_edge_payload::',ret,file=sys.stderr)
1465
- return None
1466
- if 'errors' in J:
1467
- return None
1468
- return J
1469
-
1470
- def run(self):
1471
- ret = self.validate()
1472
- if 'errors' in ret or ('success' in ret and not ret['success']):
1473
- return None
1474
- J = json.loads(self.json())
1475
- if 'source_agent_id' in J['workflow_input']:
1476
- assert(J['workflow_input']['source_agent_id']>0)
1477
- print("run :: launching job.",file=sys.stderr)
1478
- #ret = requests.post("http://localhost:38383/interface/v1/jobs",json =J)
1479
- ret = make_request(requests.post,38383,"/interface/v1/jobs",J)
1480
- print(ret.json(),file=sys.stderr)
1481
- J = ret.json()
1482
- if 'errors' in J:
1483
- return None
1484
- if 'job_id' not in J:
1485
- return None
1486
- return int(J['job_id'])
1487
-
1488
- @staticmethod
1489
- def getStatus(jobid):
1490
- #print(f'getting status for job {jobid}',file=sys.stderr)
1491
- #ret = requests.get(f"http://localhost:38383/interface/v1/jobs/{jobid}")
1492
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
1493
- J = ret.json()
1494
- #print(J['status'],file=sys.stderr)
1495
- errorcnt=0
1496
- while 'error' in J and errorcnt<3:
1497
- print('error in getstatus::',ret.status_code,J['error'],file=sys.stderr)
1498
- time.sleep(1)
1499
- #ret = requests.get(f"http://localhost:38383/interface/v1/jobs/{jobid}")
1500
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
1501
- J = ret.json()
1502
- errorcnt+=1
1503
- if 'error' in J:
1504
- return None
1505
- return {'status':J['status'],'progress':J['progress']}
1506
-
1507
- @staticmethod
1508
- def getReport(jobid):
1509
- #print(f'getting report for job {jobid}',file=sys.stderr)
1510
- #ret = requests.get(f"http://localhost:38383/interface/v1/report/raw/{jobid}")
1511
- ret = make_request(requests.get,38383,f'/interface/v1/report/raw/{jobid}')
1512
- J = ret.json()
1513
- #if 'error' in J:
1514
- #print(J,file=sys.stderr)
1515
- #return None
1516
- return J
1517
-
1518
-
1519
- def checkInputCompatibility(self,jobid):
1520
- print(f'checking input compatibility against job {jobid}',file=sys.stderr)
1521
- #ret = requests.get(f'http://localhost:38383/interface/v1/jobs/{jobid}')
1522
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
1523
- J = ret.json()
1524
- current = self.getInputTimingInfo()
1525
- remote = timingInfo(J['workflow_input'])
1526
- print('local',current,file=sys.stderr)
1527
- print(f'job {jobid}',remote,file=sys.stderr)
1528
- return current==remote
1529
-
1530
- def getInputTimingInfo(self):
1531
- return timingInfo(self.inputs,self.programVenue,self.programFPS,self.programFFOA,self.programLFOA,self.programStart)
1532
-
1533
- def json(self):
1534
-
1535
- sources = copy.deepcopy(self.inputs['sources'])
1536
- for s in self.inputs['sources']:
1537
- if len(self.inputs['sources'][s])==0:
1538
- del sources[s]
1539
-
1540
- if self.programFPS is None:
1541
- self.programFPS = self.inputs['source_frame_rate']
1542
- else:
1543
- self.inputs['source_frame_rate'] = self.programFPS
1544
- if self.programFFOA is None:
1545
- self.programFFOA = self.inputs['ffoa_timecode']
1546
- else:
1547
- self.inputs['ffoa_timecode'] = self.programFFOA
1548
- if self.programLFOA is None:
1549
- self.programLFOA = self.inputs['lfoa_timecode']
1550
- else:
1551
- self.inputs['lfoa_timecode'] = self.programLFOA
1552
-
1553
- if self.programStart is None:
1554
- for i in sources:
1555
- if len(sources[i]):
1556
- if 'resources' in sources[i][0]:
1557
- self.programStart = sources[i][0]['resources'][0]['bext_time_reference']/sources[i][0]['resources'][0]['sample_rate']
1558
- break
1559
- print('setting prog start from sources',self.programStart,file=sys.stderr)
1560
- else:
1561
- print('punching prog start into sources',self.programStart,file=sys.stderr)
1562
- for i in sources:
1563
- for k in sources[i]:
1564
- if 'resources' in k:
1565
- for r in k['resources']:
1566
- #print(r['url'],r['sample_rate'],int(self.programStart*r['sample_rate']),file=sys.stderr)
1567
- r['bext_time_reference']= int(self.programStart*r['sample_rate'])
1568
-
1569
- if self.programLFOA=="":
1570
- print('invalid LFOA !!!!!',file=sys.stderr)
1571
- if self.programFFOA=="":
1572
- print('invalid FFOA !!!!!',file=sys.stderr)
1573
- if self.programFPS=="":
1574
- print('invalid FPS !!!!!',file=sys.stderr)
1575
-
1576
- #print('prg start',self.programStart,file=sys.stderr)
1577
-
1578
- wfIn = {
1579
- "project": {"title":self.programName, "sequence":self.sequence,"language":self.language,"version":""},
1580
- "source_frame_rate":self.programFPS,
1581
- "venue":self.programVenue,
1582
- "sources": sources,
1583
- #"source_agent_id" : self.src_agent,
1584
- "ffoa_timecode": self.inputs['ffoa_timecode'],
1585
- "lfoa_timecode": self.inputs['lfoa_timecode']
1586
- }
1587
- if self.src_agent:
1588
- wfIn['source_agent_id'] = self.src_agent
1589
- if self.edits:
1590
- wfIn["edits"] = self.edits
1591
-
1592
- wdef = copy.deepcopy(self.wfDef.dict())
1593
- for t in wdef['packages']:
1594
- for p in wdef['packages'][t]:
1595
-
1596
- for k in wdef['packages'][t][p]:
1597
- #print(t,k,'::',wdef['packages'][t][p][k])
1598
- if not ('venue' in k or 'element' in k or 'format' in k or 'frame' in k):
1599
- continue
1600
- if k == 'double_frame_rate':
1601
- continue
1602
- if 'same_as_input' in wdef['packages'][t][p][k]:
1603
- if 'venue' in k:
1604
- if type(wdef['packages'][t][p][k]) is list:
1605
- wdef['packages'][t][p][k] += [self.programVenue]
1606
- wdef['packages'][t][p][k].remove('same_as_input')
1607
- else:
1608
- wdef['packages'][t][p][k] = self.programVenue
1609
- if 'element' in k:
1610
- if type(wdef['packages'][t][p][k]) is list:
1611
- wdef['packages'][t][p][k] = ['all_from_essence']
1612
- else:
1613
- wdef['packages'][t][p][k] = self.programVenue
1614
- if 'format' in k:
1615
- if type(wdef['packages'][t][p][k]) is list:
1616
- wdef['packages'][t][p][k] = ['all_from_essence'] #sources[0][0]['specifications']['audio_format']
1617
- else:
1618
- wdef['packages'][t][p][k] = 'all_from_essence' #sources[0][0]['specifications']['audio_format']
1619
- if 'frame' in k:
1620
- if type(wdef['packages'][t][p][k]) is list:
1621
- wdef['packages'][t][p][k] += [self.programFPS]
1622
- wdef['packages'][t][p][k].remove('same_as_input')
1623
- else:
1624
- wdef['packages'][t][p][k] = self.programFPS
1625
-
1626
- #print(wdef['packages'][t][p])
1627
-
1628
- if 'naming_convention' in wdef['packages'][t][p]:
1629
- if 'package_data' not in wfIn:
1630
- wfIn['package_data'] ={}
1631
- wfIn['package_data'][p] = {'naming_convention':wdef['packages'][t][p]['naming_convention']}
1632
- del wdef['packages'][t][p]['naming_convention']
1633
-
1634
-
1635
- J = {
1636
- "group_id":self.groupId,
1637
- "workflow_input": wfIn,
1638
- "workflow_definition": wdef
1639
- }
1640
-
1641
- if self.reference_run:
1642
- J['parent_job_id'] = self.reference_run
1643
-
1644
- return json.dumps(J,indent=2)
1645
-
1646
-