274 lines
9.7 KiB
Python
274 lines
9.7 KiB
Python
|
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>')
|