Skip to content
Snippets Groups Projects
Commit b31f724e authored by nimrod's avatar nimrod
Browse files

Work on future feature (full ingest of DCP from removable media).

parent 234ffe5b
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/env python3 #!/usr/bin/env python3
from hashlib import sha1 from hashlib import sha1
from base64 import b64encode from base64 import b64encode
from os import stat, listdir from os import stat, listdir, mkdir, statvfs
from os.path import isdir, basename
from shutil import copyfile
from xml.dom.minidom import parse from xml.dom.minidom import parse
import sys import sys
import vlc import vlc
class Asset(object): class Asset (object):
'''A simple asset that's part of whole DCP package.''' '''A simple asset that's part of whole DCP package.'''
def verifySize(self): def verifySize (self):
'''verify that the size of the file is correct, if present.''' '''verify that the size of the file is correct, if present.'''
try: try:
return stat(self.fullpath).st_size == self.size return stat (self.fullpath).st_size == self.size
except AttributeError: except AttributeError:
return True return True
def verifyHash(self): def verifyHash (self):
'''verify that the hash is correct, if present.''' '''verify that the hash is correct, if present.'''
if hasattr(self, 'hash') == False: if hasattr (self, 'hash') == False:
return True return True
#from os import stat #from os import stat
#try: #try:
...@@ -25,13 +27,38 @@ class Asset(object): ...@@ -25,13 +27,38 @@ class Asset(object):
#except: #except:
#buffersize = 2**16 #buffersize = 2**16
buffersize = 2 ** 16 buffersize = 2 ** 16
fh = open(self.fullpath, 'rb') fh = open (self.fullpath, 'rb')
buffer = fh.read(buffersize) buffer = fh.read (buffersize)
hash = sha1() hash = sha1 ()
while buffer: while buffer:
hash.update(buffer) hash.update (buffer)
buffer = fh.read(buffersize) buffer = fh.read (buffersize)
return(self.hash == b64encode (hash.digest()).decode()) fh.close ()
return (self.hash == b64encode (hash.digest ()).decode ())
def copyAndVerify (self, destination):
'''Copies the file to the destination directory and verifies the hash
during.'''
newfilepath = destination + '/' + self.filename
if hasattr (self, 'hash') == False:
copyfile (self.fullpath, newfilepath)
self.fullpath = newfilepath
self.rootpath = destination
return True
buffersize = 2 ** 16
in_fh = open (self.fullpath, 'rb')
out_fh = open (newfullpath, 'wb')
buffer = in_fh.read (buffersize)
hash = sha1 ()
while buffer:
hash.update (buffer)
out_fh.write (buffer)
buffer = in_fh.read (buffersize)
in_fh.close ()
out_fh.close ()
self.rootpath = destination
self.fullpath = newfullpath
return (self.hash == b64encode (hash.digest ()).decode ())
def add_duration (self): def add_duration (self):
if hasattr (self, 'type') and self.type.find ('mxf') > -1: if hasattr (self, 'type') and self.type.find ('mxf') > -1:
...@@ -40,7 +67,8 @@ class Asset(object): ...@@ -40,7 +67,8 @@ class Asset(object):
media.parse () media.parse ()
self.duration = media.get_duration () self.duration = media.get_duration ()
def __init__(self, rootpath, filename, id=None, hash=None, size=None, packinglist=False, type=None): def __init__(self, rootpath, filename, id=None, hash=None, size=None,\
packinglist=False, type=None):
'''Initialize an asset, has to have a filename and path.''' '''Initialize an asset, has to have a filename and path.'''
self.rootpath = rootpath self.rootpath = rootpath
if filename[0:8] == 'file:///': if filename[0:8] == 'file:///':
...@@ -60,15 +88,15 @@ class Asset(object): ...@@ -60,15 +88,15 @@ class Asset(object):
class DCP: class DCP:
'''A complete DCP package.''' '''A complete DCP package.'''
def verify(self): def verify (self):
'''Verifies that all assets are of the correct size and have '''Verifies that all assets are of the correct size and have
the correct hash.''' the correct hash.'''
for asset in self.assets: for asset in self.assets:
try: try:
if asset.verifySize() == False: if asset.verifySize () == False:
return False return False
except BaseException as e: except BaseException as e:
raise RuntimeError ('Failed size comparisement for ' + raise RuntimeError ('Failed size comparisement for ' +\
asset.filename) from e asset.filename) from e
#Sort the assets by size before calculating hashes, for performance. #Sort the assets by size before calculating hashes, for performance.
def sortkey(x): def sortkey(x):
...@@ -76,71 +104,75 @@ class DCP: ...@@ -76,71 +104,75 @@ class DCP:
return x.size return x.size
except AttributeError: except AttributeError:
return 0 return 0
self.assets.sort(key=sortkey) self.assets.sort (key=sortkey)
for asset in self.assets: for asset in self.assets:
try: try:
if asset.verifyHash() == False: if asset.verifyHash () == False:
return False return False
except BaseException as e: except BaseException as e:
raise RuntimeError ('Failed hash calculation for ' + raise RuntimeError ('Failed hash calculation for ' +\
asset.filename) from e asset.filename) from e
return True return True
def _parse_assetmap (self): def _parse_assetmap (self):
'''Adds the asset map file to the list of assets and parses it.''' '''Adds the asset map file to the list of assets and parses it.'''
if 'ASSETMAP.xml' in listdir(self.directory): if 'ASSETMAP.xml' in listdir (self.directory):
filename = 'ASSETMAP.xml' filename = 'ASSETMAP.xml'
elif 'ASSETMAP' in listdir(self.directory): elif 'ASSETMAP' in listdir (self.directory):
filename = 'ASSETMAP' filename = 'ASSETMAP'
else: else:
raise RuntimeError ('Couldn\'t find assetmap file') raise RuntimeError ('Couldn\'t find assetmap file')
self.assets = [Asset(self.directory, filename, type='text/xml')] self.assets = [Asset (self.directory, filename, type='text/xml')]
assetmap = self.assets[0].fullpath assetmap = self.assets[0].fullpath
try: try:
assetmap = parse(assetmap).getElementsByTagName('Asset') assetmap = parse (assetmap).getElementsByTagName ('Asset')
self.assets.append(Asset(self.directory, filename)) self.assets.append (Asset (self.directory, filename))
for element in assetmap: for element in assetmap:
id = element.getElementsByTagName('Id')[0].firstChild.data id = element.getElementsByTagName ('Id')[0].firstChild.data
id = id.split(':')[-1] id = id.split (':')[-1]
filename = element.getElementsByTagName('Path')[0].firstChild.data filename = element.getElementsByTagName ('Path')[0].firstChild.data
packinglist = len(element.getElementsByTagName('PackingList')) > 0 packinglist = len (element.getElementsByTagName ('PackingList')) > 0
if packinglist: if packinglist:
self.assets.append(Asset(self.directory, filename, id=id, packinglist=packinglist, type='text/xml')) self.assets.append (Asset (self.directory, filename,\
id=id, packinglist=packinglist, type='text/xml'))
else: else:
self.assets.append(Asset(self.directory, filename, id=id, packinglist=packinglist)) self.assets.append (Asset (self.directory, filename,\
id=id, packinglist=packinglist))
except BaseException as e: except BaseException as e:
raise RuntimeError ('Failed to parse assetmap file') from e raise RuntimeError ('Failed to parse assetmap file') from e
def _add_volindex (self): def _add_volindex (self):
'''Adds the volume index file to the list of assets.''' '''Adds the volume index file to the list of assets.'''
if 'VOLINDEX.xml' in listdir(self.directory): if 'VOLINDEX.xml' in listdir (self.directory):
filename = 'VOLINDEX.xml' filename = 'VOLINDEX.xml'
elif 'VOLINDEX' in listdir(self.directory): elif 'VOLINDEX' in listdir (self.directory):
filename = 'VOLINDEX' filename = 'VOLINDEX'
else: else:
#raise RuntimeError ('Couldn\'t find volindex file') #raise RuntimeError ('Couldn\'t find volindex file')
return return
self.assets.append(Asset(self.directory, filename, type='text/xml')) self.assets.append (Asset (self.directory, filename, type='text/xml'))
def _parse_packinglist (self): def _parse_packinglist (self):
'''Parses the packing list.''' '''Parses the packing list.'''
try: try:
pkls = (parse(x.fullpath) for x in self.assets if x.packinglist) pkls = (parse (x.fullpath) for x in self.assets if x.packinglist)
for pkl in pkls: for pkl in pkls:
if hasattr(self, 'signed') == False: if hasattr (self, 'signed') == False:
self.signed = len(pkl.getElementsByTagName('Signer')) > 0 self.signed = len (pkl.getElementsByTagName ('Signer')) > 0
try: try:
if hasattr(self, 'name') == False: if hasattr (self, 'name') == False:
self.name = pkl.getElementsByTagName('AnnotationText')[0].firstChild.data.strip() self.name = pkl.getElementsByTagName\
('AnnotationText')[0].firstChild.data.strip()
except: except:
pass pass
for element in pkl.getElementsByTagName('Asset'): for element in pkl.getElementsByTagName ('Asset'):
id = element.getElementsByTagName('Id')[0].firstChild.data id = element.getElementsByTagName ('Id')[0].firstChild.data
id = id.split(':')[-1] id = id.split (':')[-1]
hash = element.getElementsByTagName('Hash')[0].firstChild.data hash = element.getElementsByTagName ('Hash')[0].firstChild.data
type = element.getElementsByTagName('Type')[0].firstChild.data type = element.getElementsByTagName ('Type')[0].firstChild.data
size = int(element.getElementsByTagName('Size')[0].firstChild.data) size = int(element.getElementsByTagName ('Size')[0].firstChild.data)
asset = [x for x in self.assets if hasattr(x, 'id') and x.id == id][0] asset = [x for x in self.assets if hasattr (x, 'id') and\
x.id == id][0]
asset.hash = hash asset.hash = hash
asset.size = size asset.size = size
asset.type = type asset.type = type
...@@ -148,23 +180,57 @@ class DCP: ...@@ -148,23 +180,57 @@ class DCP:
except BaseException as e: except BaseException as e:
raise RuntimeError ('Failed to parse packinglist file') from e raise RuntimeError ('Failed to parse packinglist file') from e
def __init__(self, directory): def __init__ (self, directory):
'''Parses the DCP in the directory specified.''' '''Parses the DCP in the directory specified.'''
self.directory = directory self.directory = directory
self._parse_assetmap () self._parse_assetmap ()
self._add_volindex () self._add_volindex ()
self._parse_packinglist () self._parse_packinglist ()
try: try:
self.duration = max ([x.duration for x in self.assets if hasattr (x, 'duration')]) self.duration = max ([x.duration for x in self.assets if hasattr\
(x, 'duration')])
except: except:
self.duration = 'Unknown' self.duration = 'Unknown'
def copyAndVerify (self, destination):
'''Copies the DCP to the destination directory and verifies during.'''
for asset in self.assets:
totalsize = stat (asset.fullpath).st_size
try:
if asset.verifySize () == False:
return False
except BaseException as e:
raise RuntimeError ('Failed size comparisement for ' +\
asset.filename) from e
freespace = statvfs (destination).f_bavail * statvfs\
(destination).f_bsize
if freespace < totalsize:
return False
#Sort the assets by size before calculating hashes, for performance.
def sortkey(x):
try:
return x.size
except AttributeError:
return 0
self.assets.sort (key=sortkey)
newdirectory = destination + '/' + basename (self.directory)
mkdir (newdirectory)
for asset in self.assets:
try:
if asset.copyAndVerify (newdirectory) == False:
return False
except BaseException as e:
raise RuntimeError ('Failed hash calculation for ' +\
asset.filename) from e
self.directory = newdirectory
return True
if __name__ == '__main__': if __name__ == '__main__':
if (len(sys.argv) == 2): if len (sys.argv) == 2:
dcp = DCP(sys.argv[1]) dcp = DCP (sys.argv[1])
else: else:
dcp = DCP('./') dcp = DCP ('./')
try: try:
print ('Name:', dcp.name) print ('Name:', dcp.name)
except: except:
...@@ -174,8 +240,8 @@ if __name__ == '__main__': ...@@ -174,8 +240,8 @@ if __name__ == '__main__':
else: else:
print ('DCP is unsigned') print ('DCP is unsigned')
print ('Duration:', dcp.duration) print ('Duration:', dcp.duration)
if (dcp.verify()): if dcp.verify ():
print ('Verification succeeded.') print ('Verification succeeded.')
else: else:
print ('Verification failed.') print ('Verification failed.')
exit(0) exit (0)
...@@ -6,6 +6,8 @@ from PyQt4 import QtCore ...@@ -6,6 +6,8 @@ from PyQt4 import QtCore
from dcp import DCP from dcp import DCP
import ui import ui
import sys import sys
import syslog
import time
class verifyThread(QtCore.QThread): class verifyThread(QtCore.QThread):
'''A seperate thread to verify the DCP (IO intensive). '''A seperate thread to verify the DCP (IO intensive).
...@@ -20,8 +22,12 @@ class verifyThread(QtCore.QThread): ...@@ -20,8 +22,12 @@ class verifyThread(QtCore.QThread):
result = dcp.verify() result = dcp.verify()
if result: if result:
window.verifyLine.setText('OK') window.verifyLine.setText('OK')
syslog.syslog (syslog.LOG_INFO, time.ctime () + dcp.name +\
' verification succeeded.')
else: else:
window.verifyLine.setText('Corrupted!') window.verifyLine.setText('Corrupted!')
syslog.syslog (syslog.LOG_INFO, time.ctime () + dcp.name +\
' verification failed.')
except BaseException as exception: except BaseException as exception:
window.verifyLine.setText(str(exception)) window.verifyLine.setText(str(exception))
...@@ -38,6 +44,7 @@ def verify_in_thread(): ...@@ -38,6 +44,7 @@ def verify_in_thread():
if __name__ == '__main__': if __name__ == '__main__':
syslog.openlog (ident = 'dcpman', facility = syslog.LOG_USER)
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
icon = QtGui.QIcon('/usr/share/icons/oxygen/16x16/apps/kmplayer.png') icon = QtGui.QIcon('/usr/share/icons/oxygen/16x16/apps/kmplayer.png')
app.setWindowIcon(icon) app.setWindowIcon(icon)
...@@ -51,8 +58,18 @@ if __name__ == '__main__': ...@@ -51,8 +58,18 @@ if __name__ == '__main__':
dcp = DCP(directory) dcp = DCP(directory)
try: try:
window.nameLine.setText(dcp.name) window.nameLine.setText(dcp.name)
syslog.syslog (syslog.INFO, time.ctime () + dcp.name +\
'parsed.')
except AttributeError: except AttributeError:
window.nameLine.setText(directory.split('/')[-1]) window.nameLine.setText(directory.split('/')[-1])
if dcp.signed:
syslog.syslog (syslog.INFO, time.ctime () + dcp.name +\
' is signed.')
else:
syslog.syslog (syslog.INFO, time.ctime () + dcp.name +\
' is not sigend.')
syslog.syslog (syslog.INFO, time.ctime () + dcp.name +\
' duration is ' + dcp.duration)
if dcp.signed and dcp.duration == 0: if dcp.signed and dcp.duration == 0:
window.encryptedLine.setText('Most likely') window.encryptedLine.setText('Most likely')
elif dcp.signed or dcp.duration == 0: elif dcp.signed or dcp.duration == 0:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment