Skip to content

Instantly share code, notes, and snippets.

@tin2tin
Last active December 14, 2020 23:41
Show Gist options
  • Save tin2tin/b078e83138ac43d016f83c40526e57f2 to your computer and use it in GitHub Desktop.
Save tin2tin/b078e83138ac43d016f83c40526e57f2 to your computer and use it in GitHub Desktop.
Extract images and time codes from Final Cut Pro X's FCPXML 1.3 formatted files exported from Storyboarder
'''
Project Purpose:
Extract images and time codes from
Final Cut Pro X's FCPXML 1.3 formatted files
Exported from Storyboarder
'''
bl_info = {
"name": "Storyboarder fcpxml import",
"author": "tintwotin",
"version": (1, 0, 4),
"blender": (2, 79, 0),
"location": "File > Import-Export",
"description": "Import Storyboarder .fcpxml files",
"category": "Import"
}
import bpy
import os
import bpy, sys, datetime
from xml.etree.ElementTree import parse
from .pretty_img import load_scale_img
from bpy_extras.io_utils import ImportHelper, ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator
fcpxml_dirname = ""
resources = ""#sorted(Resources.scanForResources(xmlroot), key=lambda s: s.start)
markers = ""#sorted(Marker.scanForMarker(xmlroot), key=lambda s: s.startTime)
def import_fcpxml(context, filepath):#, use_some_setting):
# EXAMPLE:
# Import file and convert it to a list of markers sorted by ID and start time
free_channel = get_open_channel(bpy.context.scene)
fcpxml_file=filepath
#"C:\\Users\\User\\Documents\\Storyboarder_test2\\exports\\Storyboarder_test2.storyboarder-Exported-2018-11-07-09.36.55\\Storyboarder_test2.storyboarder.fcpxml"
xmlroot = parse(fcpxml_file).getroot()
print(fcpxml_file)
fcpxml_dirname = os.path.dirname(fcpxml_file)
#print(fcpxml_dirname)
resources = sorted(Resources.scanForResources(xmlroot), key=lambda s: s.start)
markers = sorted(Marker.scanForMarker(xmlroot), key=lambda s: s.startTime)
print(resources)
#scene = context.scene
#markers_sc = scene.timeline_markers
scene = bpy.context.scene
#open_channel = get_open_channel(scene)
if not scene.sequence_editor:
scene.sequence_editor_create()
print ("RESULTING ORDERED LIST:")
for r in resources:
print ("Resource {0} Name {1} Path {2} start time: {3} with duration {4} ".format(r.id, r.name, r.src, datetime.timedelta(seconds=r.start),datetime.timedelta(seconds=r.duration)))
for m in markers:
print ("Marker {0} Filepath {1} with start time: {2} with duration {3} ".format(m.name, m.filepath, datetime.timedelta(seconds=m.startTime),datetime.timedelta(seconds=m.durationTime)))
bpy.context.scene.sequence_editor_create() # verify vse is valid in scene
#(name, path, scale=1.0, channel=1, length=10, alpha=True)
load_scale_img(m.name, m.filepath, scale=1, channel=free_channel, start=m.startTime*24, duration=m.durationTime*24, alpha=True)
#print("Running export edl...\n")
class ImportFCPXML(Operator, ImportHelper):
"""Import Storyboarder .fcpxml)"""
bl_idname = "import_timeline.fcpxml" # important since its how bpy.ops.import_test.some_data is constructed
bl_label = "Import FCPXML"
# ExportHelper mixin class uses this
filename_ext = ".fcpxml"
filter_glob = StringProperty(
default="*.fcpxml",
options={'HIDDEN'},
maxlen=255, # Max internal buffer length, longer would be clamped.
)
'''# List of operator properties, the attributes will be assigned
# to the class instance from the operator settings before calling.
use_setting = BoolProperty(
name="FCPXML Boolean",
description="FCPXML Tooltip",
default=True,
)'''
def execute(self, context):
#fps, timecode = checkFPS()
return import_fcpxml(context, self.filepath)#, self.use_setting)
# Converts the '64bit/32bits' timecode format into seconds
def parseFCPTimeSeconds (timeString):
vals = [float(n) for n in timeString.replace('s','').split('/')]
if 1 == len(vals):
val = vals[0]
else:
val = vals[0]/vals[1]
return val
def get_open_channel(scene):
"""Find first free channel"""
channels = []
try:
for strip in scene.sequence_editor.sequences_all:
channels.append(strip.channel)
if len(channels) > 0:
return max(channels) + 1
else:
return 1
except AttributeError:
return 1
class Resources:
def __init__(self, id, name, src, start, duration):
self._id = id
self._name = name
self._src = src
self._start = start
self._duration = duration
@property
def id(self):
return self._id
@property
def name(self):
return self._name
@property
def src(self):
return self._src
@property
def start(self):
return self._start
@property
def duration(self):
return self._duration
@staticmethod
def scanForResources(element, time=[]):
start = duration = 0
src="C:"
name=""
'''try:
id = parseFCPTimeSeconds(element.attrib['id'])
except:
pass
try:
name = parseFCPTimeSeconds(element.attrib['name'])
except:
pass
try:
src = parseFCPTimeSeconds(element.attrib['src'])
except:
pass '''
try:
start = parseFCPTimeSeconds(element.attrib['start'])
except:
pass
try:
duration = parseFCPTimeSeconds(element.attrib['duration'])
except:
pass
r = []
if 'asset' == element.tag:
r.append(Resources(element.attrib['id'],element.attrib['name'], element.attrib['src'], start+ sum(time), duration + sum(time)))
else:
time.append(duration - start)
for el in element:
r.extend(Resources.scanForResources(el, list(time)))
return r
class Marker:
def __init__(self, name, ref, filepath, startTime, durationTime):
self._name = name
self._filepath = filepath
self._startTime = startTime
self._durationTime = durationTime
print(filepath)
@property
def name(self):
return self._name
@property
def ref(self):
return self._ref
@property
def filepath(self):
return self._filepath
@property
def startTime(self):
return self._startTime
@property
def durationTime(self):
return self._durationTime
@staticmethod
def scanForMarker(element, time=[]):
start = offset = 0
filepath=fcpxml_dirname
durationTime=0
try:
start = parseFCPTimeSeconds(element.attrib['start'])
except:
pass
try:
offset = parseFCPTimeSeconds(element.attrib['offset'])
except:
pass
try:
duration = parseFCPTimeSeconds(element.attrib['duration'])
except:
pass
m = []
if 'video' == element.tag:
for r in resources:
if r.id == element.attrib['ref']:
#filepath = str(Path(r.src).resolve())
filepath=str(filepath)+"\\"+r.src.replace("./","")
#try:
#print(os.path.exists(filepath))
print((filepath))
m.append(Marker(element.attrib['name'], element.attrib['ref'], filepath, offset + sum(time), duration + sum(time)))
else:
time.append(offset - start)
for el in element:
m.extend(Marker.scanForMarker(el, list(time)))
return m
#bpy.context.scene.timeline_markers.new(m.name, int(m.startTime)*24)
#print(str(int(m.startTime)))
'''text_strip = scene.sequence_editor.sequences.new_effect(
name=m.name,
type='TEXT',
channel=get_open_channel(scene),
frame_start=int(m.startTime)*24,
frame_end=int(m.startTime)*24+300
)
text_strip.font_size = 75
text_strip.text = m.name
text_strip.use_shadow = True
text_strip.select = True
text_strip.blend_type = 'ALPHA_OVER'''
#added_strips.append(text_strip)
#marker = markers_sc.new(m)
#marker.frame = int(m.startTime.rstrip('\r\n'))*24
#currentline = m.namecontext.scene
# # read the file
# filehandle = open(filepath, 'r')
# currentline = filehandle.readline().rstrip('\r\n')
#
# # iterate through the files lines
# while currentline:
# marker = markers.new(currentline)
# marker.frame = int(filehandle.readline().rstrip('\r\n'))
# currentline = filehandle.readline().rstrip('\r\n')
# Add to menu
def menu_func_import(self, context):
self.layout.operator(ImportFCPXML.bl_idname, text="Storyboarder (.fcpxml)")
def register():
bpy.utils.register_class(ImportFCPXML)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
def unregister():
bpy.utils.unregister_class(ImportFCPXML)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment