Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@Grezzo
Last active March 29, 2017 13:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Grezzo/ed62fe2292373f14d17d to your computer and use it in GitHub Desktop.
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
#! /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