373 lines
10 KiB
Python
Executable file
373 lines
10 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import os
|
|
from optparse import OptionParser
|
|
import json
|
|
import math
|
|
import shutil
|
|
import tempfile
|
|
import time
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
import uuid
|
|
import xml.sax.saxutils
|
|
|
|
import ox
|
|
from PIL import Image
|
|
|
|
base = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
generator_template = '''<generatoritem id="Text%(id)s">
|
|
<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>$(uuid)s</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>$(uuid)s</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>1920</width>
|
|
<height>1080</height>
|
|
<anamorphic>FALSE</anamorphic>
|
|
<pixelaspectratio>square</pixelaspectratio>
|
|
<fielddominance>none</fielddominance>
|
|
<rate>
|
|
<ntsc>FALSE</ntsc>
|
|
<timebase>25</timebase>
|
|
</rate>
|
|
<colordepth>24</colordepth>
|
|
<codec>
|
|
<name>Apple ProRes 422</name>
|
|
<appspecificdata>
|
|
<appname>Final Cut Pro</appname>
|
|
<appmanufacturer>Apple Inc.</appmanufacturer>
|
|
<appversion>7.0</appversion>
|
|
<data>
|
|
<qtcodec>
|
|
<codecname>Apple ProRes 422</codecname>
|
|
<codectypename>Apple ProRes 422</codectypename>
|
|
<codectypecode>apcn</codectypecode>
|
|
<codecvendorcode>appl</codecvendorcode>
|
|
<spatialquality>1024</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 = 1920 - 240
|
|
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'
|
|
gid = 0
|
|
|
|
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],
|
|
'uuid': str(uuid.uuid1()).upper(),
|
|
'duration': duration,
|
|
'subs': '\n'.join([self.sub(*s) for s in subs])
|
|
})
|
|
|
|
def sub(self, start, end, text):
|
|
self.gid += 1
|
|
text = xml.sax.saxutils.escape('\n'.join(text).strip()).replace('\n', ' ')
|
|
return generator_template % {
|
|
'id': self.gid,
|
|
'uuid': str(uuid.uuid1()).upper(),
|
|
'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)
|