Last active
March 29, 2017 13:05
-
-
Save Grezzo/ed62fe2292373f14d17d to your computer and use it in GitHub Desktop.
Opens an fcpxml (from Apple FCP X) and modifies the asset elements to contain enough audio information to be used properly by any audio elements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python | |
import logging | |
def get_assets_dict(asset_elements): | |
#Create dictionary | |
asset_elements_dict = {} | |
#Add each asset to dictionary with id as key | |
for asset_element in asset_elements: | |
asset_id = asset_element.get("id") | |
asset_elements_dict[asset_id] = asset_element | |
return asset_elements_dict | |
def fix_asset(asset_element, audio_element): | |
logging.debug("Processing audio element that references asset " + asset_element.get("id") + " (" + asset_element.get("name") + ")") | |
# Add or update hasAudio attribute | |
if not asset_element.get("hasAudio") or asset_element.get("hasAudio") == "0": | |
logging.info("Changed asset " + asset_element.get("id") + " (" + asset_element.get("name") + ") to have audio") | |
asset_element.set("hasAudio", "1") | |
#Get highest audio channel used by audio element | |
if audio_element.get("srcCh"): | |
#Get the last channel number in the comma separated list | |
used_channels = audio_element.get("srcCh").split(", ") | |
highest_used_channel = int(used_channels[-1]) | |
else: | |
#If no attribute, channel must be 1 | |
highest_used_channel = 1 | |
#Get number of channels currently in asset | |
if asset_element.get("audioChannels"): | |
asset_audio_channels = int(asset_element.get("audioChannels")) | |
else: | |
asset_audio_channels = 0 | |
if asset_audio_channels < highest_used_channel: | |
#Update audioChannels attribute | |
asset_element.set("audioChannels", str(highest_used_channel)) | |
logging.info("Changed asset " + asset_element.get("id") + " (" + asset_element.get("name") + ") to have " + asset_element.get("audioChannels") + " channels") | |
#Get audio id used by audio element | |
if audio_element.get("srcID"): | |
used_id = int(audio_element.get("srcID")) | |
else: | |
#If no attribute, id must be 1 | |
used_id = 1 | |
#Get number of ids currently in asset | |
if asset_element.get("audioSources"): | |
asset_audio_ids = int(asset_element.get("audioSources")) | |
else: | |
asset_audio_ids = 0 | |
if asset_audio_ids < used_id: | |
#Update audioSources attribute | |
asset_element.set("audioSources", str(used_id)) | |
logging.info("Changed asset " + asset_element.get("id") + " (" + asset_element.get("name") + ") to have " + asset_element.get("audioSources") + " sources") | |
def main(): | |
import argparse, os, codecs, sys | |
from xml.etree.ElementTree import parse #Set up argument parser | |
parser = argparse.ArgumentParser(description="Fix an invalid fcpxml", | |
epilog="Latest version available at https://gist.github.com/Grezzo/ed62fe2292373f14d17d") | |
parser.add_argument("input", metavar='SOURCE', help="an invalid fcpxml file") | |
parser.add_argument("-o", "--output", help="the name of the fcpxml file to create") | |
parser.add_argument("-v", "--verbose", help="print verbose logging information", action="store_true") | |
parser.add_argument("-d", "--debug", help="print debug logging information", action="store_true") | |
args = parser.parse_args() | |
#If output omitted, set output to input so it overwrites source | |
if not args.output: | |
args.output = args.input | |
#Turn on logging if specified | |
if args.debug: | |
logging.getLogger().setLevel("DEBUG") | |
elif args.verbose: | |
logging.getLogger().setLevel("INFO") | |
#Check that input file exists | |
if not os.path.isfile(args.input): | |
sys.exit(os.path.basename(__file__) + ": error: " + args.input + " does not exist") | |
#Set logging format | |
logging.basicConfig(format="%(levelname)s:%(message)s") | |
#Parse fcpxml and catch any errors | |
try: | |
tree = parse(args.input) | |
root = tree.getroot() | |
#Create a parent map for each element so audio elements can be removed if they have no ref | |
parent_map = {c:p for p in tree.iter() for c in p} | |
except: | |
sys.exit(os.path.basename(__file__) + ": error: " + args.input + " cannot be parsed") | |
#Check if it is really an fcpxml | |
if root.tag != "fcpxml": | |
sys.exit(os.path.basename(__file__) + ": error: " + args.input + " is not an fcpxml") | |
#Check if destination folder exists | |
if not os.path.exists(os.path.dirname(args.output)): | |
sys.exit(os.path.basename(__file__) + ": error: " + os.path.dirname(args.output) + " does not exist") | |
#Add assets to dictionary so they can be looked up by id | |
assets_dict = get_assets_dict(root.findall("resources/asset")) | |
#Loop through each audio element | |
audios_list = root.findall(".//audio") | |
for audio_element in audios_list: | |
ref = audio_element.get("ref") | |
#If audio element has no ref, it is invalid | |
if not ref: | |
#Audio element is invalid so remove it | |
logging.info("Removed an audio element because it has no ref attribute") | |
parent = parent_map[audio_element] | |
parent.remove(audio_element) | |
else: | |
#Update asset element to contain audio info referenced by audio element | |
fix_asset(assets_dict[ref], audio_element) | |
#Write out new fcpxml file | |
tree.write(args.output) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment