aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWolfgang Draxinger <Wolfgang.Draxinger@draxit.de>2011-03-27 01:41:23 +0100
committerWolfgang Draxinger <Wolfgang.Draxinger@draxit.de>2011-03-27 01:41:23 +0100
commitf1ef0a65ab5b1217f0dd81fe41758c0118a11814 (patch)
tree1600d0160f5be89e8bf80d20cde389261a10c49b
parente2c048c4e05d8e96f3580b3b29172423357a419d (diff)
downloadmkdcp-f1ef0a65ab5b1217f0dd81fe41758c0118a11814.tar.gz
mkdcp-f1ef0a65ab5b1217f0dd81fe41758c0118a11814.tar.bz2
CPL and PKL are created properly, AssetMap and VolIndex written but not tested yet.
-rw-r--r--mkdcp.py270
1 files 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