You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

189 lines
8.6 KiB

#!/usr/bin/env python3
''' ADI builder
author: Steven Kuterna steven.kuterna@devoteam.com
'''
import sys
import argparse
import logging
import os
import shutil
import yaml
from lxml import etree as ET
import hashlib
from datetime import datetime
loglevel= logging.WARNING
logger = logging.getLogger()
logger.setLevel(loglevel)
ch = logging.StreamHandler()
ch.setLevel(loglevel)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
def insertMetadata(tree, config):
if config['Verb'] is None: config['Verb'] = ''
if config['CreateDate'] is None:
now = datetime.now()
date = now.strftime("%Y-%m-%d")
config['CreateDate'] = date
try:
attributes= { 'Asset_Class': 'package',
'Asset_ID': str(config['AssetId']),
'Asset_Name': str(config['AssetName']),
'Provider': str(config['Provider']),
'Provider_ID': str(config['ProviderId']),
'Product': 'MOD',
'Description': str(config['Description']),
'Creation_Date': str(config['CreateDate']),
'Verb': str(config['Verb']),
'Version_Major': str(config['VersionMajor']),
'Version_Minor': str(config['VersionMinor'])
}
except KeyError as e:
logger.error('failed to add Metadata AMS attribute '+str(e))
sys.exit(0)
ET.SubElement(tree, 'AMS', attributes)
ET.SubElement(tree, 'App_Data', {'App': 'MOD', 'Name': "Metadata_Spec_Version", 'Value': "CableLabsVOD1.1"})
ET.SubElement(tree, 'App_Data', {'App': 'MOD', 'Name': "Provider_Content_Tier", 'Value': "VU"})
def createAMSattributes(asset_type, metadata):
if metadata['Verb'] is None: metadata['Verb'] = ''
try:
ams_attributes= { 'Asset_Class': asset_type,
'Asset_ID': asset_type.upper() + str(metadata['AssetId']),
'Asset_Name': str(metadata['AssetName'] + ' ' + asset_type),
'Provider': str(metadata['Provider']),
'Provider_ID': str(metadata['ProviderId']),
'Product': 'MOD',
'Description': str(metadata['Description'] + ' ' + asset_type),
'Creation_Date': str(metadata['CreateDate']),
'Verb': str(metadata['Verb']),
'Version_Major': str(metadata['VersionMajor']),
'Version_Minor': str(metadata['VersionMinor'])
}
except KeyError as e:
logger.error('failed to add Metadata AMS attribute '+str(e))
sys.exit(0)
return ams_attributes
def addAsset(tree, asset_type, asset, metadata, output):
if not 'Content' in asset:
for subasset in asset:
addAsset(tree, asset_type, asset[subasset], metadata, output)
return
logger.info(f'insert asset: {asset}')
ams_attributes = createAMSattributes(asset_type, metadata)
assetTree = ET.SubElement(tree, 'Asset')
assetMetadata = ET.SubElement(assetTree, 'Metadata')
ET.SubElement(assetMetadata, 'AMS', ams_attributes)
ET.SubElement(assetMetadata, 'App_Data', {'App': 'MOD', 'Name': 'Type', 'Value': asset_type})
for element in asset:
if type(asset[element]) is dict:
for subelement in asset[element]:
if type(asset[element][subelement]) is dict:
for subsubelemment in asset[element][subelement]:
ET.SubElement(assetMetadata, 'App_Data', {'App': 'MOD', 'Name': subsubelemment, 'Value': str(asset[element][subelement][subsubelemment])})
else:
if asset[element][subelement] is None:
asset[element][subelement] = ''
ET.SubElement(assetMetadata, 'App_Data', {'App': 'MOD', 'Name': element, 'Value': str(asset[element][subelement])})
else:
# is it content? add it under the Asset level
if str(element).upper() == 'CONTENT_CHECKSUM' or str(element).upper() == 'CONTENT_FILESIZE':
continue
if str(element).upper() == 'CONTENT':
if not os.path.exists(args.input):
logger.error(f"content file {asset[element]} is not found, exiting...")
sys.exit(0)
ET.SubElement(assetTree, 'Content', {'Value': asset[element]})
checksum = hashlib.md5(open(asset[element],'rb').read()).hexdigest()
filesize = os.path.getsize(asset[element])
ET.SubElement(assetMetadata, 'App_Data', {'App': 'MOD', 'Name': 'Content_CheckSum', 'Value': str(checksum)})
ET.SubElement(assetMetadata, 'App_Data', {'App': 'MOD', 'Name': 'Content_Filesize', 'Value': str(filesize)})
shutil.copyfile(asset[element], f'{output}/{asset[element]}')
continue
# no content, add it under the Metadata level
if asset[element] is None:
asset[element] = ''
ET.SubElement(assetMetadata, 'App_Data', {'App': 'MOD', 'Name': element, 'Value': str(asset[element])})
def addTitle(tree, title, metadata):
logger.info(f'insert title: {title}')
ams_attributes = createAMSattributes('title', metadata)
titleTree = ET.SubElement(tree, 'Metadata')
ET.SubElement(titleTree, 'AMS', ams_attributes)
ET.SubElement(titleTree, 'App_Data', {'App': 'MOD', 'Name': 'Type', 'Value': 'title'})
for element in title:
# check if there are multiple elements available
if type(title[element]) == dict:
for subelement in title[element]:
# check if it contains multiple attributes like Language and a Value
if type(title[element][subelement]) == dict:
attributes = {'App': 'MOD', 'Name': element}
# if the attribute is the same as the element change it to 'Value'
for subsubelement in title[element][subelement]:
if subsubelement == element:
attributes.update({'Value': title[element][subelement][subsubelement]})
else:
# just add it to the attributes list
attributes.update({subsubelement: title[element][subelement][subsubelement]})
# add the multiattribute element to the tree
ET.SubElement(titleTree, 'App_Data', attributes)
continue
# single attribute, add to the tree
if title[element][subelement] is None:
title[element][subelement] = ''
ET.SubElement(titleTree, 'App_Data', {'App': 'MOD', 'Name': element, 'Value': title[element][subelement]})
else:
# single element, add to the tree
if title[element] is None:
title[element] = ''
ET.SubElement(titleTree, 'App_Data', {'App': 'MOD', 'Name': element, 'Value': str(title[element])})
def insertAssets(tree, metadata, assets, output):
for asset in assets:
if asset.upper() == 'TITLE':
addTitle(tree, assets[asset], metadata)
else:
addAsset(tree, asset, assets[asset], metadata, output)
return
if __name__ == "__main__" :
argparser = argparse.ArgumentParser(description='Creates ADI from yaml...')
argparser.add_argument('--input', required=False, default="adi.yaml", help="yaml input to build adi.xml")
argparser.add_argument('--output', required=True, help="output directory for xml and content files")
args = argparser.parse_args()
logger.debug(f"generating adi based on {args.input}")
if not os.path.exists(args.input):
logger.error(f"file {args.input} is not found, exiting...")
sys.exit(0)
with open(args.input, 'r') as ymlfile:
cfg = yaml.load(ymlfile, Loader=yaml.FullLoader)
try:
os.mkdir(args.output)
except FileExistsError:
logger.warning(f'directory {args.output} already exists!')
root = ET.Element('ADI')
metadataTree = ET.SubElement(root, 'Metadata')
insertMetadata(metadataTree, cfg['metadata'])
assetTree = ET.SubElement(root, 'Asset')
insertAssets(assetTree, cfg['metadata'], cfg['asset'], args.output)
tree = ET.ElementTree(root)
try:
tree.write(f'{args.output}/adi.xml', encoding='utf-8', xml_declaration=True, pretty_print=True, doctype='<!DOCTYPE ADI SYSTEM "ADI.DTD">')
except Exception as e:
print(str(e))