edit2fcp
This commit is contained in:
commit
d755d3afc4
6 changed files with 506 additions and 0 deletions
0
edl/__init__.py
Normal file
0
edl/__init__.py
Normal file
273
edl/fcp.py
Normal file
273
edl/fcp.py
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
from ox.utils import ET
|
||||
from urllib import quote
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from utils import tostring
|
||||
|
||||
def xmlroot(root, key, data):
|
||||
if isinstance(data, list) or \
|
||||
isinstance(data, tuple):
|
||||
e = ET.SubElement(root, key.split(':')[0])
|
||||
for value in data:
|
||||
xmlroot(e, key, value)
|
||||
elif isinstance(data, dict):
|
||||
for k in data:
|
||||
if k.startswith('_'):
|
||||
root.attrib[k[1:]] = data[k]
|
||||
xmlroot(root, k, data[k])
|
||||
else:
|
||||
if key.startswith('_'):
|
||||
root.attrib[key[1:]] = unicode(data)
|
||||
else:
|
||||
e = ET.SubElement(root, key.split(':')[0])
|
||||
if isinstance(data, bool):
|
||||
e.text = 'TRUE' if data else 'FALSE'
|
||||
else:
|
||||
e.text = unicode(data)
|
||||
|
||||
class Project(object):
|
||||
duration = 0
|
||||
timebase = 30
|
||||
ntsc = True
|
||||
width = 1920
|
||||
height = 1080
|
||||
colordepth = 24
|
||||
samplerate = 48000
|
||||
audiodepth = 16
|
||||
|
||||
_clips = {
|
||||
'video1': [],
|
||||
'audio1': [],
|
||||
'audio2': [],
|
||||
}
|
||||
files = {}
|
||||
_files = []
|
||||
clip_n = 1
|
||||
|
||||
def add_clip(self, clip, position):
|
||||
clip_in = self.frames(clip['in'])
|
||||
clip_out = self.frames(clip['out'])
|
||||
duration = clip_out - clip_in
|
||||
f, name = self.get_file(clip)
|
||||
for track, type in ((1, 'video'), (1, 'audio'), (2, 'audio')):
|
||||
self._clips['%s%s' % (type, track)].append({
|
||||
'_id': 'clip%s_%s_%s' % (self.clip_n, type, track),
|
||||
'name': name,
|
||||
'duration': int(clip['durations'][0] * self.timebase),
|
||||
'rate': [{
|
||||
'ntsc': self.ntsc,
|
||||
'timebase': self.timebase
|
||||
}],
|
||||
#clip in/out
|
||||
'in': clip_in,
|
||||
'out': clip_out,
|
||||
#timeline position
|
||||
'start': position,
|
||||
'end': position + duration,
|
||||
'file': [f],
|
||||
'sourcetrack': [{
|
||||
'mediatype': type,
|
||||
'trackindex': track
|
||||
}],
|
||||
'comments': [{
|
||||
'mastercomment1': clip['url'],
|
||||
'clipcommenta': clip['url'],
|
||||
}],
|
||||
})
|
||||
self.clip_n += 1
|
||||
return position + duration
|
||||
|
||||
def frames(self, seconds):
|
||||
return int((30000.0/1001 if self.ntsc else self.timebase) * seconds)
|
||||
|
||||
def get_file(self, clip):
|
||||
path = clip['path']
|
||||
if path in self.files:
|
||||
f = {'_id': self.files[path]['_id']}
|
||||
name = self.files[path]['name']
|
||||
else:
|
||||
info = {
|
||||
'width': clip['width'],
|
||||
'height': clip['height'],
|
||||
'channels': clip['channels'],
|
||||
'samplerate': clip['samplerate'],
|
||||
'duration': self.frames(clip['file_duration']),
|
||||
}
|
||||
|
||||
name = os.path.splitext(path.split('/')[-1])[0]
|
||||
pathurl = '%s%s' % (self.base, quote(path))
|
||||
f = self.files[path] = {
|
||||
'_id': name.replace('.', '_') + '1',
|
||||
'name': name,
|
||||
'pathurl': pathurl,
|
||||
'rate': [{
|
||||
'timebase': self.timebase,
|
||||
'ntsc': self.ntsc,
|
||||
}],
|
||||
'duration': info['duration'],
|
||||
'media': [{
|
||||
'video': [{
|
||||
'duration': info['duration'],
|
||||
'samplecharacteristics': [{
|
||||
'width': info['width'],
|
||||
'height': info['height'],
|
||||
}]
|
||||
}],
|
||||
'audio': [{
|
||||
'samplecharacteristics': [{
|
||||
'samplerate': info['samplerate'],
|
||||
'depth': 16
|
||||
}],
|
||||
'channelcount': info['channels']
|
||||
|
||||
}],
|
||||
}],
|
||||
}
|
||||
self._files.append({
|
||||
'_id': name.replace('.', '_'),
|
||||
'name': name,
|
||||
'duration': info['duration'],
|
||||
'rate': [{
|
||||
'timebase': self.timebase,
|
||||
'ntsc': self.ntsc,
|
||||
}],
|
||||
'file': [f]
|
||||
})
|
||||
return f, name
|
||||
|
||||
def __init__(self, clips, base):
|
||||
self.uuid = str(uuid.uuid1()).upper()
|
||||
self.clips = clips
|
||||
self.base = 'file://localhost%s' % quote(base)
|
||||
self.duration = 0
|
||||
for clip in self.clips:
|
||||
self.duration = self.add_clip(clip, self.duration)
|
||||
|
||||
def __str__(self):
|
||||
xmeml = ET.Element("xmeml", {
|
||||
"version": "5"
|
||||
})
|
||||
name = 'Sequence 1'
|
||||
sequence = ET.SubElement(xmeml, "sequence", {
|
||||
"id": "%s " % name
|
||||
})
|
||||
xmlroot(sequence, 'sequence', {
|
||||
'uuid': self.uuid,
|
||||
'updatebehavior': 'add',
|
||||
'name': name,
|
||||
'duration': self.duration,
|
||||
'rate': [{
|
||||
'ntsc': self.ntsc,
|
||||
'timebase': self.timebase
|
||||
}],
|
||||
'timecode': [{
|
||||
'rate': [{
|
||||
'ntsc': self.ntsc,
|
||||
'timebase': self.timebase
|
||||
}],
|
||||
'string': '01:00:00;00',
|
||||
'frame': self.frames(3600),
|
||||
'source': 'source',
|
||||
'displayformat': 'DF'
|
||||
}],
|
||||
'in': -1,
|
||||
'out': -1,
|
||||
'media': [{
|
||||
'video': [{
|
||||
'format': [{
|
||||
'samplecharacteristics': [{
|
||||
'width': self.width,
|
||||
'height': self.height,
|
||||
'anamorphic': False,
|
||||
'pixelaspectratio': 'Square',
|
||||
'fielddominance': 'none',
|
||||
'rate': [{
|
||||
'ntsc': self.ntsc,
|
||||
'timebase': self.timebase
|
||||
}],
|
||||
'colordepth': self.colordepth,
|
||||
'codec': [{
|
||||
'name': 'Apple ProRes 422',
|
||||
'appspecificdata': [{
|
||||
'appname': 'Final Cut Pro',
|
||||
'appmanufacturer': 'Apple Inc.',
|
||||
'appversion': '7.0',
|
||||
'data': [{
|
||||
'qtcodec': [{
|
||||
'codecname': 'Apple ProRes 422',
|
||||
'codectypename': 'Apple ProRes 422',
|
||||
'codectypecode': 'apcn',
|
||||
'codecvendorcode': 'appl',
|
||||
'spatialquality': 1024,
|
||||
'temporalquality': 0,
|
||||
'keyframerate': 0,
|
||||
'datarate': 0,
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
|
||||
}],
|
||||
'appspecificdata': [{
|
||||
'appname': 'Final Cut Pro',
|
||||
'appmanufacturer': 'Apple Inc.',
|
||||
'appversion': '7.0',
|
||||
'data': [{
|
||||
'fcpimageprocessing': [{
|
||||
'useyuv': True,
|
||||
'usesuperwhite': False,
|
||||
'rendermode': 'Float10BPP',
|
||||
}]
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
'track': [{'clipitem': [clip]} for clip in self._clips['video1']]
|
||||
}],
|
||||
'audio': [{
|
||||
'format': [{
|
||||
'samplecharacteristics': [{
|
||||
'depth': self.audiodepth,
|
||||
'samplerate': self.samplerate,
|
||||
}],
|
||||
}],
|
||||
'outputs': [{
|
||||
'group': [{
|
||||
'index': 1,
|
||||
'numchannels': 2,
|
||||
'downmix': 0,
|
||||
'channel:1': {
|
||||
'channel': [{'index': 1}]
|
||||
},
|
||||
'channel:2': {
|
||||
'channel': [{'index': 2}]
|
||||
}
|
||||
}]
|
||||
}],
|
||||
'in': -1,
|
||||
'out': -1,
|
||||
'track:1': [{'clipitem': [clip]} for clip in self._clips['audio1']]
|
||||
+ [{
|
||||
'enabled': True,
|
||||
'locked': False,
|
||||
'outputchannelindex': 1,
|
||||
}]
|
||||
,
|
||||
'track:2': [{'clipitem': [clip]} for clip in self._clips['audio2']]
|
||||
+ [{
|
||||
'enabled': True,
|
||||
'locked': False,
|
||||
'outputchannelindex': 2,
|
||||
}]
|
||||
,
|
||||
}]
|
||||
}],
|
||||
'ismasterclip': False
|
||||
})
|
||||
b = ET.SubElement(xmeml, "bin")
|
||||
ET.SubElement(b, "name").text = "Resources"
|
||||
children = ET.SubElement(b, "children")
|
||||
for clip in self._files:
|
||||
xmlroot(children, 'clip', [clip])
|
||||
|
||||
return tostring(xmeml, '<!DOCTYPE xmeml>')
|
||||
13
edl/m3u.py
Normal file
13
edl/m3u.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
class Project(object):
|
||||
def __init__(self, clips):
|
||||
self.clips = clips
|
||||
|
||||
def __str__(self):
|
||||
m3u = ['#EXTM3U']
|
||||
for clip in self.clips:
|
||||
m3u.append('#EXTINF:%d, %s' % (int(clip['duration']), clip['url']))
|
||||
m3u.append('#EXTVLCOPT:start-time=%0.3f' % clip['in'])
|
||||
m3u.append('#EXTVLCOPT:stop-time=%0.3f' % clip['out'])
|
||||
m3u.append(clip['path'])
|
||||
return '\n'.join(m3u)
|
||||
18
edl/utils.py
Normal file
18
edl/utils.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from ox.utils import ET
|
||||
from lxml import etree
|
||||
from StringIO import StringIO
|
||||
|
||||
def tostring(xml, doctype=None):
|
||||
head = '<?xml version="1.0" encoding="UTF-8" ?>'
|
||||
if doctype:
|
||||
head += '\n' + doctype
|
||||
else:
|
||||
doctype = ''
|
||||
|
||||
x = etree.parse(StringIO(ET.tostring(xml)))
|
||||
return head + '\n' \
|
||||
+ etree.tostring(x, pretty_print=True)
|
||||
|
||||
return head + '\n' \
|
||||
+ ET.tostring(xml)
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue