This python module runs an x264 encoder and sends it frames generated from LaTeX source files or loaded as images, to output a compressed video stream.
Instantiate the class passing in a configuration list and the width and height for output (which must both be multiples of two). The configuration list has dictionaries specifying scenes as its elements. Each scene dictionary should have a key 'type' associated with either 'images' or 'static text'. The tool reads each scene in turn and renders it to the output file. Read the source code to see which keys need to be present for each of the two scene types.
More information coming soon ...
Code samples on this website come without any guarantees – use them at your own risk! I am grateful for comments or patches sent to my email address: first name.last name@cl.cam.ac.uk.
#!/opt/local/bin/python
from PIL import Image
import os
x264_path = '/path/to/x264'
yuv_fifo = '/tmp/vidauthor_fifo.yuv'
temp_dir = '/tmp/'
temp_tex_file = 'temp_texsource.tex'
temp_png_file = temp_tex_file.replace('.tex', '.png')
temp_tex_file_path = os.path.join(temp_dir, temp_tex_file)
temp_pdf_file_path = os.path.join(temp_dir, temp_tex_file.replace('.tex', '.pdf'))
temp_png_file_path = os.path.join(temp_dir, temp_tex_file.replace('.tex', '.png'))
class vidauthor:
def __init__(self, scenes, video_width, video_height):
self.scenes = scenes
self.video_width = video_width
self.video_height = video_height
self.ar = float(self.video_height) / float(self.video_width)
def tex_to_png(self, title, content_file_name):
content = ''
if content_file_name != None:
content = open(content_file_name).read()
f = open(temp_tex_file_path, 'w')
latex_width_cm = int((float(self.video_width) / 1280.0) * 32.0)
latex_height_cm = int(latex_width_cm * self.ar)
f.write(r"""\documentclass{article}
\usepackage{url}
\pdfpagewidth %dcm
\pdfpageheight %dcm
\setlength{\textwidth}{%dcm}
\setlength{\oddsidemargin}{-0.8in}
\setlength{\evensidemargin}{\oddsidemargin}
\setlength{\topmargin}{0mm}
\setlength{\topmargin}{0mm}
\setlength{\headheight}{0mm}
\setlength{\headsep}{0mm}
\setlength{\footskip}{0mm}
\setlength{\parindent}{0mm}
\renewcommand\familydefault{\sfdefault}
\begin{document}
\begin{center} \Huge\textbf{%s} \end{center}
\vspace{0.3cm}
\Huge
%s
\end{document}
""" % (latex_width_cm, latex_height_cm, latex_width_cm - 1, title, content,))
f.close()
os.system('pdflatex -output-directory %s %s' % (temp_dir, temp_tex_file_path,))
os.system('convert -density 600x600 %s -resize %dx -density 600x600 %s' % (temp_pdf_file_path, self.video_width, temp_png_file_path,))
def generate_video(self, video_file_path):
# Create the YUV fifo if it doesn't exist.
if not os.path.exists(yuv_fifo):
os.mkfifo(yuv_fifo)
# Start x264.
os.system('%s -o %s %s %dx%d &' % (x264_path, video_file_path, yuv_fifo, self.video_width, self.video_height,))
f = open(yuv_fifo, 'wb')
def append_image(image_file_name):
# Open the image and convert from RGB to YCbCr
im = Image.open(image_file_name)
y,cb,cr = im.convert('YCbCr').split()
# Down-sample chroma
cb = cb.resize((cb.size[0] / 2, cb.size[1] / 2), Image.BILINEAR)
cr = cr.resize((cr.size[0] / 2, cr.size[1] / 2), Image.BILINEAR)
# Write raw data to the YUV file
f.write(y.tostring("raw", "L"))
f.write(cb.tostring("raw", "L"))
f.write(cr.tostring("raw", "L"))
# Concatenate the scenes in order
for i, s in enumerate(self.scenes):
if s.has_key('disable') and s['disable'] == True:
continue
if s['type'] == 'images':
source_path = s['source path']
frame_to_name = s['frame to name']
present_to_frame = s['present to frame']
present_count = s['present count']
last_frame_no = -1
present_no = 0
for present_no in range(0, present_count):
# Convert from the current (presentation) frame number to the frame number in the scene
frame_no = present_to_frame(present_no)
image_file_name = os.path.join(source_path, frame_to_name(frame_no))
if not os.path.exists(image_file_name):
break
append_image(image_file_name)
if s.has_key('final frame duration'):
frame_no = present_to_frame(present_no - 1)
image_file_name = os.path.join(source_path, frame_to_name(frame_no))
for i in range(1, s['final frame duration'] - 1):
append_image(image_file_name)
elif s['type'] == 'static text':
tex_source_path = s['source path']
present_count = s['present count']
self.tex_to_png(s['scene title'], tex_source_path)
for i in range(0, present_count):
append_image(temp_png_file_path)
# Flush data to the YUV file
f.close()