padmafcp/srt2fcp.py

368 lines
10 KiB
Python
Raw Normal View History

2018-09-09 12:16:02 +00:00
#!/usr/bin/env python3
2018-09-09 12:09:55 +00:00
import sys
import os
from optparse import OptionParser
import tempfile
import shutil
import math
import time
import json
2018-09-09 12:16:02 +00:00
import urllib.request
import urllib.parse
import urllib.error
2018-09-09 12:09:55 +00:00
import xml.sax.saxutils
import ox
2018-09-09 12:16:02 +00:00
from PIL import Image
2018-09-09 12:09:55 +00:00
base = os.path.abspath(os.path.dirname(__file__))
2018-09-09 12:16:02 +00:00
generator_template = '''<generatoritem id="Text">
2018-09-09 12:09:55 +00:00
<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>
2018-09-09 12:26:21 +00:00
<value>%(font)s</value>
2018-09-09 12:09:55 +00:00
</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>
'''
2018-09-09 12:16:02 +00:00
fcp_header = '''<?xml version="1.0" encoding="UTF-8"?>
2018-09-09 12:09:55 +00:00
<!DOCTYPE xmeml>
<xmeml version="5">'''
2018-09-09 12:16:02 +00:00
fcp_footer = '''</xmeml>'''
2018-09-09 12:09:55 +00:00
2018-09-09 12:16:02 +00:00
sequence_template = '''
2018-09-09 12:09:55 +00:00
<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
2018-09-09 12:44:48 +00:00
font = os.path.join(base, 'DejaVuSansCondensedBold.ttf')
if not os.path.exists(font):
font = '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono-Bold.ttf'
2018-09-09 12:09:55 +00:00
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
2018-09-09 12:26:21 +00:00
font = 'Courier New'
#font = 'Lucida Grande'
2018-09-09 12:09:55 +00:00
2018-09-09 12:44:48 +00:00
def __init__(self, size=None, font=None):
if size is not None:
self.fontsize = size
if font is not None:
self.font = font
2018-09-09 12:09:55 +00:00
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:
2018-09-09 12:16:02 +00:00
print("warning", start, '<', duration, value)
2018-09-09 12:09:55 +00:00
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,
2018-09-09 12:26:21 +00:00
'font': self.font,
2018-09-09 12:09:55 +00:00
}
def save(self, output):
with open(output, 'w') as f:
2018-09-09 12:34:11 +00:00
f.write(fcp_header)
2018-09-09 12:09:55 +00:00
for s in self.sequences:
2018-09-09 12:34:11 +00:00
f.write(s)
f.write(fcp_footer)
2018-09-09 12:09:55 +00:00
2018-09-09 12:44:48 +00:00
2018-09-09 12:09:55 +00:00
if __name__ == '__main__':
usage = "usage: %prog srtfile"
parser = OptionParser(usage=usage)
parser.add_option('-w', '--wrap', dest='wrap', help='rewrap text', action="store_true")
2018-09-09 12:44:48 +00:00
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')
2018-09-09 12:09:55 +00:00
(opts, args) = parser.parse_args()
2018-09-09 12:44:48 +00:00
2018-09-09 12:09:55 +00:00
if not args:
parser.print_help()
sys.exit()
2018-09-09 12:44:48 +00:00
2018-09-09 12:09:55 +00:00
srt = args[0]
output = srt.replace('.srt', '') + '.xml'
2018-09-09 12:44:48 +00:00
fcp = Fcp(font=opts.font, size=int(opts.size))
2018-09-09 12:09:55 +00:00
fcp.add_srt(srt, opts.wrap)
fcp.save(output)