From 37f78524a9141b2a50cc98d43704ca3d60167409 Mon Sep 17 00:00:00 2001 From: Steven Kuterna Date: Fri, 5 Mar 2021 20:42:54 +0000 Subject: [PATCH] first commit --- adi_builder.py | 187 +++++++++++++++++++++++++++++++++++++++++++++++ adi_minimal.yaml | 85 +++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 adi_builder.py create mode 100644 adi_minimal.yaml diff --git a/adi_builder.py b/adi_builder.py new file mode 100644 index 0000000..2ca6421 --- /dev/null +++ b/adi_builder.py @@ -0,0 +1,187 @@ +#!/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) + + +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', 'Type': 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', 'Type': '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='') + except Exception as e: + print(str(e)) diff --git a/adi_minimal.yaml b/adi_minimal.yaml new file mode 100644 index 0000000..55d05e2 --- /dev/null +++ b/adi_minimal.yaml @@ -0,0 +1,85 @@ +#YAML file used as input for adi builder +#main structure: +# +#-metadata +# containing the package metadata +#-asset +# containing the collection of assets: +# - title asset metadata +# - movie asset metadata + content +# - still-image asset(box-cover, poster, image) AMS element metadata + content +# - preview asset AMS element metadata + content +# +# when adding more than 1 element use any random element name (indicated with arrows <-- in example below) eg: +#asset: +# title: +# DefaultTitle: This is the default title +# Summary_Long: +# 1: +# Language: NLD +# Summary_Short: Lange NLD samenvatting +# 2: +# Language: ENG +# Summary_Short: Long ENG summary +# image: +# 1: <-- +# qualifier: ScreenGrab1 +# 2: <-- +# qualifier: HighResPortrait +# +# more info about ADI on the wiki: +# https://wikiprojects.upc.biz/display/VBOASP/ADI+to+TVA+mapping+overview +# https://wikiprojects.upc.biz/display/VBOASP/ADI+metadata+ingest+specification+including+Series+delivery+via+xml + +metadata: + AssetId : 20200827160000 + AssetName : ADI minimal test + Provider : VodFeatureTeam + ProviderId: VODFT + Description: test with minimal adi + Verb: + CreateDate: + VersionMajor: 0 + VersionMinor: 1 +asset: + title: + Title: This is the title + Summary_Long: + 1: + Language: NLD + Summary_Short: Lange NLD samenvatting + 2: + Language: ENG + Summary_Short: Long ENG summary + Summary_Short: + 1: + Language: NLD + Summary_Short: korte NLD samenvatting + 2: + Language: ENG + Summary_Short: short ENG summary + Rating: 15 + Advisories: Not rated + Run_Time: 01:23:45 + Display_Run_Time: 01:24 + Genre: 1/1 + Category: + 1: root/VodFT + Licensing_Window_Start: 2020-08-31T00:00:00 + Licensing_Window_End: 2020-09-30T23:59:59 + Suggested_Price: 2.99 + + movie: + Subtitle_Languages: + 1: ENG + 2: NLD + Content: video.ts + + preview: + Content: video.ts + + image: + 1: + Image_Qualifier: HighResPortrait + Image_Aspect_Ratio: "480x720" + Content: highresportrait.jpg