padmafcp/srt2fcp.py

367 lines
10 KiB
Python
Executable file

#!/usr/bin/env python3
import sys
import os
from optparse import OptionParser
import tempfile
import shutil
import math
import time
import json
import urllib.request
import urllib.parse
import urllib.error
import xml.sax.saxutils
import ox
from PIL import Image
base = os.path.abspath(os.path.dirname(__file__))
generator_template = '''<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>%(font)s</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 = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xmeml>
<xmeml version="5">'''
fcp_footer = '''</xmeml>'''
sequence_template = '''
<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')
if not os.path.exists(font):
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
font = 'Courier New'
#font = 'Lucida Grande'
def __init__(self, size=None, font=None):
if size is not None:
self.fontsize = size
if font is not None:
self.font = font
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', '&#13;&#10;')
return generator_template % {
'start': start,
'end': end,
'duration': end-start,
'text': text,
'fontsize': self.fontsize,
'font': self.font,
}
def save(self, output):
with open(output, 'w') as f:
f.write(fcp_header)
for s in self.sequences:
f.write(s)
f.write(fcp_footer)
if __name__ == '__main__':
usage = "usage: %prog srtfile"
parser = OptionParser(usage=usage)
parser.add_option('-w', '--wrap', dest='wrap', help='rewrap text', action="store_true")
parser.add_option('-s', '--size', dest='size', help='font size, default=18', type=int, default=18)
parser.add_option('-f', '--font', dest='font', help='font name, default="Courier New"', type=str, default='Courier New')
(opts, args) = parser.parse_args()
if not args:
parser.print_help()
sys.exit()
srt = args[0]
output = srt.replace('.srt', '') + '.xml'
fcp = Fcp(font=opts.font, size=int(opts.size))
fcp.add_srt(srt, opts.wrap)
fcp.save(output)