sprocket-systems.coda.sdk 1.3.3__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,1663 +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.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 fmt=='imax5':
728
- fmt='5.1'
729
- elif fmt=='imax6':
730
- fmt='5.1.1'
731
- elif fmt=='imax12':
732
- fmt='5.1.4'
733
- if t1cc and fmt!='same_as_input' and 'imax' not in fmt:
734
- fmt += ';mode=imax_enhanced'
735
- typ = "printmaster"
736
- block['output_essences'][typ+'_'+fps+'_'+fmt.replace(';','_').replace('=','_')]= {
737
- 'audio_format': fmt,
738
- 'frame_rate': fps,
739
- 'type':typ
740
- }
741
-
742
- packtype = 'dts'
743
- if t1cc:
744
- packtype = 'imax_enhanced'
745
-
746
- if packtype not in self.packages:
747
- self.packages[packtype] = {}
748
- pid = f"my-{packtype.replace('_','-')}-package-{len(self.packages[packtype])+1}"
749
-
750
- #if fmt=='same_as_input':
751
- #fmt = 'all_from_essence'
752
- #if fps=='same_as_input':
753
- #fps = 'all_from_essence'
754
-
755
- pfmt = fmt
756
- if fmt=='imax12':
757
- pfmt = '5.1.4;mode=imax_enhanced'
758
- elif fmt=='imax6':
759
- pfmt = '5.1.1;mode=imax_enhanced'
760
- elif fmt=='imax5':
761
- pfmt = '5.1;mode=imax_enhanced'
762
-
763
- self.packages[packtype][pid] = {
764
- "name": name,
765
- "process_block_ids":blist,
766
- "format" : pfmt,
767
- "frame_rate": fps,
768
- "include_package_wide_uuid" :package_wide_uuid,
769
- }
770
- if type(encode_profile) is dict:
771
- self.packages[packtype][pid]["encoding_profile"]=encode_profile
772
- else:
773
- self.packages[packtype][pid]["encoding_profile_id"]=encode_profile
774
-
775
- if naming_convention_id:
776
- self.packages[packtype][pid]['naming_convention_id'] = naming_convention_id
777
- if naming_convention:
778
- self.packages[packtype][pid]['naming_convention'] = naming_convention.copy()
779
- if naming_options:
780
- self.packages[packtype][pid]["naming_convention_options"] = naming_options
781
-
782
-
783
- 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):
784
-
785
- naming_convention_id=None
786
- if naming_convention and type(naming_convention) is str:
787
- presets = CodaPreset.getPresets('naming')
788
- pf = [ p for p in presets if p['name']==naming_convention ]
789
- assert(len(pf)==1)
790
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
791
- naming_convention_id = int(pf[0]['naming_convention_id'])
792
- naming_convention=None
793
- elif naming_convention and type(naming_convention) is dict:
794
- naming_convention_id=None
795
- #else:
796
- elif naming_convention is not None:
797
- naming_convention_id = int(naming_convention)
798
- naming_convention=None
799
-
800
- blist = []
801
- for b in process_blocks:
802
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
803
- if len(block)==0:
804
- print('process block not found',b,file=sys.stderr)
805
- return -1
806
- blist += block
807
- block = self.processBlocks[block[0]]
808
- fps = essences[0]
809
- fmt = essences[1]
810
- typ = essences[2]
811
- for F in fmt:
812
- for t in typ:
813
- block['output_essences'][t+'_'+fps+'_'+F]= {
814
- 'audio_format': F,
815
- 'frame_rate': fps,
816
- 'type':t
817
- }
818
-
819
- tag = 'interleaved'
820
- if container=='mov':
821
- tag = 'mov'
822
-
823
- if tag not in self.packages:
824
- self.packages[tag] = {}
825
- pid = f"my-{tag}-package-{len(self.packages[tag])+1}"
826
-
827
- if not streams:
828
- streams = []
829
- for t in sorted(typ):
830
- for f in fmt:
831
- if t == "printmaster":
832
- E = ['audio/pm']
833
- elif t== "dme":
834
- E = ['audio/dx','audio/fx','audio/mx']
835
- else:
836
- continue
837
- for e in E:
838
- for ch in CodaWorkflow.getChannels(f):
839
- streams += [ {'format':f, 'element':e, 'channel':ch } ]
840
-
841
- #if fps=='same_as_input':
842
- #fps = 'all_from_essence'
843
-
844
- self.packages[tag][pid] = {
845
- "name": name,
846
- "frame_rate": fps,
847
- "process_block_ids":blist,
848
- "streams": streams,
849
- "include_package_wide_uuid" :package_wide_uuid,
850
- }
851
- if naming_convention:
852
- self.packages[tag][pid]['naming_convention'] = naming_convention.copy()
853
- if naming_convention_id:
854
- self.packages[tag][pid]['naming_convention_id'] = naming_convention_id
855
- if naming_options:
856
- self.packages[tag][pid]["naming_convention_options"] = naming_options
857
-
858
- return
859
-
860
- 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):
861
-
862
- naming_convention_id=None
863
- if naming_convention and type(naming_convention) is str:
864
- presets = CodaPreset.getPresets('naming')
865
- pf = [ p for p in presets if p['name']==naming_convention ]
866
- assert(len(pf)==1)
867
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
868
- naming_convention_id = int(pf[0]['naming_convention_id'])
869
- naming_convention=None
870
- elif naming_convention and type(naming_convention) is dict:
871
- naming_convention_id=None
872
- #else:
873
- elif naming_convention is not None:
874
- naming_convention_id = int(naming_convention)
875
- naming_convention=None
876
-
877
- blist = []
878
- venues = []
879
- for b in process_blocks:
880
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
881
- if len(block)==0:
882
- print('process block not found',b,file=sys.stderr)
883
- return -1
884
- blist += block
885
- block = self.processBlocks[block[0]]
886
- venues += [ block['output_settings']['venue']]
887
- fps = essences[0]
888
- fmt = essences[1]
889
- typ = essences[2]
890
- for fr in fps:
891
- for F in fmt:
892
- for t in typ:
893
- block['output_essences'][t+'_'+fr+'_'+F.replace(';','_').replace('=','_')]= {
894
- 'audio_format': F,
895
- 'frame_rate': fr,
896
- 'type':t
897
- }
898
-
899
- if 'multi_mono' not in self.packages:
900
- self.packages['multi_mono'] = {}
901
- pid = f"my-multi-mono-package-{len(self.packages['multi_mono'])+1}"
902
-
903
- #if 'same_as_input' in fps:
904
- #fps = ['all_from_essence']
905
- #if 'same_as_input' in fmt:
906
- #fmt = ['all_from_essence']
907
- #if 'same_as_input' in typ:
908
- #typ = ['all_from_essence']
909
- #if 'same_as_input' in venues:
910
- #venues = ['all_from_essence']
911
-
912
- self.packages['multi_mono'][pid] = {
913
- "name": name,
914
- "frame_rates": fps,
915
- "formats":fmt,
916
- "elements":typ,
917
- "venues": list(set(venues)),
918
- "process_block_ids":blist,
919
- "include_package_wide_uuid" :package_wide_uuid,
920
- "include_pro_tools_session" :include_pt_session,
921
- }
922
- if naming_convention:
923
- self.packages['multi_mono'][pid]['naming_convention'] = naming_convention.copy()
924
- if naming_convention_id:
925
- self.packages['multi_mono'][pid]['naming_convention_id'] = naming_convention_id
926
- if naming_options:
927
- self.packages['multi_mono'][pid]["naming_convention_options"] = naming_options
928
-
929
- return
930
-
931
- def addAdmPackage(self,name,process_blocks,essences=("same_as_input","same_as_input"),naming_convention=None,naming_options=None,package_wide_uuid=False):
932
-
933
- naming_convention_id=None
934
- if naming_convention and type(naming_convention) is str:
935
- presets = CodaPreset.getPresets('naming')
936
- pf = [ p for p in presets if p['name']==naming_convention ]
937
- assert(len(pf)==1)
938
- print('found naming convention id',pf[0]['naming_convention_id'],'for',naming_convention,file=sys.stderr)
939
- naming_convention_id = int(pf[0]['naming_convention_id'])
940
- naming_convention=None
941
- elif naming_convention and type(naming_convention) is dict:
942
- naming_convention_id=None
943
- #else:
944
- elif naming_convention is not None:
945
- naming_convention_id = int(naming_convention)
946
- naming_convention=None
947
-
948
- blist = []
949
- venues = []
950
- assert(len(process_blocks)==1)
951
- for b in process_blocks:
952
- block = [ B for B in self.processBlocks if self.processBlocks[B]['name']==b]
953
- if len(block)==0:
954
- print('process block not found',b,file=sys.stderr)
955
- return -1
956
- blist += block
957
- block = self.processBlocks[block[0]]
958
- venues += [ block['output_settings']['venue']]
959
- fps = essences[0]
960
- fmt = "atmos"
961
- typ = essences[1]
962
- block['output_essences'][typ+'_'+fps+'_'+fmt]= {
963
- 'audio_format': fmt,
964
- 'frame_rate': fps,
965
- 'type':typ
966
- }
967
-
968
- if 'adm' not in self.packages:
969
- self.packages['adm'] = {}
970
- pid = f"my-adm-package-{len(self.packages['adm'])+1}"
971
-
972
- #if fps=='same_as_input':
973
- #fps = 'all_from_essence'
974
- #if typ=='same_as_input':
975
- #typ = 'all_from_essence'
976
-
977
- self.packages['adm'][pid] = {
978
- "name": name,
979
- "frame_rate": fps,
980
- "format":fmt,
981
- "element":typ,
982
- "venue": list(set(venues))[0],
983
- "process_block_ids":blist,
984
- "include_package_wide_uuid" :package_wide_uuid,
985
- }
986
- if naming_convention:
987
- self.packages['adm'][pid]['naming_convention'] = naming_convention.copy()
988
- if naming_convention_id:
989
- self.packages['adm'][pid]['naming_convention_id'] = naming_convention_id
990
- if naming_options:
991
- self.packages['adm'][pid]["naming_convention_options"] = naming_options
992
-
993
- return
994
-
995
- def addDestination(self,name,url,auth=None, options=None):
996
- self.destinations[name] = {
997
- 'url':url,
998
- 'auth':auth,
999
- 'opts':options
1000
- }
1001
- return
1002
-
1003
- def sendPackagesToDestination(self,dest,packageList):
1004
- assert dest in self.destinations
1005
- plist = []
1006
- for pname in packageList:
1007
- found = False
1008
- for t in self.packages:
1009
- for p in self.packages[t]:
1010
- if self.packages[t][p]['name']==pname:
1011
- plist += [p]
1012
- found=True
1013
- if not found:
1014
- print(f'warning !!! package {pname} not found',file=sys.stderr)
1015
-
1016
- assert(len(plist)>0)
1017
-
1018
- for p in plist:
1019
- if 'package_ids' not in self.destinations[dest]:
1020
- self.destinations[dest]['package_ids'] =[]
1021
- self.destinations[dest]['package_ids'] += [p]
1022
- return
1023
-
1024
- def sendPackagesToAgent(self,client,packageList):
1025
-
1026
- #aid=0
1027
- #if (os.getenv('DATAIO_AGENT_ID')):
1028
- #aid = int(os.getenv('DATAIO_AGENT_ID'))
1029
- #else:
1030
- #try:
1031
- #ret = requests.get("http://localhost:38384/info")
1032
- #J = ret.json()
1033
- #if 'id' in J:
1034
- #print('got src agent from data-io client',J['id'],file=sys.stderr)
1035
- #aid= J['id']
1036
- #except:
1037
- #pass
1038
-
1039
- if type(client)== str:
1040
- if client!='Origin':
1041
- #ret = requests.get('http://localhost:38383/interface/v1/agents')
1042
- ret = make_request(requests.get,38383,'/interface/v1/agents')
1043
- allclients = ret.json()
1044
- C = [ k for k in allclients if k['hostname']==client]
1045
- #for c in C:
1046
- #print(c['hostname'],c['id'],file=sys.stderr)
1047
- assert(len(C)==1)
1048
- aid = C[0]['id']
1049
- print('fetched agent id',client,'->',aid,file=sys.stderr)
1050
- else:
1051
- aid=0
1052
- else:
1053
- aid = int(client)
1054
- #print('sending to agent',aid,file=sys.stderr)
1055
-
1056
- # check if agent already exists or use new
1057
- pid = f"my-agent-{len(self.agents)+1}"
1058
- for a in self.agents:
1059
- if self.agents[a]['agent_id']==aid:
1060
- pid = a
1061
-
1062
- plist = []
1063
- for pname in packageList:
1064
- found = False
1065
- for t in self.packages:
1066
- for p in self.packages[t]:
1067
- if self.packages[t][p]['name']==pname:
1068
- plist += [p]
1069
- found = True
1070
- if not found:
1071
- print(f'warning !!! package {pname} not found',file=sys.stderr)
1072
-
1073
- assert(len(plist)>0)
1074
-
1075
- if pid not in self.agents:
1076
- self.agents[pid] = {
1077
- "agent_id":aid,
1078
- "package_ids": plist
1079
- }
1080
- else:
1081
- self.agents[pid]['package_ids'] += plist
1082
- self.agents[pid]['package_ids'] = list(set(self.agents[pid]['package_ids'])) # enforce unique packages
1083
- return
1084
-
1085
- def getPackageList(self):
1086
- packlist= []
1087
- for t in self.packages:
1088
- for n in self.packages[t]:
1089
- p = copy.deepcopy(self.packages[t][n])
1090
- p['type'] =t
1091
- packlist += [p]
1092
- return packlist
1093
-
1094
- def dict(self):
1095
-
1096
- dests = {}
1097
- for d in self.destinations:
1098
- if len(self.destinations[d]['package_ids'])>0:
1099
- if 's3://' in self.destinations[d]['url']:
1100
- if 's3' not in dests:
1101
- dests['s3']= {}
1102
- dests['s3'][d] = self.destinations[d]
1103
- else:
1104
- dests['s3'][d] = self.destinations[d]
1105
-
1106
- _wfDef = {
1107
- "name":self.name,
1108
- "process_blocks" : copy.deepcopy(self.processBlocks),
1109
- "packages" : copy.deepcopy(self.packages),
1110
- "workflow_parameters": {
1111
- "dme_stem_mapping": {
1112
- #"audio/fx1": "audio/fx;contents=comp",
1113
- "audio/nar": "audio/dx;contents=comp",
1114
- "audio/vox": "audio/mx;contents=comp",
1115
- "audio/fx": "audio/fx;contents=comp",
1116
- #"audio/fx2": "audio/fx;contents=comp",
1117
- #"audio/dx1": "audio/dx;contents=comp",
1118
- "audio/arch": "audio/dx;contents=comp",
1119
- #"audio/dx2": "audio/dx;contents=comp",
1120
- "audio/fffx": "audio/fx;contents=comp",
1121
- #"audio/fol2": "audio/fx;contents=comp",
1122
- "audio/dx": "audio/dx;contents=comp",
1123
- #"audio/mx2": "audio/mx;contents=comp",
1124
- #"audio/fol1": "audio/fx;contents=comp",
1125
- #"audio/mx1": "audio/mx;contents=comp",
1126
- #"audio/fx3": "audio/fx;contents=comp",
1127
- #"audio/fx4": "audio/fx;contents=comp",
1128
- "audio/scr": "audio/mx;contents=comp",
1129
- "audio/adr": "audio/dx;contents=comp",
1130
- #"audio/dxcomp": "audio/dx;contents=comp",
1131
- "audio/sng": "audio/mx;contents=comp",
1132
- #"audio/wla": "audio/fx;contents=comp",
1133
- #"audio/mxcomp": "audio/mx;contents=comp",
1134
- "audio/mnemx": "audio/mx;contents=comp",
1135
- "audio/fol": "audio/fx;contents=comp",
1136
- #"audio/fxcomp": "audio/fx;contents=comp",
1137
- "audio/mx": "audio/mx;contents=comp",
1138
- "audio/pfx": "audio/fx;contents=comp",
1139
- "audio/bg": "audio/fx;contents=comp",
1140
- #"audio/fix4": "audio/fx;contents=comp",
1141
- "audio/audiodescription": "audio/dx;contents=comp",
1142
- #"audio/fix2": "audio/fx;contents=comp",
1143
- "audio/vo": "audio/dx;contents=comp",
1144
- #"audio/fix3": "audio/fx;contents=comp",
1145
- "audio/crd": "audio/fx;contents=comp",
1146
- "audio/fix": "audio/fx;contents=comp",
1147
- #"audio/fix1": "audio/fx;contents=comp",
1148
- "audio/lg": "audio/dx;contents=comp"
1149
- },
1150
- "enable_atmos_renders": [
1151
- "7.1.4",
1152
- "7.1"
1153
- ]
1154
- }
1155
- }
1156
-
1157
- for k in self.wfparams:
1158
- _wfDef['workflow_parameters'][k] = self.wfparams[k]
1159
-
1160
- if self.agents and len(self.agents):
1161
- _wfDef["agents"]= copy.deepcopy(self.agents)
1162
- if dests and len(dests):
1163
- _wfDef["destinations"]= copy.deepcopy(dests)
1164
-
1165
- return _wfDef
1166
-
1167
-
1168
- class CodaJob(object):
1169
-
1170
- def __init__(self,name,input_venue=None,input_time_options=None,sequence=None,output_language=None):
1171
-
1172
- self.programFPS = None
1173
- self.programLFOA = None
1174
- self.programFFOA = None
1175
- self.programStart = None
1176
-
1177
- if input_time_options:
1178
- inputFramerate,ffoa,lfoa,start_time = input_time_options
1179
- if inputFramerate is not None:
1180
- inputFramerate = inputFramerate.upper()
1181
- if start_time is not None:
1182
- if type(start_time) is str:
1183
- """
1184
- start_time = [float(s) for s in start_time.split(':')]
1185
- dt = {
1186
- "23": 24000./1001.,
1187
- "24" : 1./24.,
1188
- "25" : 1./25,
1189
- "29" : 24000./1001. * 1.25,
1190
- "30" :1./30.
1191
- }
1192
- hmsf = [ 3600., 60.,1.0, dt[inputFramerate] ]
1193
- start_time = sum ( [ a[0]*a[1] for a in zip(start_time,hmsf) ])
1194
- """
1195
- start_time =tc_to_time_seconds(start_time,inputFramerate)
1196
- self.programFPS = inputFramerate
1197
- self.programLFOA = lfoa
1198
- self.programFFOA = ffoa
1199
- self.programStart = start_time
1200
-
1201
- self.reference_run = None
1202
- self.programName = name
1203
- self.programVenue = input_venue
1204
- self.sequence = sequence
1205
- self.language = output_language
1206
- self.inputs = { "sources" : {"interleaved" : [], "multi_mono_groups": [] ,"adms":[] ,"iab_mxfs": [] }}
1207
- self.wfDef = None
1208
- self.edits = None
1209
- if not self.sequence:
1210
- self.sequence = ""
1211
- if not self.language:
1212
- self.language= "UND"
1213
-
1214
- self.src_agent=None
1215
- if (os.getenv('DATAIO_AGENT_ID')):
1216
- self.src_agent = int(os.getenv('DATAIO_AGENT_ID'))
1217
- else:
1218
- try:
1219
- #ret = requests.get("http://localhost:38384/api/agent")
1220
- ret = make_request(requests.get,38384,'/api/agent')
1221
- J = ret.json()
1222
- if 'id' in J:
1223
- print('got src agent from data-io client',J['id'],file=sys.stderr)
1224
- self.src_agent= J['id']
1225
- except Exception as E:
1226
- print('error getting agent from data-io client',str(E),file=sys.stderr)
1227
- pass
1228
-
1229
- if (os.getenv('CODA_GROUP_ID')):
1230
- self.groupId = int(os.getenv('CODA_GROUP_ID'))
1231
- else:
1232
- self.groupId = 1
1233
- return
1234
-
1235
- def forceImax5(self):
1236
- fmts = []
1237
- for t in self.inputs['sources']:
1238
- for k in self.inputs['sources'][t]:
1239
- fmts += [ not (k['format']=='5.0' or k['format']=='imax5') ]
1240
- if any(fmts):
1241
- return False
1242
- for t in self.inputs['sources']:
1243
- for k in self.inputs['sources'][t]:
1244
- k['format'] = 'imax5'
1245
- return True
1246
-
1247
- def setInputLanguage(self,lang):
1248
- for t in self.inputs['sources']:
1249
- for k in self.inputs['sources'][t]:
1250
- k['language'] = lang
1251
-
1252
- def setProgramForType(self,typ,prog="program-1"):
1253
- for t in self.inputs['sources']:
1254
- for k in self.inputs['sources'][t]:
1255
- if typ in k['type']:
1256
- k['program'] = prog
1257
- return
1258
-
1259
- def setProgramForFormat(self,fmt,prog="program-1"):
1260
- for t in self.inputs['sources']:
1261
- for k in self.inputs['sources'][t]:
1262
- if fmt == k['format']:
1263
- k['program'] = prog
1264
- return
1265
-
1266
- def setUniqueProgram(self,prog="program-1"):
1267
- for t in self.inputs['sources']:
1268
- for k in self.inputs['sources'][t]:
1269
- k['program'] = prog
1270
- return
1271
-
1272
- def addInputEssences(self,essences):
1273
- if len(essences)==0:
1274
- return
1275
- for e in essences:
1276
- assert(e.esstype)
1277
- self.inputs['sources']['multi_mono_groups'] += [e.dict() for e in essences if isinstance(e,CodaEssence) and e.esstype=='multi_mono']
1278
- self.inputs['sources']['interleaved'] += [e.dict() for e in essences if isinstance(e,CodaEssence) and e.esstype=='interleaved']
1279
- return
1280
-
1281
- def addInputFiles(self,files,file_info=None,program=None,force_fps=None):
1282
- alls3ins = all(['s3://' in f for f in files])
1283
- if not alls3ins and not self.src_agent:
1284
- 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)
1285
- return -1 #assert(self.src_agent)
1286
-
1287
- absfiles = [os.path.abspath(f) for f in files if 's3://' not in f] ### make sure to use absolute paths for coda inspect...
1288
-
1289
- if len(absfiles)>0:
1290
- print('coda inspect scanning',len(absfiles),'files',file=sys.stderr)
1291
- codaexe = shutil.which('coda') #'/usr/local/bin/coda'
1292
- if os.getenv('CODA_CLI_EXE'):
1293
- codaexe = os.getenv('CODA_CLI_EXE')
1294
- if not codaexe:
1295
- if not file_info:
1296
- print('ERRROR ! you need coda cli installed to autoscan local files',file=sys.stderr)
1297
- return -1 #assert(codaexe)
1298
- else:
1299
- # manual essence creation from local files
1300
- essences = [ CodaEssence(file_info['format'],stemtype=file_info['type']) ]
1301
- for e in essences:
1302
- res = []
1303
- for r in absfiles:
1304
- res += [
1305
- {
1306
- 'url' : r
1307
- }
1308
- ]
1309
- e.addMultiMonoResources(res,samps=file_info['samps'])
1310
- self.addInputEssences(essences)
1311
- else:
1312
- try:
1313
- print('using coda cli', codaexe,file=sys.stderr)
1314
- try:
1315
- ret = subprocess.run([codaexe] +['checkin'],shell=False,check=True,stdout = subprocess.PIPE)
1316
- except:
1317
- print('ERRROR ! you need coda agent to be running to autoscan local files',file=sys.stderr)
1318
- return -1
1319
-
1320
- fpsflag = []
1321
- if force_fps is not None:
1322
- fpsflag = [ '--frame-rate'] + [ force_fps ]
1323
-
1324
- cmd = [codaexe] +['inspect']+ fpsflag + ['-i'] + absfiles
1325
-
1326
- #print(' '.join(cmd),file=sys.stderr)
1327
-
1328
- ret = subprocess.run(cmd,shell=False,check=True,stdout = subprocess.PIPE)
1329
- j = json.loads(ret.stdout)
1330
- for t in j['sources']:
1331
- for g in j['sources'][t]:
1332
- if 'resources' in g:
1333
- g['resources'] = sorted(g['resources'], key=lambda d: d['channel_label']) ## sort file list by channel label for repeatability
1334
- if program is not None:
1335
- for t in j['sources']:
1336
- for g in j['sources'][t]:
1337
- g['program']= program
1338
- if 'multi_mono_groups' in j['sources']:
1339
- for mmgroup in j['sources']['multi_mono_groups']:
1340
- if mmgroup['format']=='7.1.5' or mmgroup['format']=='5.1.1':
1341
- mmgroup['format']+= ';mode=imax_enhanced'
1342
- for t in j['sources']:
1343
- self.inputs['sources'][t] += j['sources'][t]
1344
- self.inputs['ffoa_timecode'] = j['ffoa_timecode']
1345
- self.inputs['lfoa_timecode'] = j['lfoa_timecode']
1346
- self.inputs['source_frame_rate'] = j['source_frame_rate']
1347
- except Exception as e:
1348
- print(str(e),file=sys.stderr)
1349
- return -1
1350
-
1351
- s3files = [f for f in files if 's3://' in f]
1352
-
1353
- if len(s3files)>0:
1354
- print('adding',len(s3files),'s3 files',file=sys.stderr)
1355
- assert(file_info is not None)
1356
-
1357
- # manual essence creation from s3 bucket
1358
- essences = [ CodaEssence(file_info['format'],stemtype=file_info['type']) ]
1359
- for e in essences:
1360
- res = []
1361
- for r in s3files:
1362
- res += [
1363
- {
1364
- 'url' : r,
1365
- 'auth':file_info['s3_auth'],
1366
- 'opts':file_info['s3_options'],
1367
- }
1368
- ]
1369
- e.addMultiMonoResources(res,samps=file_info['samps'])
1370
-
1371
- self.addInputEssences(essences)
1372
-
1373
- return 0
1374
-
1375
- def addEdits(self,edit_payload):
1376
- self.edits = edit_payload
1377
- return
1378
-
1379
- def validate(self, skip_cloud_validation=True):
1380
- # we could check for errors here, like adding a reel package but having no edits, etc...
1381
- if self.wfDef:
1382
- for f in self.wfDef.packages:
1383
- if f=='dcp_mxf':
1384
- for p in self.wfDef.packages[f]:
1385
- if self.wfDef.packages[f][p]['include_reel_splitting']:
1386
- assert(self.edits)
1387
- elif f=='multi_mono_reels' and len(self.wfDef.packages[f])>0:
1388
- assert(self.edits)
1389
-
1390
- J = json.loads(self.json())
1391
-
1392
- assert( 'agents' in J['workflow_definition'] or 'destinations' in J['workflow_definition'] )
1393
- if 'agents' in J['workflow_definition']:
1394
- for a in J['workflow_definition']['agents']:
1395
- assert(len(J['workflow_definition']['agents'][a]['package_ids']) >0)
1396
-
1397
- # check that all multi monos have same bext
1398
- if 'multi_mono_groups' in J['workflow_input']['sources']:
1399
- bext =None
1400
- for t in J['workflow_input']['sources']['multi_mono_groups']:
1401
- for f in t['resources']:
1402
- if bext is None:
1403
- bext = f['bext_time_reference']
1404
- else:
1405
- assert(bext==f['bext_time_reference'])
1406
-
1407
- #ret = requests.post("http://localhost:38383/interface/v1/jobs?validate_only=true&skip_cloud_validation=true",json =J)
1408
- ret = make_request(requests.post,38383,f"/interface/v1/jobs?validate_only=true&skip_cloud_validation={skip_cloud_validation}",J)
1409
- print('validate :: ',ret.json(),file=sys.stderr)
1410
- return ret.json()
1411
-
1412
- def setWorkflow(self,wf):
1413
- self.wfDef = copy.deepcopy(wf)
1414
- return
1415
-
1416
- def useReferenceJob(self,jobid):
1417
- #ret = requests.get(f'http://localhost:38383/interface/v1/jobs/{jobid}')
1418
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
1419
- J = ret.json()
1420
- assert(J['status']=='COMPLETED')
1421
- wid = J['conductor_workflow_instance_id']
1422
- print(f'referencing cache from job {jobid} --> {wid}',file=sys.stderr)
1423
- self.reference_run = jobid
1424
- return
1425
-
1426
- def setGroup(self,gid):
1427
- if type(gid) is str:
1428
- #ret = requests.get(f'http://localhost:38383/interface/v1/groups')
1429
- ret = make_request(requests.get,38383,'/interface/v1/groups')
1430
- J = ret.json()
1431
- if 'error' in J:
1432
- return None
1433
- pf = [ p for p in J if p['name']==gid ]
1434
- assert(len(pf)==1)
1435
- print('found group id',pf[0]['group_id'],'for',gid,file=sys.stderr)
1436
- self.groupId = int(pf[0]['group_id'])
1437
- else:
1438
- self.groupId = int(gid)
1439
- return
1440
-
1441
- @staticmethod
1442
- def get_jobs_by_date(start_date,end_date):
1443
- ret = make_request(requests.get,38383,f'/interface/v1/jobs?sort=asc&start_date={start_date}&end_date={end_date}')
1444
- #print(ret.json(),file=sys.stderr)
1445
- return ret.json()
1446
-
1447
- @staticmethod
1448
- def run_raw_payload(J):
1449
- CodaJob.validate_raw_payload(J)
1450
- if 'errors' in ret or ('success' in ret and not ret['success']):
1451
- return None
1452
- if 'source_agent_id' in J['workflow_input']:
1453
- assert(J['workflow_input']['source_agent_id']>0)
1454
- print("run raw :: launching job.",file=sys.stderr)
1455
- #ret = requests.post("http://localhost:38383/interface/v1/jobs",json =J)
1456
- ret = make_request(requests.post,38383,'/interface/v1/jobs',J)
1457
- print(ret.json(),file=sys.stderr)
1458
- J = ret.json()
1459
- if 'errors' in J:
1460
- return None
1461
- if 'job_id' not in J:
1462
- return None
1463
- return int(J['job_id'])
1464
-
1465
- @staticmethod
1466
- def validate_raw_payload(J):
1467
- #ret = requests.post("http://localhost:38383/interface/v1/jobs?validate_only=true",json =J)
1468
- ret = make_request(requests.post,38383,"/interface/v1/jobs?validate_only=true",J)
1469
- print('validate raw :: ',ret.json(),file=sys.stderr)
1470
- return ret.json()
1471
-
1472
- def get_edge_payload(self):
1473
- ret = self.validate()
1474
- if 'errors' in ret or ('success' in ret and not ret['success']):
1475
- return None
1476
- J = json.loads(self.json())
1477
- ret = make_request(requests.post,38383,"/interface/v1/jobs/edge",J)
1478
- try:
1479
- J = ret.json()
1480
- except:
1481
- print('get_edge_payload::',ret,file=sys.stderr)
1482
- return None
1483
- if 'errors' in J:
1484
- return None
1485
- return J
1486
-
1487
- def run(self):
1488
- ret = self.validate()
1489
- if 'errors' in ret or ('success' in ret and not ret['success']):
1490
- return None
1491
- J = json.loads(self.json())
1492
- if 'source_agent_id' in J['workflow_input']:
1493
- assert(J['workflow_input']['source_agent_id']>0)
1494
- print("run :: launching job.",file=sys.stderr)
1495
- #ret = requests.post("http://localhost:38383/interface/v1/jobs",json =J)
1496
- ret = make_request(requests.post,38383,"/interface/v1/jobs",J)
1497
- print(ret.json(),file=sys.stderr)
1498
- J = ret.json()
1499
- if 'errors' in J:
1500
- return None
1501
- if 'job_id' not in J:
1502
- return None
1503
- return int(J['job_id'])
1504
-
1505
- @staticmethod
1506
- def getStatus(jobid):
1507
- #print(f'getting status for job {jobid}',file=sys.stderr)
1508
- #ret = requests.get(f"http://localhost:38383/interface/v1/jobs/{jobid}")
1509
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
1510
- J = ret.json()
1511
- #print(J['status'],file=sys.stderr)
1512
- errorcnt=0
1513
- while 'error' in J and errorcnt<3:
1514
- print('error in getstatus::',ret.status_code,J['error'],file=sys.stderr)
1515
- time.sleep(1)
1516
- #ret = requests.get(f"http://localhost:38383/interface/v1/jobs/{jobid}")
1517
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
1518
- J = ret.json()
1519
- errorcnt+=1
1520
- if 'error' in J:
1521
- return None
1522
- return {'status':J['status'],'progress':J['progress']}
1523
-
1524
- @staticmethod
1525
- def getReport(jobid):
1526
- #print(f'getting report for job {jobid}',file=sys.stderr)
1527
- #ret = requests.get(f"http://localhost:38383/interface/v1/report/raw/{jobid}")
1528
- ret = make_request(requests.get,38383,f'/interface/v1/report/raw/{jobid}')
1529
- J = ret.json()
1530
- #if 'error' in J:
1531
- #print(J,file=sys.stderr)
1532
- #return None
1533
- return J
1534
-
1535
-
1536
- def checkInputCompatibility(self,jobid):
1537
- print(f'checking input compatibility against job {jobid}',file=sys.stderr)
1538
- #ret = requests.get(f'http://localhost:38383/interface/v1/jobs/{jobid}')
1539
- ret = make_request(requests.get,38383,f'/interface/v1/jobs/{jobid}')
1540
- J = ret.json()
1541
- current = self.getInputTimingInfo()
1542
- remote = timingInfo(J['workflow_input'])
1543
- print('local',current,file=sys.stderr)
1544
- print(f'job {jobid}',remote,file=sys.stderr)
1545
- return current==remote
1546
-
1547
- def getInputTimingInfo(self):
1548
- return timingInfo(self.inputs,self.programVenue,self.programFPS,self.programFFOA,self.programLFOA,self.programStart)
1549
-
1550
- def json(self):
1551
-
1552
- sources = copy.deepcopy(self.inputs['sources'])
1553
- for s in self.inputs['sources']:
1554
- if len(self.inputs['sources'][s])==0:
1555
- del sources[s]
1556
-
1557
- if self.programFPS is None:
1558
- self.programFPS = self.inputs['source_frame_rate']
1559
- else:
1560
- self.inputs['source_frame_rate'] = self.programFPS
1561
- if self.programFFOA is None:
1562
- self.programFFOA = self.inputs['ffoa_timecode']
1563
- else:
1564
- self.inputs['ffoa_timecode'] = self.programFFOA
1565
- if self.programLFOA is None:
1566
- self.programLFOA = self.inputs['lfoa_timecode']
1567
- else:
1568
- self.inputs['lfoa_timecode'] = self.programLFOA
1569
-
1570
- if self.programStart is None:
1571
- for i in sources:
1572
- if len(sources[i]):
1573
- if 'resources' in sources[i][0]:
1574
- self.programStart = sources[i][0]['resources'][0]['bext_time_reference']/sources[i][0]['resources'][0]['sample_rate']
1575
- break
1576
- print('setting prog start from sources',self.programStart,file=sys.stderr)
1577
- else:
1578
- print('punching prog start into sources',self.programStart,file=sys.stderr)
1579
- for i in sources:
1580
- for k in sources[i]:
1581
- if 'resources' in k:
1582
- for r in k['resources']:
1583
- #print(r['url'],r['sample_rate'],int(self.programStart*r['sample_rate']),file=sys.stderr)
1584
- r['bext_time_reference']= int(self.programStart*r['sample_rate'])
1585
-
1586
- if self.programLFOA=="":
1587
- print('invalid LFOA !!!!!',file=sys.stderr)
1588
- if self.programFFOA=="":
1589
- print('invalid FFOA !!!!!',file=sys.stderr)
1590
- if self.programFPS=="":
1591
- print('invalid FPS !!!!!',file=sys.stderr)
1592
-
1593
- #print('prg start',self.programStart,file=sys.stderr)
1594
-
1595
- wfIn = {
1596
- "project": {"title":self.programName, "sequence":self.sequence,"language":self.language,"version":""},
1597
- "source_frame_rate":self.programFPS,
1598
- "venue":self.programVenue,
1599
- "sources": sources,
1600
- #"source_agent_id" : self.src_agent,
1601
- "ffoa_timecode": self.inputs['ffoa_timecode'],
1602
- "lfoa_timecode": self.inputs['lfoa_timecode']
1603
- }
1604
- if self.src_agent:
1605
- wfIn['source_agent_id'] = self.src_agent
1606
- if self.edits:
1607
- wfIn["edits"] = self.edits
1608
-
1609
- wdef = copy.deepcopy(self.wfDef.dict())
1610
- for t in wdef['packages']:
1611
- for p in wdef['packages'][t]:
1612
-
1613
- for k in wdef['packages'][t][p]:
1614
- #print(t,k,'::',wdef['packages'][t][p][k])
1615
- if not ('venue' in k or 'element' in k or 'format' in k or 'frame' in k):
1616
- continue
1617
- if k == 'double_frame_rate':
1618
- continue
1619
- if 'same_as_input' in wdef['packages'][t][p][k]:
1620
- if 'venue' in k:
1621
- if type(wdef['packages'][t][p][k]) is list:
1622
- wdef['packages'][t][p][k] += [self.programVenue]
1623
- wdef['packages'][t][p][k].remove('same_as_input')
1624
- else:
1625
- wdef['packages'][t][p][k] = self.programVenue
1626
- if 'element' in k:
1627
- if type(wdef['packages'][t][p][k]) is list:
1628
- wdef['packages'][t][p][k] = ['all_from_essence']
1629
- else:
1630
- wdef['packages'][t][p][k] = self.programVenue
1631
- if 'format' in k:
1632
- if type(wdef['packages'][t][p][k]) is list:
1633
- wdef['packages'][t][p][k] = ['all_from_essence'] #sources[0][0]['specifications']['audio_format']
1634
- else:
1635
- wdef['packages'][t][p][k] = 'all_from_essence' #sources[0][0]['specifications']['audio_format']
1636
- if 'frame' in k:
1637
- if type(wdef['packages'][t][p][k]) is list:
1638
- wdef['packages'][t][p][k] += [self.programFPS]
1639
- wdef['packages'][t][p][k].remove('same_as_input')
1640
- else:
1641
- wdef['packages'][t][p][k] = self.programFPS
1642
-
1643
- #print(wdef['packages'][t][p])
1644
-
1645
- if 'naming_convention' in wdef['packages'][t][p]:
1646
- if 'package_data' not in wfIn:
1647
- wfIn['package_data'] ={}
1648
- wfIn['package_data'][p] = {'naming_convention':wdef['packages'][t][p]['naming_convention']}
1649
- del wdef['packages'][t][p]['naming_convention']
1650
-
1651
-
1652
- J = {
1653
- "group_id":self.groupId,
1654
- "workflow_input": wfIn,
1655
- "workflow_definition": wdef
1656
- }
1657
-
1658
- if self.reference_run:
1659
- J['parent_job_id'] = self.reference_run
1660
-
1661
- return json.dumps(J,indent=2)
1662
-
1663
-