fcp2srt
This commit is contained in:
commit
00521c3dc4
5 changed files with 691 additions and 0 deletions
BIN
DejaVuSansCondensedBold.ttf
Normal file
BIN
DejaVuSansCondensedBold.ttf
Normal file
Binary file not shown.
201
fcp2edit.py
Executable file
201
fcp2edit.py
Executable file
|
@ -0,0 +1,201 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
import lxml
|
||||||
|
import lxml.etree
|
||||||
|
import ox
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
source = sys.argv[1]
|
||||||
|
data = []
|
||||||
|
tree = lxml.etree.parse(source)
|
||||||
|
|
||||||
|
api = ox.API('https://pad.ma/api/')
|
||||||
|
|
||||||
|
if os.path.exists('sot.json'):
|
||||||
|
sot = json.load(open('sot.json'))
|
||||||
|
else:
|
||||||
|
sot = api.find({
|
||||||
|
'query': {
|
||||||
|
'conditions': [{'key': 'list', 'value': 'zi:SOT'}]
|
||||||
|
},
|
||||||
|
'keys': ['id', 'title', 'duration'],
|
||||||
|
'range': [0, 5000]
|
||||||
|
})['data']['items']
|
||||||
|
with open('sot.json', 'w') as f:
|
||||||
|
json.dump(sot, f, indent=2)
|
||||||
|
|
||||||
|
def get_item(id):
|
||||||
|
id = id.replace('_', ' ').replace('.MOV', '').replace('BT2C0662 3', 'BT2C0662')
|
||||||
|
for data in sot:
|
||||||
|
if id in data['title'].replace('/', ' '):
|
||||||
|
return data['id']
|
||||||
|
for k in ('STK', 'MNK', 'BT2C'):
|
||||||
|
id = id.replace(k, '').strip()
|
||||||
|
for data in sot:
|
||||||
|
if id in data['title'].replace('/', ' '):
|
||||||
|
return data['id']
|
||||||
|
print 'missing', id
|
||||||
|
|
||||||
|
durations = {data['id']: data['duration'] for data in sot}
|
||||||
|
|
||||||
|
def parse_fps(rate):
|
||||||
|
if rate.find('ntsc').text == 'TRUE':
|
||||||
|
rate = int(rate.find('timebase').text) * 1000 / 1001
|
||||||
|
else:
|
||||||
|
rate = int(rate.find('timebase').text)
|
||||||
|
return rate
|
||||||
|
|
||||||
|
tracks = []
|
||||||
|
seq = tree.getroot()[0]
|
||||||
|
fps = parse_fps(seq.find('rate'))
|
||||||
|
v = seq.find('media').find('video')
|
||||||
|
for t in v.xpath('.//track'):
|
||||||
|
track = []
|
||||||
|
for clipitem in t.xpath('.//clipitem'):
|
||||||
|
_id = clipitem.attrib['id'].strip().split('-Apple')[0]
|
||||||
|
_id = _id.replace('Shampoo flask', '').replace(' HDR', '').replace(' hdr', '').replace('pro res 422', '').replace(' cool', '').strip()
|
||||||
|
_id = re.sub(' \d\d?$', '', _id)
|
||||||
|
|
||||||
|
#start/end - position on timeline
|
||||||
|
#in/out - in/out in clip
|
||||||
|
clip_fps = parse_fps(clipitem.find('rate'))
|
||||||
|
_in = int(clipitem.findall('in')[0].text)
|
||||||
|
_out = int(clipitem.findall('out')[0].text)
|
||||||
|
duration = _out - _in
|
||||||
|
_start = int(clipitem.findall('start')[0].text)
|
||||||
|
_end = int(clipitem.findall('end')[0].text)
|
||||||
|
if _start == -1 and _end == -1:
|
||||||
|
print 'strange', _start, _end, _in, _out, _id
|
||||||
|
continue
|
||||||
|
if _start == -1:
|
||||||
|
_start = _end - duration
|
||||||
|
elif _end == -1:
|
||||||
|
_end = _start + duration
|
||||||
|
if filter(lambda x: x <0, [_start, _end, _in, _out]):
|
||||||
|
print 'why -?', _start, _end, _in, _out, _id
|
||||||
|
if _out - _in != _end - _start:
|
||||||
|
print '??', _in, _out, _out-_in, 'vs', _start, _end, _end-_start, _id
|
||||||
|
if _start > -1 and _end > -1:
|
||||||
|
track.append({
|
||||||
|
'in': _in / fps,
|
||||||
|
'out': _out / fps,
|
||||||
|
'start': _start / fps,
|
||||||
|
'end': _end / fps,
|
||||||
|
'file': _id,
|
||||||
|
'id': get_item(_id),
|
||||||
|
'track': len(tracks)
|
||||||
|
})
|
||||||
|
if track:
|
||||||
|
tracks.append(track)
|
||||||
|
|
||||||
|
with open('/tmp/tracks.json', 'w') as f:
|
||||||
|
json.dump(tracks, f, indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
for i, track in enumerate(tracks):
|
||||||
|
with open('/tmp/tracks%s.json' % i, 'w') as f:
|
||||||
|
json.dump(track, f, indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
def flatten_tracks(tracks):
|
||||||
|
def split_at_overlaps(clip):
|
||||||
|
offset_start = clip['start']
|
||||||
|
offset_clip = clip['in']
|
||||||
|
points = [clip['start'], clip['end']]
|
||||||
|
for track in tracks:
|
||||||
|
for c in track:
|
||||||
|
if c['track'] != clip['track'] and c['start'] > -1 and c['end'] > -1:
|
||||||
|
if c['start' ] > clip['start'] and c['start'] < clip['end']:
|
||||||
|
points.append(c['start'])
|
||||||
|
if c['end' ] > clip['start'] and c['end'] < clip['end']:
|
||||||
|
points.append(c['end'])
|
||||||
|
print clip['track'], points
|
||||||
|
clips = []
|
||||||
|
for i, point in enumerate(points[:-1]):
|
||||||
|
offset_in = point - offset_start
|
||||||
|
duration = points[i+1] - point
|
||||||
|
if duration > 0:
|
||||||
|
clips.append({
|
||||||
|
'in': offset_clip + offset_in,
|
||||||
|
'out': offset_clip + offset_in + duration,
|
||||||
|
'start': point,
|
||||||
|
'end': points[i+1],
|
||||||
|
'track': clip['track'],
|
||||||
|
'id': clip['id']
|
||||||
|
})
|
||||||
|
return clips
|
||||||
|
|
||||||
|
clips = []
|
||||||
|
for track in tracks:
|
||||||
|
for clip in track:
|
||||||
|
clips += split_at_overlaps(clip)
|
||||||
|
for clip in clips:
|
||||||
|
for c in clips:
|
||||||
|
if c['track'] > clip['track']:
|
||||||
|
if c['start'] <= clip['start'] and c['end'] >= clip['end']:
|
||||||
|
clip['delete'] = True
|
||||||
|
_clips = sorted([c for c in clips if not c.get('delete')], key=lambda a: a['start'])
|
||||||
|
clips = []
|
||||||
|
for c in _clips:
|
||||||
|
if clips and clips[-1]['out'] == c['in'] and clips[-1]['id'] == c['id']:
|
||||||
|
print 'join', clips[-1], c
|
||||||
|
clips[-1]['end'] = c['end']
|
||||||
|
clips[-1]['out'] = c['out']
|
||||||
|
else:
|
||||||
|
clips.append(c)
|
||||||
|
position = None
|
||||||
|
for c in clips:
|
||||||
|
if position == None:
|
||||||
|
position = c['start']
|
||||||
|
if c['start'] != position:
|
||||||
|
print 'wrong start', c['start'], position, abs(position - c['start'])
|
||||||
|
position += c['out'] - c['in']
|
||||||
|
return clips
|
||||||
|
|
||||||
|
timeline = flatten_tracks(tracks)
|
||||||
|
pandora_edit = []
|
||||||
|
for c in timeline:
|
||||||
|
if c['id']:
|
||||||
|
if c['out'] > durations[c['id']] or c['in'] > durations[c['id']] or c['in'] < 0 or c['out'] <= c['in']:
|
||||||
|
print 'invalid in/out', c, durations[c['id']]
|
||||||
|
else:
|
||||||
|
pandora_edit.append({
|
||||||
|
'in': c['in'],
|
||||||
|
'out': c['out'],
|
||||||
|
'item': c['id']
|
||||||
|
})
|
||||||
|
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
print len(pandora_edit)
|
||||||
|
#print json.dumps(timeline, indent=2, sort_keys=True)
|
||||||
|
print json.dumps(pandora_edit, indent=2, sort_keys=True)
|
||||||
|
with open(os.path.expanduser('~/.ox/client.padma.json')) as f:
|
||||||
|
settings = json.load(f)
|
||||||
|
r = api.signin(username=settings['username'], password=settings['password'])
|
||||||
|
assert(r['status']['code'] == 200)
|
||||||
|
print 'add clips', len(pandora_edit)
|
||||||
|
#print pandora_edit
|
||||||
|
#r = api.addEdit({
|
||||||
|
# 'name': 'Ship of Theseus',
|
||||||
|
# 'clips': pandora_edit
|
||||||
|
#})
|
||||||
|
#print r['data'].get('id') or r
|
||||||
|
clips = [c['id'] for c in api.getEdit({'id': 'j:Ship of Theseus', 'keys': ['clips']})['data']['clips']]
|
||||||
|
if clips:
|
||||||
|
api.removeClips({
|
||||||
|
'edit': 'j:Ship of Theseus',
|
||||||
|
'ids': clips
|
||||||
|
})
|
||||||
|
step = 100
|
||||||
|
while pandora_edit:
|
||||||
|
clips = pandora_edit[:step]
|
||||||
|
pandora_edit = pandora_edit[step:]
|
||||||
|
print 'add', len(clips), 'todo', len(pandora_edit)
|
||||||
|
r = api.addClips({
|
||||||
|
'edit': 'j:Ship of Theseus',
|
||||||
|
'clips': clips
|
||||||
|
})
|
||||||
|
print 'total added', len(r and r.get('data', {}).get('clips') or 0)
|
91
fcp2json.py
Executable file
91
fcp2json.py
Executable file
|
@ -0,0 +1,91 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
import lxml
|
||||||
|
import lxml.etree
|
||||||
|
import ox
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
fps = 25
|
||||||
|
tree = lxml.etree.parse(sys.argv[1])
|
||||||
|
source = sys.argv[1]
|
||||||
|
target = '%s.json' % os.path.splitext(source)[0]
|
||||||
|
data = {
|
||||||
|
'descriptions': [],
|
||||||
|
'transcripts': []
|
||||||
|
}
|
||||||
|
for g in tree.xpath('//generatoritem'):
|
||||||
|
#start/end = relative position of the clip in the parent sequence.
|
||||||
|
#in/out indicate the portion of the source media file to reference.
|
||||||
|
_in = int(g.findall('in')[0].text)
|
||||||
|
_out = int(g.findall('out')[0].text)
|
||||||
|
_start = int(g.findall('start')[0].text)
|
||||||
|
_end = int(g.findall('end')[0].text)
|
||||||
|
effect = g.findall('effect')
|
||||||
|
assert len(effect) == 1
|
||||||
|
for parameter in effect[0].findall('parameter'):
|
||||||
|
if parameter.findall('parameterid')[0].text == 'str':
|
||||||
|
value = parameter.findall('value')[0].text
|
||||||
|
|
||||||
|
if _start == -1 and _end == -1:
|
||||||
|
_start = _in
|
||||||
|
_end = _out
|
||||||
|
if _start == -1:
|
||||||
|
_start = 0
|
||||||
|
#print _in, _out, _start, _end, value
|
||||||
|
value = '<br>\n'.join([v.strip() for v in value.strip().split('\r')])
|
||||||
|
data['transcripts'].append({
|
||||||
|
'in': _start/fps, 'out': (_end-1)/fps, 'value': value
|
||||||
|
})
|
||||||
|
|
||||||
|
_last = 0
|
||||||
|
for g in tree.xpath('//clipitem'):
|
||||||
|
#in/out indicate the portion of the source media file to reference.
|
||||||
|
#start/end = relative position of the clip in the parent sequence.
|
||||||
|
_in = int(g.findall('in')[0].text) / fps
|
||||||
|
_out = int(g.findall('out')[0].text) / fps
|
||||||
|
_start = int(g.findall('start')[0].text) / fps
|
||||||
|
_end = int(g.findall('end')[0].text) / fps
|
||||||
|
name= g.findall('name')[0].text.strip()
|
||||||
|
#print _in, _out, _start, _end, name
|
||||||
|
if _start == -0.04:
|
||||||
|
_start = _last
|
||||||
|
if _end == -0.04:
|
||||||
|
_end = _start + (_out - _in)
|
||||||
|
name = name.replace('.dv', '').replace('_ ', ': ')
|
||||||
|
id = name.replace(' ', '%20')
|
||||||
|
value = 'Source: <a href="/%s/%.3f,%.3f">%s/%s-%s</a>' % (id, _in, _out, name, ox.formatDuration(_in), ox.formatDuration(_out))
|
||||||
|
data['descriptions'].append({
|
||||||
|
'in': _start, 'out': _end-0.04, 'value': value
|
||||||
|
})
|
||||||
|
_last = _end
|
||||||
|
|
||||||
|
with open(target, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
'''
|
||||||
|
import os
|
||||||
|
import ox
|
||||||
|
|
||||||
|
with open(os.path.expanduser('~/.ox/client.json')) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
api = ox.API('https://pad.ma/api/')
|
||||||
|
r = api.signin(username=config['username'], password=config['password'])
|
||||||
|
assert(r['status']['code'] == 200)
|
||||||
|
assert(r['data']['user'] != '')
|
||||||
|
for s in data['descriptions']:
|
||||||
|
s['item'] = 'BHK'
|
||||||
|
s['layer'] = 'descriptions'
|
||||||
|
print s
|
||||||
|
r = api.addAnnotation(s)
|
||||||
|
assert(r['status']['code'] == 200)
|
||||||
|
for s in data['transcripts']:
|
||||||
|
s['item'] = 'BHK'
|
||||||
|
s['layer'] = 'transcripts'
|
||||||
|
print s
|
||||||
|
r = api.addAnnotation(s)
|
||||||
|
assert(r['status']['code'] == 200)
|
||||||
|
'''
|
43
fcp2srt.py
Executable file
43
fcp2srt.py
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
import lxml
|
||||||
|
import lxml.etree
|
||||||
|
import ox
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
source = sys.argv[1]
|
||||||
|
target = '%s.srt' % os.path.splitext(source)[0]
|
||||||
|
fps = 25
|
||||||
|
data = []
|
||||||
|
tree = lxml.etree.parse(source)
|
||||||
|
|
||||||
|
for g in tree.xpath('//generatoritem'):
|
||||||
|
#start/end = relative position of the clip in the parent sequence.
|
||||||
|
#in/out indicate the portion of the source media file to reference.
|
||||||
|
_in = int(g.findall('in')[0].text)
|
||||||
|
_out = int(g.findall('out')[0].text)
|
||||||
|
_start = int(g.findall('start')[0].text)
|
||||||
|
_end = int(g.findall('end')[0].text)
|
||||||
|
effect = g.findall('effect')
|
||||||
|
assert len(effect) == 1
|
||||||
|
for parameter in effect[0].findall('parameter'):
|
||||||
|
if parameter.findall('parameterid')[0].text == 'str':
|
||||||
|
value = parameter.findall('value')[0].text
|
||||||
|
|
||||||
|
if _start == -1 and _end == -1:
|
||||||
|
_start = _in
|
||||||
|
_end = _out
|
||||||
|
if _start == -1:
|
||||||
|
_start = 0
|
||||||
|
#print _in, _out, _start, _end, value
|
||||||
|
value = '\n'.join([v.strip() for v in value.strip().split('\r')])
|
||||||
|
value = value.replace('\n\n', '<br><br>\n')
|
||||||
|
data.append({
|
||||||
|
'in': _start/fps, 'out': (_end-1)/fps, 'value': value
|
||||||
|
})
|
||||||
|
|
||||||
|
with open(target, 'w') as f:
|
||||||
|
f.write(ox.srt.encode(data))
|
356
srt2fcp.py
Executable file
356
srt2fcp.py
Executable file
|
@ -0,0 +1,356 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import division
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from optparse import OptionParser
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import urllib
|
||||||
|
import xml.sax.saxutils
|
||||||
|
|
||||||
|
import ox
|
||||||
|
import Image
|
||||||
|
|
||||||
|
base = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
generator_template = u'''<generatoritem id="Text">
|
||||||
|
<name>Text</name>
|
||||||
|
<duration>3000</duration>
|
||||||
|
<rate>
|
||||||
|
<ntsc>FALSE</ntsc>
|
||||||
|
<timebase>25</timebase>
|
||||||
|
</rate>
|
||||||
|
<in>0</in>
|
||||||
|
<out>%(duration)s</out>
|
||||||
|
<start>%(start)s</start>
|
||||||
|
<end>%(end)s</end>
|
||||||
|
<enabled>TRUE</enabled>
|
||||||
|
<anamorphic>FALSE</anamorphic>
|
||||||
|
<alphatype>black</alphatype>
|
||||||
|
<effect>
|
||||||
|
<name>Text</name>
|
||||||
|
<effectid>Text</effectid>
|
||||||
|
<effectcategory>Text</effectcategory>
|
||||||
|
<effecttype>generator</effecttype>
|
||||||
|
<mediatype>video</mediatype>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>str</parameterid>
|
||||||
|
<name>Text</name>
|
||||||
|
<value>%(text)s</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>fontname</parameterid>
|
||||||
|
<name>Font</name>
|
||||||
|
<value>Courier New</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>fontsize</parameterid>
|
||||||
|
<name>Size</name>
|
||||||
|
<valuemin>0</valuemin>
|
||||||
|
<valuemax>1000</valuemax>
|
||||||
|
<value>%(fontsize)s</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>fontstyle</parameterid>
|
||||||
|
<name>Style</name>
|
||||||
|
<valuemin>1</valuemin>
|
||||||
|
<valuemax>4</valuemax>
|
||||||
|
<valuelist>
|
||||||
|
<valueentry>
|
||||||
|
<name>Plain</name>
|
||||||
|
<value>1</value>
|
||||||
|
</valueentry>
|
||||||
|
<valueentry>
|
||||||
|
<name>Bold</name>
|
||||||
|
<value>2</value>
|
||||||
|
</valueentry>
|
||||||
|
<valueentry>
|
||||||
|
<name>Italic</name>
|
||||||
|
<value>3</value>
|
||||||
|
</valueentry>
|
||||||
|
<valueentry>
|
||||||
|
<name>Bold/Italic</name>
|
||||||
|
<value>4</value>
|
||||||
|
</valueentry>
|
||||||
|
</valuelist>
|
||||||
|
<value>1</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>fontalign</parameterid>
|
||||||
|
<name>Alignment</name>
|
||||||
|
<valuemin>1</valuemin>
|
||||||
|
<valuemax>3</valuemax>
|
||||||
|
<valuelist>
|
||||||
|
<valueentry>
|
||||||
|
<name>Left</name>
|
||||||
|
<value>1</value>
|
||||||
|
</valueentry>
|
||||||
|
<valueentry>
|
||||||
|
<name>Center</name>
|
||||||
|
<value>2</value>
|
||||||
|
</valueentry>
|
||||||
|
<valueentry>
|
||||||
|
<name>Right</name>
|
||||||
|
<value>3</value>
|
||||||
|
</valueentry>
|
||||||
|
</valuelist>
|
||||||
|
<value>1</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>fontcolor</parameterid>
|
||||||
|
<name>Font Color</name>
|
||||||
|
<value>
|
||||||
|
<alpha>255</alpha>
|
||||||
|
<red>255</red>
|
||||||
|
<green>255</green>
|
||||||
|
<blue>255</blue>
|
||||||
|
</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>origin</parameterid>
|
||||||
|
<name>Origin</name>
|
||||||
|
<value>
|
||||||
|
<horiz>-0.402778</horiz>
|
||||||
|
<vert>-0.217014</vert>
|
||||||
|
</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>fonttrack</parameterid>
|
||||||
|
<name>Tracking</name>
|
||||||
|
<valuemin>-200</valuemin>
|
||||||
|
<valuemax>200</valuemax>
|
||||||
|
<value>1</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>leading</parameterid>
|
||||||
|
<name>Leading</name>
|
||||||
|
<valuemin>-100</valuemin>
|
||||||
|
<valuemax>100</valuemax>
|
||||||
|
<value>0</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>aspect</parameterid>
|
||||||
|
<name>Aspect</name>
|
||||||
|
<valuemin>0.1</valuemin>
|
||||||
|
<valuemax>5</valuemax>
|
||||||
|
<value>1</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>autokern</parameterid>
|
||||||
|
<name>Auto Kerning</name>
|
||||||
|
<value>TRUE</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>subpixel</parameterid>
|
||||||
|
<name>Use Subpixel</name>
|
||||||
|
<value>TRUE</value>
|
||||||
|
</parameter>
|
||||||
|
</effect>
|
||||||
|
<filter>
|
||||||
|
<effect>
|
||||||
|
<name>Basic Motion</name>
|
||||||
|
<effectid>basic</effectid>
|
||||||
|
<effectcategory>motion</effectcategory>
|
||||||
|
<effecttype>motion</effecttype>
|
||||||
|
<mediatype>video</mediatype>
|
||||||
|
<parameter>
|
||||||
|
<parameterid>center</parameterid>
|
||||||
|
<name>Center</name>
|
||||||
|
<value>
|
||||||
|
<horiz>0</horiz>
|
||||||
|
<vert>-0.00315457</vert>
|
||||||
|
</value>
|
||||||
|
</parameter>
|
||||||
|
</effect>
|
||||||
|
</filter>
|
||||||
|
<sourcetrack>
|
||||||
|
<mediatype>video</mediatype>
|
||||||
|
</sourcetrack>
|
||||||
|
<itemhistory>
|
||||||
|
<uuid>BAA51DEC-ECB0-4879-9910-8E83B0EF7C1B</uuid>
|
||||||
|
</itemhistory>
|
||||||
|
</generatoritem>
|
||||||
|
'''
|
||||||
|
|
||||||
|
fcp_header = u'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE xmeml>
|
||||||
|
<xmeml version="5">'''
|
||||||
|
fcp_footer = u'''</xmeml>'''
|
||||||
|
|
||||||
|
sequence_template = u'''
|
||||||
|
<sequence id="%(id)s ">
|
||||||
|
<uuid>72DC4146-6224-4400-BAAC-2AB6E0D3D292</uuid>
|
||||||
|
<updatebehavior>add</updatebehavior>
|
||||||
|
<name>%(id)s</name>
|
||||||
|
<duration>%(duration)s</duration>
|
||||||
|
<rate>
|
||||||
|
<ntsc>FALSE</ntsc>
|
||||||
|
<timebase>25</timebase>
|
||||||
|
</rate>
|
||||||
|
<timecode>
|
||||||
|
<rate>
|
||||||
|
<ntsc>FALSE</ntsc>
|
||||||
|
<timebase>25</timebase>
|
||||||
|
</rate>
|
||||||
|
<string>01:00:00:00</string>
|
||||||
|
<frame>90000</frame>
|
||||||
|
<source>source</source>
|
||||||
|
<displayformat>NDF</displayformat>
|
||||||
|
</timecode>
|
||||||
|
<in>-1</in>
|
||||||
|
<out>-1</out>
|
||||||
|
<media>
|
||||||
|
<video>
|
||||||
|
<format>
|
||||||
|
<samplecharacteristics>
|
||||||
|
<width>720</width>
|
||||||
|
<height>576</height>
|
||||||
|
<anamorphic>FALSE</anamorphic>
|
||||||
|
<pixelaspectratio>PAL-601</pixelaspectratio>
|
||||||
|
<fielddominance>none</fielddominance>
|
||||||
|
<rate>
|
||||||
|
<ntsc>FALSE</ntsc>
|
||||||
|
<timebase>25</timebase>
|
||||||
|
</rate>
|
||||||
|
<colordepth>24</colordepth>
|
||||||
|
<codec>
|
||||||
|
<name>Apple DV - PAL</name>
|
||||||
|
<appspecificdata>
|
||||||
|
<appname>Final Cut Pro</appname>
|
||||||
|
<appmanufacturer>Apple Inc.</appmanufacturer>
|
||||||
|
<appversion>7.0</appversion>
|
||||||
|
<data>
|
||||||
|
<qtcodec>
|
||||||
|
<codecname>Apple DV - PAL</codecname>
|
||||||
|
<codectypename>DV - PAL</codectypename>
|
||||||
|
<codectypecode>dvcp</codectypecode>
|
||||||
|
<codecvendorcode>appl</codecvendorcode>
|
||||||
|
<spatialquality>1023</spatialquality>
|
||||||
|
<temporalquality>0</temporalquality>
|
||||||
|
<keyframerate>0</keyframerate>
|
||||||
|
<datarate>0</datarate>
|
||||||
|
</qtcodec>
|
||||||
|
</data>
|
||||||
|
</appspecificdata>
|
||||||
|
</codec>
|
||||||
|
</samplecharacteristics>
|
||||||
|
<appspecificdata>
|
||||||
|
<appname>Final Cut Pro</appname>
|
||||||
|
<appmanufacturer>Apple Inc.</appmanufacturer>
|
||||||
|
<appversion>7.0</appversion>
|
||||||
|
<data>
|
||||||
|
<fcpimageprocessing>
|
||||||
|
<useyuv>TRUE</useyuv>
|
||||||
|
<usesuperwhite>FALSE</usesuperwhite>
|
||||||
|
<rendermode>YUV8BPP</rendermode>
|
||||||
|
</fcpimageprocessing>
|
||||||
|
</data>
|
||||||
|
</appspecificdata>
|
||||||
|
</format>
|
||||||
|
<track>
|
||||||
|
%(subs)s
|
||||||
|
<enabled>TRUE</enabled>
|
||||||
|
<locked>FALSE</locked>
|
||||||
|
</track>
|
||||||
|
</video>
|
||||||
|
</media>
|
||||||
|
<ismasterclip>FALSE</ismasterclip>
|
||||||
|
</sequence>'''
|
||||||
|
|
||||||
|
def wrap_text(start, end, text, fontsize=30):
|
||||||
|
margin = 40
|
||||||
|
width = 640
|
||||||
|
#font = os.path.join(base, 'DejaVuSansCondensedBold.ttf')
|
||||||
|
font = '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono-Bold.ttf'
|
||||||
|
|
||||||
|
n = 10
|
||||||
|
alltxt = []
|
||||||
|
text = ox.strip_tags(ox.decode_html(text))
|
||||||
|
for t in text.strip().split('\n'):
|
||||||
|
lines = ox.wrapText(t, width - 2 * margin, 20, font, fontsize)
|
||||||
|
for line in lines:
|
||||||
|
alltxt.append(line)
|
||||||
|
alltxt.append('')
|
||||||
|
|
||||||
|
pages = int(math.ceil(len(alltxt) / n))
|
||||||
|
lines_per_page = int(math.ceil(len(alltxt) / pages))
|
||||||
|
frames_per_page = int((end - start) / pages)
|
||||||
|
pos = start
|
||||||
|
r = []
|
||||||
|
for p in range(0, len(alltxt), lines_per_page):
|
||||||
|
txt = alltxt[p:p+lines_per_page]
|
||||||
|
r.append((pos, pos+frames_per_page, txt))
|
||||||
|
pos += frames_per_page
|
||||||
|
return r
|
||||||
|
|
||||||
|
class Fcp:
|
||||||
|
sequences = []
|
||||||
|
fps = 25
|
||||||
|
fontsize = 18
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_srt(self, srt, wrap=True):
|
||||||
|
data = ox.srt.load(srt)
|
||||||
|
#info = api.get(id=item_id, keys=['layers'])
|
||||||
|
subs = []
|
||||||
|
#for s in info['data']['layers']['transcripts']:
|
||||||
|
duration = -1
|
||||||
|
for s in data:
|
||||||
|
value = s['value'].replace('<br/>', '\n').replace('<br>', '\n')
|
||||||
|
value = ox.strip_tags(value)
|
||||||
|
start = int(s['in'] * fcp.fps)
|
||||||
|
end = int(s['out'] * fcp.fps)
|
||||||
|
if start < duration:
|
||||||
|
print "warning", start, '<', duration, value
|
||||||
|
start = duration
|
||||||
|
duration = end
|
||||||
|
|
||||||
|
if wrap:
|
||||||
|
for t in wrap_text(start, end, value, self.fontsize):
|
||||||
|
subs.append(t)
|
||||||
|
else:
|
||||||
|
subs.append((start, end, value.split('\n')))
|
||||||
|
self.sequences.append(sequence_template % {
|
||||||
|
'id': os.path.splitext(os.path.basename(srt))[0],
|
||||||
|
'duration': duration,
|
||||||
|
'subs': '\n'.join([self.sub(*s) for s in subs])
|
||||||
|
})
|
||||||
|
|
||||||
|
def sub(self, start, end, text):
|
||||||
|
text = xml.sax.saxutils.escape('\n'.join(text).strip()).replace('\n', ' ')
|
||||||
|
return generator_template % {
|
||||||
|
'start': start,
|
||||||
|
'end': end,
|
||||||
|
'duration': end-start,
|
||||||
|
'text': text,
|
||||||
|
'fontsize': self.fontsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
def save(self, output):
|
||||||
|
with open(output, 'w') as f:
|
||||||
|
f.write(fcp_header.encode('utf-8'))
|
||||||
|
for s in self.sequences:
|
||||||
|
f.write(s.encode('utf-8'))
|
||||||
|
f.write(fcp_footer.encode('utf-8'))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
usage = "usage: %prog srtfile"
|
||||||
|
parser = OptionParser(usage=usage)
|
||||||
|
parser.add_option('-w', '--wrap', dest='wrap', help='rewrap text', action="store_true")
|
||||||
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
srt = args[0]
|
||||||
|
output = srt.replace('.srt', '') + '.xml'
|
||||||
|
fcp = Fcp()
|
||||||
|
fcp.add_srt(srt, opts.wrap)
|
||||||
|
fcp.save(output)
|
Loading…
Reference in a new issue