From f1ef0a65ab5b1217f0dd81fe41758c0118a11814 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Sun, 27 Mar 2011 01:41:23 +0100 Subject: CPL and PKL are created properly, AssetMap and VolIndex written but not tested yet. --- mkdcp.py | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 218 insertions(+), 52 deletions(-) diff --git a/mkdcp.py b/mkdcp.py index 4147d30..c1ba72e 100644 --- a/mkdcp.py +++ b/mkdcp.py @@ -1,9 +1,8 @@ #!/usr/bin/env python # -*- coding: utf8 -*- -def generate_SMPTE_CPL(): - -import subprocess, time +import subprocess, time, os, os.path +from xml.sax.saxutils import escape try: from lxml import etree @@ -42,39 +41,128 @@ def asdcp_digest(filename): digest, file = subprocess.Popen(['asdcp-test', '-t', filename], stdout=subprocess.PIPE).stdout.read().strip().split(' ') return digest -class Asset: - def __init__(self): - self.UUID = asdcp_genuuid() +def dcp_digest(string): + import hashlib, base64 + sha1 = hashlib.sha1() + sha1.update(string) + return base64.b64encode(sha1.digest()) - def yield_cpl_SMPTE(self, head_element) - etree.SubElement(_asset, 'Id' ).text = 'urn:uuid:' + asset.UUID - etree.SubElement(_asset, 'AnnotationText' ).text = escape(asset.annotation.encode('ascii', 'xmlcharrefreplace')) - etree.SubElement(_asset, 'Hash' ).text = asset.digest - etree.SubElement(_asset, 'EditRate' ).text = asset.editrate - etree.SubElement(_asset, 'EntryPoint' ).text = asset.entrypoint - etree.SubElement(_asset, 'IntrinsicDuration' ).text = asset.intrisicduration - etree.SubElement(_asset, 'Duration' ).text = asset.duration +def asdcp_readheader(filename): + asdcp = subprocess.Popen(['asdcp-test', '-H', '-i', filename], stdout=subprocess.PIPE) + asdcp_output = asdcp.stdout.readlines() + asdcp.wait() -class CompositionPlayList(Asset): - def __init__(self, title, kind, reellist=None, rating=None): - self.UUID = asdcp_genuuid() - if reellist: - self.reellist = reellist + if asdcp.returncode != 0: # asdcp-test failed - maybe MXF Interop stereoscopic picture + asdcp = subprocess.Popen(['asdcp-test', '-3', '-H', '-i', filename], stdout=subprocess.PIPE) + asdcp_output = asdcp.stdout.readlines() + asdcp.wait() + + if asdcp.returncode != 0: # asdcp-test failed again + return None + + attr_dict = dict() + for field in [attr.strip().split(': ') for attr in asdcp_output if True in [ + e in attr for e in [ + 'StoredWidth:', + 'StoredHeight:', + 'EditRate:', + 'SampleRate:', + 'AudioSamplingRate:', + 'AspectRatio:', + 'Duration:', + 'AssetUUID:', + 'Label Set Type:', + 'audio', + 'pictures' ] + ] + ]: + if len(field) == 2: + attr_dict[field[0]] = field[1] else: - self.reellist = [] + attr_dict['type'] = field[0] + + UUID = attr_dict['AssetUUID'] + + if 'audio' in attr_dict['type']: + asset = SoundTrack() + asset.targetfilename = UUID + '_pcm.mxf' + + if 'pictures' in attr_dict['type']: + asset = PictureTrack(stereoscopic = 'stereoscopic' in attr_dict['type']) + asset.targetfilename = UUID + '_j2c.mxf' + asset.framerate = tuple(map(int,attr_dict['SampleRate'].split('/'))) + asset.width = int(attr_dict['StoredWidth']) + asset.height = int(attr_dict['StoredHeight']) + asset.aspectratio = tuple(map(int, attr_dict['AspectRatio'].split('/'))) + + asset.UUID = UUID + asset.editrate = tuple(map(int,attr_dict['EditRate'].split('/'))) + asset.duration = int(attr_dict['ContainerDuration']) ; asset.intrinsicduration = asset.duration + asset.annotation = "Source file: '" + os.path.basename(filename) + "'" + asset.originalfilename = filename + asset.size = os.stat(filename).st_size + + asset.digest = asdcp_digest(filename) + + return asset - self.rating = rating - def AddReel(self, reel): - self.reellists.append(reel) +class Asset(object): + def __init__(self): + self.UUID = asdcp_genuuid() + self.digest = '' + self.size = 0 + + def yield_pkl_SMPTE(self, head_element): + etree.SubElement(head_element, 'Id' ).text = 'urn:uuid:' + self.UUID + etree.SubElement(head_element, 'AnnotationText' ).text = escape(self.annotation.encode('ascii', 'xmlcharrefreplace')) + etree.SubElement(head_element, 'Hash' ).text = self.digest + etree.SubElement(head_element, 'Size' ).text = '%d' % (self.size,) + etree.SubElement(head_element, 'OriginalFilename' ).text = self.targetfilename # the PKL original filename is our target filename! + + def yield_am_SMPTE(self, head_element): + _asset = etree.SubElement(head_element, 'Asset') + etree.SubElement(_asset, 'Id').text = 'urn:uuid:' + self.UUID + yield_ChunkList_SMPTE(_asset) - def yield_SMPTE(reellist): - from xml.sax.saxutils import escape + def yield_ChunkList_SMPTE(self, head_element): + _chunklist = etree.SubElement(head_element, 'ChunkList') + _chunk = etree.SubElement(_chunklist, 'Chunk') + etree.SubElement(_chunk, 'Path').text = self.targetfilename + etree.SubElement(_chunk, 'VolumeIndex').text = '1' + etree.SubElement(_chunk, 'Offset').text = '0' + etree.SubElement(_chunk, 'Length').text = '%d' % (self.size,) + +class Track(Asset): + def __init__(self): + super(Track, self).__init__() + self.entrypoint = 0 + + def yield_cpl_SMPTE(self, head_element): + etree.SubElement(head_element, 'Id' ).text = 'urn:uuid:' + self.UUID + etree.SubElement(head_element, 'AnnotationText' ).text = escape(self.annotation.encode('ascii', 'xmlcharrefreplace')) + etree.SubElement(head_element, 'Hash' ).text = self.digest + etree.SubElement(head_element, 'EditRate' ).text = '%d %d' % self.editrate + etree.SubElement(head_element, 'EntryPoint' ).text = '%d' % (self.entrypoint,) + etree.SubElement(head_element, 'IntrinsicDuration' ).text = '%d' % (self.intrinsicduration,) + etree.SubElement(head_element, 'Duration' ).text = '%d' % (self.duration,) + +class CompositionPlayList(Asset): + def __init__(self, title, kind, reels=list(), rating=None): + super(CompositionPlayList, self).__init__() + self.title = title + self.annotation = "Playlist '" + title + "'" + self.kind = kind + self.reels = reels + self.rating = rating + self.targetfilename = self.UUID + '_cpl.xml' + + def xml_SMPTE(self): title = escape(self.title.encode('ascii', 'xmlcharrefreplace')) # CPL head - cpl = etree.Element('{http://www.smpte-ra.org/schemas/429-7/2006/CPL}CompositionPlaylist') + cpl = etree.Element('{http://www.smpte-ra.org/schemas/429-7/2006/CPL}CompositionPlaylist', nsmap={None: 'http://www.smpte-ra.org/schemas/429-7/2006/CPL'}) etree.SubElement(cpl, 'Id' ).text = 'urn:uuid:' + self.UUID etree.SubElement(cpl, 'IssueDate' ).text = ISSUEDATE @@ -90,43 +178,121 @@ class CompositionPlayList(Asset): etree.SubElement(cpl, 'RatingList') # Reels - _reellist = etree.SubElement(cpl, 'ReelList') - - for reel in reellist: - _reel = etree.SubElement(_reellist, 'Reel') - etree.SubElement(_reel, 'Id').text = 'urn:uuid:' + reel.UUID - _assetlist = etree.SubElement(_reel, 'AssetList') + reellist = etree.SubElement(cpl, 'ReelList') - for asset in reel.assetlist: - asset.cpl_SMPTE(_reel) + for reel in self.reels: + reel.yield_cpl_SMPTE(reellist) - if isinstance(asset, PictureTrack): - if isinstance(asset, StereoscopicPictureTrack): - _asset = etree.SubElement(_assetlist, '{http://www.smpte-ra.org/schemas/429-10/2008/Main-Stereo-Picture-CPL}MainStereoscopicPicture', nsmap={'msp-cpl': 'http://www.smpte-ra.org/schemas/429-10/2008/Main-Stereo-Picture-CPL'} ) - if isinstance(asset, SoundTrack): - _asset = etree.SubElement(_assetlist, 'MainSound') - + _xml = etree.tostring(cpl, pretty_print=True, xml_declaration=True, standalone=None, encoding='UTF-8') + self.digest = dcp_digest(_xml) + + def write_SMPTE(self): + pass - return etree.tostring(cpl, encoding="UTF-8") - + def yield_pkl_SMPTE(self, head_element): + super(CompositionPlayList, self).yield_pkl_SMPTE(head_element) + etree.SubElement(head_element, 'Type').text = 'text/xml' class PackingList(Asset): - -class Track(Asset): - pass + def __init__(self, assets=list()): + super(PackingList, self).__init__() + self.assets = assets + self.targetfilename = self.UUID + '_pkl.xml' + + def xml_SMPTE(self): + pkl = etree.Element('{http://www.smpte-ra.org/schemas/429-8/2007/PKL}PackingList', nsmap={None: 'http://www.smpte-ra.org/schemas/429-8/2007/PKL'}) + etree.SubElement(pkl, 'Id' ).text = 'urn:uuid:' + self.UUID + etree.SubElement(pkl, 'IssueDate' ).text = ISSUEDATE + etree.SubElement(pkl, 'Issuer' ).text = ISSUER + etree.SubElement(pkl, 'Creator' ).text = CREATOR + + _assetlist = etree.SubElement(pkl, 'AssetList') + + for asset in self.assets: + _asset = etree.SubElement(_assetlist, 'Asset') + asset.yield_pkl_SMPTE(_asset) + + _xml = etree.tostring(pkl, pretty_print=True, xml_declaration=True, standalone=True, encoding='UTF-8') + self.size=len(_xml) + self.digest=dcp_digest(_xml) + return _xml + + def yield_am_SMPTE(self, head_element): + _asset = etree.SubElement(head_element, 'Asset') + etree.SubElement(_asset, 'Id').text = 'urn:uuid:' + self.UUID + etree.SubElement(_asset, 'PackingList').text = 'true' + yield_ChunkList_SMPTE(_asset) class SoundTrack(Track): + def __init__(self): + super(SoundTrack, self).__init__() + def yield_cpl_SMPTE(self, head_element): - _asset = etree.SubElement(head_element, 'MainSound') - super().yield_cpl_SMPTE(_asset) + asset = etree.SubElement(head_element, 'MainSound') + super(SoundTrack, self).yield_cpl_SMPTE(asset) + + def yield_pkl_SMPTE(self, head_element): + super(SoundTrack, self).yield_pkl_SMPTE(head_element) + etree.SubElement(head_element, 'Type').text = 'application/mxf' class PictureTrack(Track): + def __init__(self, stereoscopic = False): + super(PictureTrack, self).__init__() + self.stereoscopic = stereoscopic + + def yield_cpl_SMPTE(self, head_element): + if self.stereoscopic: + asset = etree.SubElement( + head_element, + '{http://www.smpte-ra.org/schemas/429-10/2008/Main-Stereo-Picture-CPL}MainStereoscopicPicture', + nsmap={'msp-cpl': 'http://www.smpte-ra.org/schemas/429-10/2008/Main-Stereo-Picture-CPL'} ) + else: + asset = etree.SubElement(head_element, 'MainPicture') + + super(PictureTrack, self).yield_cpl_SMPTE(asset) + + etree.SubElement(asset, 'FrameRate').text = '%d %d' % self.framerate + etree.SubElement(asset, 'ScreenAspectRatio').text = '%d %d' % self.aspectratio + + def yield_pkl_SMPTE(self, head_element): + super(PictureTrack, self).yield_pkl_SMPTE(head_element) + etree.SubElement(head_element, 'Type').text = 'application/mxf' + +class Reel(object): + def __init__(self, assets=list()): + self.UUID=asdcp_genuuid() + self.assets = assets + def yield_cpl_SMPTE(self, head_element): - _asset = etree.SubElement(head_element, 'MainPicture') - super().yield_cpl_SMPTE(_asset) + reel = etree.SubElement(head_element, 'Reel') + etree.SubElement(reel, 'Id').text = 'urn:uuid:' + self.UUID + assetlist = etree.SubElement(reel, 'AssetList') + for asset in self.assets: + asset.yield_cpl_SMPTE(reel) -class Reel: - def __init__(self): - self.UUID=asdcp_genuid() + + +class Assetmap(object): + def __init__(self, assets=list()) + self.assets = assets + self.UUID = asdcp_genuuid() + self.volumecount = 1 + + def xml_SMPTE(self): + assetmap = etree.Element('{http://www.smpte-ra.org/schemas/429-9/2007/AM}AssetMap', nsmap={None: 'http://www.smpte-ra.org/schemas/429-9/2007/AM'}) + assetlist = etree.SubElement(assetmap, 'AssetList') + for asset in assets: + asset.yield_am_SMPTE(assetlist) + + _xml = etree.tostring(assetmap, pretty_print=True, xml_declaration=True, standalone=True, encoding='UTF-8') + return _xml + +class VolumeIndex(object): + def xml_SMPTE(self): + _volumeindex = etree.Element('{http://www.smpte-ra.org/schemas/429-9/2007/AM}VolumeIndex', nsmap={None: 'http://www.smpte-ra.org/schemas/429-9/2007/AM'}) + etree.SubElement(_volumeindex, 'Index').text = '1' + + _xml = etree.tostring(_volumeindex, pretty_print=True, xml_declaration=True, standalone=True, encoding='UTF-8') + return _xml -- cgit v1.2.3