Last active
December 14, 2020 23:41
-
-
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
This file contains hidden or 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
''' | |
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