Welcome, guest | Sign In | My Account | Store | Cart
#!/usr/bin/python

import os, sys, threading, time

# Dimensions for recommended povray rendering
recommended_width
, recommended_height = 751, 459
povray_aspect_ratio
= (1. * recommended_width) / recommended_height

def set_resolution(w, h):
   
global mpeg_width, mpeg_height, povray_width, povray_height
   
# mpeg_height and mpeg_width must both be even to make mpeg2encode
   
# happy. The aspect ratio for video should be 4:3.
   
def even(x):
       
return int(x) & -2
    mpeg_width
= even(w)
    mpeg_height
= even(h)
    povray_height
= mpeg_height
    povray_width
= int(povray_aspect_ratio * povray_height)

def set_width(w):
    set_resolution
(w, (3.0 / 4.0) * w)
def set_height(h):
    set_resolution
((4.0 / 3.0) * h, h)

set_resolution
(600, 450)

worker_list
= [
   
('localhost', '/tmp/mpeg'),
   
('server', '/tmp/mpeg'),
   
('laptop', '/tmp/mpeg'),
   
('mac', '/Users/wware/tmp')
   
]

bitrate
= 6.0e6

framelimit
= None

povray_pretty
= True

border
= None

####################
#                  #
#   DEBUG STUFF    #
#                  #
####################

DEBUG
= False

def linenum(*args):
   
try:
       
raise Exception
   
except:
        tb
= sys.exc_info()[2]
        f
= tb.tb_frame.f_back
       
print f.f_code.co_filename, f.f_code.co_name, f.f_lineno,
   
if len(args) > 0:
       
print ' --> ',
       
for x in args:
           
print x,
   
print

def do(cmd, howfarback=0):
   
if DEBUG:
       
if False:
           
try:
               
raise Exception
           
except:
                tb
= sys.exc_info()[2]
                f
= tb.tb_frame.f_back
               
for i in range(howfarback):
                    f
= f.f_back
               
print f.f_code.co_filename, f.f_code.co_name, f.f_lineno
       
print cmd
   
if os.system(cmd) != 0:
       
raise Exception(cmd)

############################
#                          #
#    DISTRIBUTED POVRAY    #
#                          #
############################

_which_povray_job
= 0

class PovrayJob:
   
def __init__(self, srcdir, dstdir, povfmt, povmin, povmax_plus_one, yuv,
                 pwidth
, pheight, ywidth, yheight, textlist):

       
assert povfmt[-4:] == '.pov'
       
assert yuv[-4:] == '.yuv'
       
self.srcdir = srcdir
       
self.dstdir = dstdir
       
self.povfmt = povfmt
       
self.povmin = povmin
       
self.povmax_plus_one = povmax_plus_one
       
self.yuv = yuv
       
self.pwidth = pwidth
       
self.pheight = pheight
       
self.ywidth = ywidth
       
self.yheight = yheight
       
self.textlist = textlist

   
def go(self, machine, workdir):

       
local = machine in ('localhost', '127.0.0.1')

       
def worker_do(cmd):
           
if DEBUG: print '[[%s]]' % machine,
           
if local:
               
# do stuff on this machine
               
do(cmd, howfarback=1)
           
else:
               
# do stuff on a remote machine
               
do('ssh %s "%s"' % (machine, cmd), howfarback=1)

       
if povray_pretty:
            povray_options
= '+A -V -D +X'
       
else:
            povray_options
= '-A +Q0 -V -D +X'

        worker_do
('mkdir -p ' + workdir)
       
# worker_do('find %s -type f -exec rm -f {} \;' % workdir)

       
#
       
# Create a shell script to run on the worker machine
       
#
       
global _which_povray_job
       
self.scriptname = 'povray_job_%08d.sh' % _which_povray_job
        _which_povray_job
+= 1
        video_aspect_ratio
= 4.0 / 3.0
        w2
= int(video_aspect_ratio * self.pheight)
        jpg
= (self.povfmt % self.povmin)[:-4] + '.jpg'
        tgalist
= ''
        povlist
= ''
        scriptlines
= [ ]
        scriptlines
.append('cd %s' % workdir)
       
# Worker machine renders a bunch of pov files to tga files
       
for i in range(self.povmin, self.povmax_plus_one):
            pov
= self.povfmt % i
            povlist
+= ' ' + pov
            tga
= pov[:-4] + '.tga'
            tgalist
+= ' ' + tga
            scriptlines
.append('povray +I%s +O%s +FT %s +W%d +H%d 2>/dev/null' %
                             
(pov, tga, povray_options, self.pwidth, self.pheight))
       
# Worker machine averages the tga files into one jpeg file
        scriptlines
.append('convert -average %s -crop %dx%d+%d+0 -geometry %dx%d! %s' %
                           
(tgalist, w2, self.pheight, (self.pwidth - w2) / 2,
                           
self.ywidth, self.yheight, jpg))
       
# Worker cleans up the pov and tga files, no longer needed
        scriptlines
.append('rm -f %s %s' %
                           
(povlist, tgalist))
       
if DEBUG:
           
for line in scriptlines:
               
print machine + '>>> ' + line
        shellscript
= open(os.path.join(self.srcdir, self.scriptname), 'w')
       
for line in scriptlines:
            shellscript
.write(line + '\n')
        shellscript
.close()

       
#
       
# Copy shell script and pov files to worker
       
#
       
if local:
            cmd
= ('(cd %s; tar cf - %s %s) | (cd %s; tar xf -)' %
                   
(self.srcdir, self.scriptname, povlist, workdir))
       
else:
            cmd
= ('(cd %s; tar cf - %s %s) | gzip | ssh %s "(cd %s; gunzip | tar xf -)"' %
                   
(self.srcdir, self.scriptname, povlist, machine, workdir))
       
do(cmd)
       
do('rm -f ' + os.path.join(self.srcdir, self.scriptname))
        worker_do
('chmod +x ' + os.path.join(workdir, self.scriptname))

       
#
       
# Run the shell script on the worker
       
#
        worker_do
(os.path.join(workdir, self.scriptname))

       
#
       
# Retrieve finished image file back from worker
       
#
       
if DEBUG: print '[[%s]]' % machine,
       
if local:
           
do('cp %s %s' % (os.path.join(workdir, jpg),
                             os
.path.join(self.dstdir, jpg)))
       
else:
           
do('scp %s:%s %s' % (machine, os.path.join(workdir, jpg),
                                 os
.path.join(self.dstdir, jpg)))

       
#
       
# Put text on finished image, apply border, and convert to YUV
       
#
       
if self.textlist:
            cmd
= ('convert %s -font times-roman -pointsize 30' %
                   
(os.path.join(self.dstdir, jpg)))
           
for i in range(len(self.textlist)):
                cmd
+= ' -annotate +10+%d "%s"' % (30 * (i + 1), self.textlist[i])
           
if border is not None:
                cmd
+= ' -bordercolor black -border %dx%d' % border
            cmd
+= ' ' + os.path.join(self.dstdir, self.yuv)
           
do(cmd)
       
else:
           
do('convert %s %s' %
               
(os.path.join(self.dstdir, jpg),
                os
.path.join(self.dstdir, self.yuv)))

       
#
       
# Clean up remaining files on the worker machine
       
#
        worker_do
('rm -f %s %s' %
                 
(os.path.join(workdir, self.scriptname),
                   os
.path.join(workdir, jpg)))

all_workers_stop
= False

class Worker(threading.Thread):

   
def __init__(self, jobqueue, machine, workdir):
        threading
.Thread.__init__(self)
       
self.machine = machine
       
self.jobqueue = jobqueue
       
self.workdir = workdir
       
self.busy = True

   
#
   
# Each worker grabs a new jobs as soon as he finishes the previous
   
# one. This allows mixing of slower and faster worker machines; each
   
# works at capacity.
   
#
   
def run(self):
       
global all_workers_stop
       
while not all_workers_stop:
            job
= self.jobqueue.get()
           
if job is None:
               
# no jobs left in the queue, we're finished
               
self.busy = False
               
return
           
try:
                job
.go(self.machine, self.workdir)
           
except:
                all_workers_stop
= True
               
raise

class PovrayJobQueue:

   
def __init__(self):
       
self.worker_pool = [ ]
       
self.jobqueue = [ ]
       
self._lock = threading.Lock()
       
for machine, workdir in worker_list:
           
self.worker_pool.append(Worker(self, machine, workdir))

   
def append(self, job):
       
self._lock.acquire()   # thread safety
       
self.jobqueue.append(job)
       
self._lock.release()
   
def get(self):
       
self._lock.acquire()   # thread safety
       
try:
            r
= self.jobqueue.pop(0)
       
except IndexError:
            r
= None
       
self._lock.release()
       
return r

   
def start(self):
       
for worker in self.worker_pool:
            worker
.start()
   
def wait(self):
        busy_workers
= 1
       
while busy_workers > 0:
            time
.sleep(0.5)
            busy_workers
= 0
           
for worker in self.worker_pool:
               
if worker.busy:
                    busy_workers
+= 1
           
if all_workers_stop:
               
raise Exception

####################
#                  #
#    MPEG STUFF    #
#                  #
####################

params = """MPEG-2 Test Sequence, 30 frames/sec
%(sourcefileformat)s    /* name of source files */
-         /* name of reconstructed images ("
-": don't store) */
-         /* name of intra quant matrix file     ("
-": default matrix) */
-         /* name of non intra quant matrix file ("
-": default matrix) */
stat.out  /* name of statistics file ("
-": stdout ) */
1         /* input picture file format: 0=*.Y,*.U,*.V, 1=*.yuv, 2=*.ppm */
%(frames)d       /* number of frames */
0         /* number of first frame */
00:00:00:00 /* timecode of first frame */
15        /* N (# of frames in GOP) */
3         /* M (I/P frame distance) */
0         /* ISO/IEC 11172-2 stream */
0         /* 0:frame pictures, 1:field pictures */
%(width)d       /* horizontal_size */
%(height)d       /* vertical_size */
2         /* aspect_ratio_information 1=square pel, 2=4:3, 3=16:9, 4=2.11:1 */
5         /* frame_rate_code 1=23.976, 2=24, 3=25, 4=29.97, 5=30 frames/sec. */
%(bitrate)f  /* bit_rate (bits/s) */
112       /* vbv_buffer_size (in multiples of 16 kbit) */
0         /* low_delay  */
0         /* constrained_parameters_flag */
4         /* Profile ID: Simple = 5, Main = 4, SNR = 3, Spatial = 2, High = 1 */
8         /* Level ID:   Low = 10, Main = 8, High 1440 = 6, High = 4          */
0         /* progressive_sequence */
1         /* chroma_format: 1=4:2:0, 2=4:2:2, 3=4:4:4 */
2         /* video_format: 0=comp., 1=PAL, 2=NTSC, 3=SECAM, 4=MAC, 5=unspec. */
5         /* color_primaries */
5         /* transfer_characteristics */
4         /* matrix_coefficients */
%(width)d       /* display_horizontal_size */
%(height)d       /* display_vertical_size */
0         /* intra_dc_precision (0: 8 bit, 1: 9 bit, 2: 10 bit, 3: 11 bit */
1         /* top_field_first */
0 0 0     /* frame_pred_frame_dct (I P B) */
0 0 0     /* concealment_motion_vectors (I P B) */
1 1 1     /* q_scale_type  (I P B) */
1 0 0     /* intra_vlc_format (I P B)*/
0 0 0     /* alternate_scan (I P B) */
0         /* repeat_first_field */
0         /* progressive_frame */
0         /* P distance between complete intra slice refresh */
0         /* rate control: r (reaction parameter) */
0         /* rate control: avg_act (initial average activity) */
0         /* rate control: Xi (initial I frame global complexity measure) */
0         /* rate control: Xp (initial P frame global complexity measure) */
0         /* rate control: Xb (initial B frame global complexity measure) */
0         /* rate control: d0i (initial I frame virtual buffer fullness) */
0         /* rate control: d0p (initial P frame virtual buffer fullness) */
0         /* rate control: d0b (initial B frame virtual buffer fullness) */
2 2 11 11 /* P:  forw_hor_f_code forw_vert_f_code search_width/height */
1 1 3  3  /* B1: forw_hor_f_code forw_vert_f_code search_width/height */
1 1 7  7  /* B1: back_hor_f_code back_vert_f_code search_width/height */
1 1 7  7  /* B2: forw_hor_f_code forw_vert_f_code search_width/height */
1 1 3  3  /* B2: back_hor_f_code back_vert_f_code search_width/height */
"""


def textlist(i):
   
return [ ]

# Where will I keep all my temporary files? On Mandriva, /tmp is small
# but $HOME/tmp is large.
mpeg_dir
= '/home/wware/tmp/mpeg'

def remove_old_yuvs():
   
# you don't always want to do this
   
do("rm -rf " + mpeg_dir + "/yuvs")
   
do("mkdir -p " + mpeg_dir + "/yuvs")

class MpegSequence:

   
def __init__(self):
       
self.frame = 0
       
self.width = mpeg_width
       
self.height = mpeg_height
       
self.size = (self.width, self.height)

   
def __len__(self):
       
return self.frame

   
def yuv_format(self):
       
# Leave off the ".yuv" so we can use it for the
       
# mpeg2encode parameter file.
       
return mpeg_dir + '/yuvs/foo.%06d'

   
def yuv_name(self, i=None):
       
if i is None:
            i
= self.frame
       
return (self.yuv_format() % i) + '.yuv'

   
# By default, each title page stays up for five seconds
   
def titleSequence(self, titlefile, frames=150):
       
assert os.path.exists(titlefile)
       
if framelimit is not None: frames = min(frames, framelimit)
        first_yuv
= self.yuv_name()
       
if border is not None:
            w
, h = self.width - 2 * border[0], self.height - 2 * border[1]
            borderoption
= ' -bordercolor black -border %dx%d' % border
       
else:
            w
, h = self.width, self.height
            borderoption
= ''
       
do('convert %s -geometry %dx%d! %s %s' %
           
(titlefile, w, h, borderoption, first_yuv))
       
self.frame += 1
       
for i in range(1, frames):
           
import shutil
            shutil
.copy(first_yuv, self.yuv_name())
           
self.frame += 1

   
def previouslyComputed(self, fmt, frames, begin=0):
       
assert os.path.exists(titlefile)
       
if framelimit is not None: frames = min(frames, framelimit)
       
for i in range(frames):
           
import shutil
            src
= fmt % (i + begin)
            shutil
.copy(src, self.yuv_name())
           
self.frame += 1

   
def motionBlurSequence(self, povfmt, frames,
                           ratio
, avg, begin=0):
       
# avg is how many subframes are averaged to produce each frame
       
# ratio is the ratio of subframes to frames
       
if framelimit is not None: frames = min(frames, framelimit)
        pq
= PovrayJobQueue()
        yuvs
= [ ]
        srcdir
, povfmt = os.path.split(povfmt)

       
for i in range(frames):
            yuv
= self.yuv_name()
            yuvs
.append(yuv)
            dstdir
, yuv = os.path.split(yuv)
            ywidth
, yheight = mpeg_width, mpeg_height
           
if border is not None:
                ywidth
-= 2 * border[0]
                yheight
-= 2 * border[1]
            job
= PovrayJob(srcdir, dstdir, povfmt,
                           
begin + i * ratio,
                           
begin + i * ratio + avg,
                            yuv
,
                            povray_width
, povray_height,
                            ywidth
, yheight, textlist(i))
            pq
.append(job)
           
self.frame += 1
        pq
.start()
        pq
.wait()

   
def encode(self):
        parfil
= mpeg_dir + "/foo.par"
        outf
= open(parfil, "w")
        outf
.write(params % {'sourcefileformat': self.yuv_format(),
                             
'frames': len(self),
                             
'height': self.height,
                             
'width': self.width,
                             
'bitrate': bitrate})
        outf
.close()
       
# encoding is an inexpensive operation, do it even if not for real
       
do('mpeg2encode %s/foo.par %s/foo.mpeg' % (mpeg_dir, mpeg_dir))
       
do('rm -f %s/foo.mp4' % mpeg_dir)
       
do('ffmpeg -i %s/foo.mpeg -sameq %s/foo.mp4' % (mpeg_dir, mpeg_dir))

"""
Here is an example usage of this stuff.

import os, sys, animate, string

animate.worker_list = [
    ('localhost', '/tmp/mpeg'),
    ('server', '/tmp/mpeg'),
    ('laptop', '/tmp/mpeg'),
    ('mac', '/Users/wware/tmp')
    ]

for arg in sys.argv[1:]:
    if arg == 'debug':
        animate.DEBUG = True
    elif arg == 'ugly':
        animate.povray_pretty = False
    elif arg.startswith('framelimit='):
        animate.framelimit = string.atoi(arg[11:])

h = 438
w = 584
animate.set_resolution(w, h)
animate.border = (w/10, h/10)

#################################

N = 1   # nominally 1, test with 4, 5, or 9

animate.remove_old_yuvs()

m = animate.MpegSequence()
m.titleSequence('title1.gif', 150 / N)

# Each frame is 5 femtoseconds, each subframe is 0.5 fs
def textlist(i):
    nsecs = i * 5.0e-6
    return [
        '%.4f nanoseconds' % nsecs,
        '%.4f rotations' % (nsecs / 0.2),
        ]
animate.textlist = textlist
m.titleSequence('title2.gif', 150 / N)
m.motionBlurSequence(os.path.join(animate.mpeg_dir, 'fastpov/fast.%06d.pov'),
                     450 / N, 10 * N, 10 / N)


# Each frame is 20 femtoseconds, each subframe is 2 fs
def textlist(i):
    nsecs = i * 20.0e-6
    return [
        '%.3f nanoseconds' % nsecs,
        '%.3f rotations' % (nsecs / 0.2),
        ]
animate.textlist = textlist
m.titleSequence('title3.gif', 150 / N)
m.motionBlurSequence(os.path.join(animate.mpeg_dir, 'medpov/med.%06d.pov'),
                     450 / N, 10 * N, 10 / N)


# Each frame is 200 femtoseconds, each subframe is 20 fs
def textlist(i):
    nsecs = i * 200.0e-6
    return [
        '%.2f nanoseconds' % nsecs,
        '%.2f rotations' % (nsecs / 0.2),
        ]
animate.textlist = textlist
m.titleSequence('title4.gif', 150 / N)
m.motionBlurSequence(os.path.join(animate.mpeg_dir, 'slowpov/slow.%06d.pov'),
                     450 / N, 10 * N, 10 / N)
m.encode()
"""

History