#!/usr/bin/python import sys import os import ntpath import tempfile import shutil import hashlib import xml.etree.ElementTree as ET if len(sys.argv) < 2: print('Syntax: %s ' % sys.argv[1]) sys.exit(1) metadata_xml = os.path.join(sys.argv[1], 'metadata.xml'); tree = ET.parse(metadata_xml) def digest_head_tail(filename, size=1000000): m = hashlib.md5() f = open(filename, 'rb') m.update(f.read(size)) f.seek(size, 2) m.update(f.read(size)) f.close() return m.hexdigest() + str(os.path.getsize(filename)) def command(c): print(f'*** {c}') os.system(c) try: os.makedirs(os.path.join(sys.argv[1], 'dummy')) except: pass root = tree.getroot() for c in root.find('Playlist').findall('Content'): type = c.find('Type').text if type == 'DCP': # Find the ASSETMAP assetmap_file = None for p in c.findall('Path'): if os.path.basename(p.text) == 'ASSETMAP': interop = True elif os.path.basename(p.text) == 'ASSETMAP.xml': interop = False assetmap_file = p.text assert(assetmap_file is not None) dir = os.path.dirname(assetmap_file) assets = {} assetmap = ET.parse(assetmap_file) ns = {'am': 'http://www.digicine.com/PROTO-ASDCP-AM-20040311#'} if interop else {'am': 'http://www.smpte-ra.org/schemas/429-9/2007/AM'} for a in assetmap.getroot().find('am:AssetList', ns).findall('am:Asset', ns): assets[a.find('am:Id', ns).text[9:]] = a.find('am:ChunkList', ns).find('am:Chunk', ns).find('am:Path', ns).text cpl_id = None cpl_ns = 'http://www.digicine.com/PROTO-ASDCP-CPL-20040511#}' if interop else 'http://www.smpte-ra.org/schemas/429-7/2006/CPL' for k, v in assets.items(): try: e = ET.parse(os.path.join(dir, v)) print(e.getroot().tag) if e.getroot().tag == "{" + cpl_ns + "}CompositionPlaylist": cpl_id = k except: pass assert(cpl_id is not None) cpl = ET.parse(os.path.join(dir, assets[cpl_id])) ns = {'cpl': cpl_ns} for r in cpl.find('cpl:ReelList', ns).findall('cpl:Reel', ns): for a in r.find('cpl:AssetList', ns).iter(): if a.tag == '{%s}MainPicture' % ns['cpl']: id = a.find('cpl:Id', ns).text[9:] duration = int(a.find('cpl:IntrinsicDuration', ns).text) black_png = tempfile.NamedTemporaryFile('wb', suffix='.png') black_j2cs = [] black_j2c = tempfile.NamedTemporaryFile('wb', suffix='.j2c') os.system('convert -size 1998x1080 xc:black %s' % black_png.name) os.system('opj_compress -i %s -o %s' % (black_png.name, black_j2c.name)) j2c_dir = tempfile.mkdtemp() for i in range(0, duration): try: os.link(black_j2c.name, os.path.join(j2c_dir, '%06d.j2c' % i)) except OSError as e: if e.errno == 31: # Too many links black_j2cs.append(black_j2c) black_j2c = tempfile.NamedTemporaryFile('wb', suffix='.j2c') os.link(black_j2c.name, os.path.join(j2c_dir, '%06d.j2c' % i)) else: raise print('=> wrap it') os.system('asdcp-wrap -a %s %s %s' % (id, j2c_dir, os.path.join(sys.argv[1], 'dummy', assets[id]))) elif a.tag == '{%s}MainSound' % ns['cpl']: wav = tempfile.NamedTemporaryFile('wb', suffix='.wav') id = a.find('cpl:Id', ns).text[9:] duration = int(a.find('cpl:IntrinsicDuration', ns).text) edit_rate = int(a.find('cpl:EditRate', ns).text.split()[0]) os.system('sox -n -r 48000 -c 6 %s trim 0.0 %f' % (wav.name, float(duration) / edit_rate)) os.system('asdcp-wrap -a %s %s %s' % (id, wav.name, os.path.join(sys.argv[1], 'dummy', assets[id]))) elif type == 'Sndfile': audio_frame_rate = int(c.find('AudioFrameRate').text) channels = int(c.find('AudioMapping').find('InputChannels').text) path = os.path.join(sys.argv[1], 'dummy', ntpath.basename(c.find('Path').text)) audio_length = int(c.find('AudioLength').text) os.system('sox -n -r %d -c %d %s trim 0.0 %f' % (audio_frame_rate, channels, path, float(audio_length) / audio_frame_rate)) elif type == 'FFmpeg': video_cmd = '' audio_cmd = '' path = os.path.join(sys.argv[1], 'dummy', ntpath.basename(c.find('Path').text)) if c.find('AudioStream') is not None: audio_channels = int(c.find('AudioStream').find('Mapping').find('InputChannels').text) audio_frame_rate = int(c.find('AudioStream').find('FrameRate').text) audio_length = int(c.find('AudioStream').find('Length').text) names = { 1: 'mono', 2: 'stereo', 3: '3.0', 4: '4.0', 5: '4.1', 6: '5.1', 7: '6.1', 8: '7.1' } os.system(f'ffmpeg -t {float(audio_length) / audio_frame_rate} -f lavfi -i anullsrc=channel_layout={names[audio_channels]}:sample_rate={audio_frame_rate} audio.aac') if c.find('VideoFrameRate') is not None: video_frame_rate = float(c.find('VideoFrameRate').text) video_length = int(c.find('VideoLength').text) video_cmd = '-s qcif -f rawvideo -pix_fmt rgb24 -r %f -i /dev/zero' % video_frame_rate command(f'ffmpeg -t {video_length / video_frame_rate} -s qcif -f rawvideo -pix_fmt rgb24 -r {video_frame_rate} -i /dev/zero -shortest video.mp4') if c.find('AudioStream') is not None and c.find('VideoFrameRate') is not None: command(f'ffmpeg -i video.mp4 -i audio.aac -c copy -shortest "{path}"') elif c.find('VideoFrameRate') is not None: command(f'ffmpeg -i video.mp4 "{path}"') else: command('sox -n -r %d -c %d %s trim 0.0 %f' % (audio_frame_rate, audio_channels, path, float(audio_length) / audio_frame_rate)) try: os.unlink('video.mp4') os.unlink('audio.aac') except: pass c.find('Path').text = path c.find('Digest').text = digest_head_tail(path) elif type == 'Image': width = int(c.find('VideoWidth').text) height = int(c.find('VideoHeight').text) path = os.path.join(sys.argv[1], 'dummy', ntpath.basename(c.find('Path').text)) os.system('convert -size %dx%d xc:black "%s"' % (width, height, path)) else: print('Skipped %s' % type) shutil.move(metadata_xml, metadata_xml + '.bak') r = open(metadata_xml, 'w') r.write(ET.tostring(root).decode('UTF-8')) r.close()