mirror of
https://github.com/python/cpython.git
synced 2025-07-29 14:15:07 +00:00

Added class RandomVinFile which supports random access and warming the cache. Added eofseen and errorseen methods to BasicVinFile. Use RGB mode for rgb8 data on entry level Indigo. Minor cosmetic changes.
934 lines
23 KiB
Python
Executable file
934 lines
23 KiB
Python
Executable file
# Classes to read and write CMIF video files.
|
|
# (For a description of the CMIF video format, see cmif-file.ms.)
|
|
|
|
|
|
# Layers of functionality:
|
|
#
|
|
# VideoParams: maintain essential parameters of a video file
|
|
# Displayer: display a frame in a window (with some extra parameters)
|
|
# Grabber: grab a frame from a window
|
|
# BasicVinFile: read a CMIF video file
|
|
# BasicVoutFile: write a CMIF video file
|
|
# VinFile: BasicVinFile + Displayer
|
|
# VoutFile: BasicVoutFile + Displayer + Grabber
|
|
#
|
|
# XXX Future extension:
|
|
# BasicVinoutFile: supports overwriting of individual frames
|
|
|
|
|
|
# Imported modules
|
|
|
|
import sys
|
|
import gl
|
|
import GL
|
|
import colorsys
|
|
|
|
|
|
# Exception raised for various occasions
|
|
|
|
Error = 'VFile.Error' # file format errors
|
|
CallError = 'VFile.CallError' # bad call
|
|
AssertError = 'VFile.AssertError' # internal malfunction
|
|
|
|
|
|
# Constants returned by gl.getdisplaymode(), from <gl/get.h>
|
|
|
|
DMRGB = 0
|
|
DMSINGLE = 1
|
|
DMDOUBLE = 2
|
|
DMRGBDOUBLE = 5
|
|
|
|
|
|
# Max nr. of colormap entries to use
|
|
|
|
MAXMAP = 4096 - 256
|
|
|
|
|
|
# Parametrizations of colormap handling based on color system.
|
|
# (These functions are used via eval with a constructed argument!)
|
|
|
|
def conv_grey(l, x, y):
|
|
return colorsys.yiq_to_rgb(l, 0, 0)
|
|
|
|
def conv_yiq(y, i, q):
|
|
return colorsys.yiq_to_rgb(y, (i-0.5)*1.2, q-0.5)
|
|
|
|
def conv_hls(l, h, s):
|
|
return colorsys.hls_to_rgb(h, l, s)
|
|
|
|
def conv_hsv(v, h, s):
|
|
return colorsys.hsv_to_rgb(h, s, v)
|
|
|
|
def conv_rgb(r, g, b):
|
|
raise Error, 'Attempt to make RGB colormap'
|
|
|
|
def conv_rgb8(rgb, d1, d2):
|
|
rgb = int(rgb*255.0)
|
|
r = (rgb >> 5) & 0x07
|
|
g = (rgb ) & 0x07
|
|
b = (rgb >> 3) & 0x03
|
|
return (r/7.0, g/7.0, b/3.0)
|
|
|
|
|
|
# Choose one of the above based upon a color system name
|
|
|
|
def choose_conversion(format):
|
|
try:
|
|
return eval('conv_' + format)
|
|
except:
|
|
raise Error, 'Unknown color system: ' + `format`
|
|
|
|
|
|
# Inverses of the above
|
|
|
|
def inv_grey(r, g, b):
|
|
y, i, q = colorsys.rgb_to_yiq(r, g, b)
|
|
return y, 0, 0
|
|
|
|
def inv_yiq(r, g, b):
|
|
y, i, q = colorsys.rgb_to_yiq(r, g, b)
|
|
return y, i/1.2 + 0.5, q + 0.5
|
|
|
|
def inv_hls(r, g, b):
|
|
h, l, s = colorsys.rgb_to_hls(r, g, b)
|
|
return l, h, s
|
|
|
|
def inv_hsv(r, g, b):
|
|
h, s, v = colorsys.rgb_to_hsv(r, g, b)
|
|
return v, h, s
|
|
|
|
def inv_rgb(r, g, b):
|
|
raise Error, 'Attempt to invert RGB colormap'
|
|
|
|
def inv_rgb8(r, g, b):
|
|
r = int(r*7.0)
|
|
g = int(g*7.0)
|
|
b = int(b*7.0)
|
|
rgb = ((r&7) << 5) | ((b&3) << 3) | (g&7)
|
|
return rgb / 255.0, 0, 0
|
|
|
|
|
|
# Choose one of the above based upon a color system name
|
|
|
|
def choose_inverse(format):
|
|
try:
|
|
return eval('inv_' + format)
|
|
except:
|
|
raise Error, 'Unknown color system: ' + `format`
|
|
|
|
|
|
# Predicate to see whether this is an entry level (non-XS) Indigo.
|
|
# If so we can lrectwrite 8-bit wide pixels into a window in RGB mode
|
|
|
|
def is_entry_indigo():
|
|
# XXX hack, hack. We should call gl.gversion() but that doesn't
|
|
# exist in earlier Python versions. Therefore we check the number
|
|
# of bitplanes *and* the size of the monitor.
|
|
xmax = gl.getgdesc(GL.GD_XPMAX)
|
|
if xmax <> 1024: return 0
|
|
ymax = gl.getgdesc(GL.GD_YPMAX)
|
|
if ymax != 768: return 0
|
|
r = gl.getgdesc(GL.GD_BITS_NORM_SNG_RED)
|
|
g = gl.getgdesc(GL.GD_BITS_NORM_SNG_GREEN)
|
|
b = gl.getgdesc(GL.GD_BITS_NORM_SNG_BLUE)
|
|
return (r, g, b) == (3, 3, 2)
|
|
|
|
|
|
# Routines to grab data, per color system (only a few really supported).
|
|
# (These functions are used via eval with a constructed argument!)
|
|
|
|
def grab_rgb(w, h, pf):
|
|
if gl.getdisplaymode() <> DMRGB:
|
|
raise Error, 'Sorry, can only grab rgb in single-buf rgbmode'
|
|
if pf <> 1 and pf <> 0:
|
|
raise Error, 'Sorry, only grab rgb with packfactor 1'
|
|
return gl.lrectread(0, 0, w-1, h-1), None
|
|
|
|
def grab_rgb8(w, h, pf):
|
|
if gl.getdisplaymode() <> DMRGB:
|
|
raise Error, 'Sorry, can only grab rgb8 in single-buf rgbmode'
|
|
if pf <> 1 and pf <> 0:
|
|
raise Error, 'Sorry, can only grab rgb8 with packfactor 1'
|
|
if not is_entry_indigo():
|
|
raise Error, 'Sorry, can only grab rgb8 on entry level Indigo'
|
|
# XXX Dirty Dirty here.
|
|
# XXX Set buffer to cmap mode, grab image and set it back.
|
|
gl.cmode()
|
|
gl.gconfig()
|
|
gl.pixmode(GL.PM_SIZE, 8)
|
|
data = gl.lrectread(0, 0, w-1, h-1)
|
|
data = data[:w*h] # BUG FIX for python lrectread
|
|
gl.RGBmode()
|
|
gl.gconfig()
|
|
gl.pixmode(GL.PM_SIZE, 32)
|
|
return data, None
|
|
|
|
def grab_grey(w, h, pf):
|
|
raise Error, 'Sorry, grabbing grey not implemented'
|
|
|
|
def grab_yiq(w, h, pf):
|
|
raise Error, 'Sorry, grabbing yiq not implemented'
|
|
|
|
def grab_hls(w, h, pf):
|
|
raise Error, 'Sorry, grabbing hls not implemented'
|
|
|
|
def grab_hsv(w, h, pf):
|
|
raise Error, 'Sorry, grabbing hsv not implemented'
|
|
|
|
|
|
# Choose one of the above based upon a color system name
|
|
|
|
def choose_grabber(format):
|
|
try:
|
|
return eval('grab_' + format)
|
|
except:
|
|
raise Error, 'Unknown color system: ' + `format`
|
|
|
|
|
|
# Base class to manage video format parameters
|
|
|
|
class VideoParams:
|
|
|
|
# Initialize an instance.
|
|
# Set all parameters to something decent
|
|
# (except width and height are set to zero)
|
|
|
|
def init(self):
|
|
# Essential parameters
|
|
self.format = 'grey' # color system used
|
|
# Choose from: 'rgb', 'rgb8', 'hsv', 'yiq', 'hls'
|
|
self.width = 0 # width of frame
|
|
self.height = 0 # height of frame
|
|
self.packfactor = 1 # expansion using rectzoom
|
|
# if packfactor == 0, data is one 32-bit word/pixel;
|
|
# otherwise, data is one byte/pixel
|
|
self.c0bits = 8 # bits in first color dimension
|
|
self.c1bits = 0 # bits in second color dimension
|
|
self.c2bits = 0 # bits in third color dimension
|
|
self.offset = 0 # colormap index offset (XXX ???)
|
|
self.chrompack = 0 # set if separate chrominance data
|
|
return self
|
|
|
|
# Set the frame width and height (e.g. from gl.getsize())
|
|
|
|
def setsize(self, size):
|
|
self.width, self.height = size
|
|
|
|
# Retrieve the frame width and height (e.g. for gl.prefsize())
|
|
|
|
def getsize(self):
|
|
return (self.width, self.height)
|
|
|
|
# Set all parameters.
|
|
# This does limited validity checking;
|
|
# if the check fails no parameters are changed
|
|
|
|
def setinfo(self, values):
|
|
(self.format, self.width, self.height, self.packfactor,\
|
|
self.c0bits, self.c1bits, self.c2bits, self.offset, \
|
|
self.chrompack) = values
|
|
|
|
# Retrieve all parameters in a format suitable for a subsequent
|
|
# call to setinfo()
|
|
|
|
def getinfo(self):
|
|
return (self.format, self.width, self.height, self.packfactor,\
|
|
self.c0bits, self.c1bits, self.c2bits, self.offset, \
|
|
self.chrompack)
|
|
|
|
# Write the relevant bits to stdout
|
|
|
|
def printinfo(self):
|
|
print 'Format: ', self.format
|
|
print 'Size: ', self.width, 'x', self.height
|
|
print 'Pack: ', self.packfactor, '; chrom:', self.chrompack
|
|
print 'Bits: ', self.c0bits, self.c1bits, self.c2bits
|
|
print 'Offset: ', self.offset
|
|
|
|
|
|
# Class to display video frames in a window.
|
|
# It is the caller's responsibility to ensure that the correct window
|
|
# is current when using showframe(), initcolormap(), clear() and clearto()
|
|
|
|
class Displayer(VideoParams):
|
|
|
|
# Initialize an instance.
|
|
# This does not need a current window
|
|
|
|
def init(self):
|
|
self = VideoParams.init(self)
|
|
# User-settable parameters
|
|
self.magnify = 1.0 # frame magnification factor
|
|
self.xorigin = 0 # x frame offset
|
|
self.yorigin = 0 # y frame offset (from bottom)
|
|
self.quiet = 0 # if set, don't print messages
|
|
self.fallback = 1 # allow fallback to grey
|
|
# Internal flags
|
|
self.colormapinited = 0 # must initialize window
|
|
self.skipchrom = 0 # don't skip chrominance data
|
|
self.color0 = None # magic, used by clearto()
|
|
self.fixcolor0 = 0 # don't need to fix color0
|
|
return self
|
|
|
|
# setinfo() must reset some internal flags
|
|
|
|
def setinfo(self, values):
|
|
VideoParams.setinfo(values)
|
|
self.colormapinited = 0
|
|
self.skipchrom = 0
|
|
self.color0 = None
|
|
self.fixcolor0 = 0
|
|
|
|
# Show one frame, initializing the window if necessary
|
|
|
|
def showframe(self, data, chromdata):
|
|
if not self.colormapinited:
|
|
self.initcolormap()
|
|
if self.fixcolor0:
|
|
gl.mapcolor(self.color0)
|
|
self.fixcolor0 = 0
|
|
w, h, pf = self.width, self.height, self.packfactor
|
|
factor = self.magnify
|
|
if pf: factor = factor * pf
|
|
if chromdata and not self.skipchrom:
|
|
cp = self.chrompack
|
|
cw = (w+cp-1)/cp
|
|
ch = (h+cp-1)/cp
|
|
gl.rectzoom(factor*cp, factor*cp)
|
|
gl.pixmode(GL.PM_SIZE, 16)
|
|
gl.writemask(self.mask - ((1 << self.c0bits) - 1))
|
|
gl.lrectwrite(self.xorigin, self.yorigin, \
|
|
self.xorigin + cw - 1, self.yorigin + ch - 1, \
|
|
chromdata)
|
|
#
|
|
if pf:
|
|
gl.writemask((1 << self.c0bits) - 1)
|
|
gl.pixmode(GL.PM_SIZE, 8)
|
|
w = w/pf
|
|
h = h/pf
|
|
gl.rectzoom(factor, factor)
|
|
gl.lrectwrite(self.xorigin, self.yorigin, \
|
|
self.xorigin + w - 1, self.yorigin + h - 1, data)
|
|
gl.gflush()
|
|
|
|
# Initialize the window: set RGB or colormap mode as required,
|
|
# fill in the colormap, and clear the window
|
|
|
|
def initcolormap(self):
|
|
self.colormapinited = 1
|
|
self.color0 = None
|
|
self.fixcolor0 = 0
|
|
if self.format == 'rgb':
|
|
gl.RGBmode()
|
|
gl.gconfig()
|
|
gl.RGBcolor(200, 200, 200) # XXX rather light grey
|
|
gl.clear()
|
|
return
|
|
if self.format == 'rgb8' and is_entry_indigo():
|
|
gl.RGBmode()
|
|
gl.gconfig()
|
|
gl.RGBcolor(200, 200, 200) # XXX rather light grey
|
|
gl.clear()
|
|
gl.pixmode(GL.PM_SIZE, 8)
|
|
return
|
|
gl.cmode()
|
|
gl.gconfig()
|
|
self.skipchrom = 0
|
|
if self.offset == 0:
|
|
self.mask = 0x7ff
|
|
else:
|
|
self.mask = 0xfff
|
|
if not self.quiet:
|
|
sys.stderr.write('Initializing color map...')
|
|
self._initcmap()
|
|
gl.clear()
|
|
if not self.quiet:
|
|
sys.stderr.write(' Done.\n')
|
|
|
|
# Clear the window to a default color
|
|
|
|
def clear(self):
|
|
if not self.colormapinited: raise CallError
|
|
if gl.getdisplaymode() in (DMRGB, DMRGBDOUBLE):
|
|
gl.RGBcolor(200, 200, 200) # XXX rather light grey
|
|
gl.clear()
|
|
return
|
|
gl.writemask(0xffffffff)
|
|
gl.clear()
|
|
|
|
# Clear the window to a given RGB color.
|
|
# This may steal the first color index used; the next call to
|
|
# showframe() will restore the intended mapping for that index
|
|
|
|
def clearto(self, r, g, b):
|
|
if not self.colormapinited: raise CallError
|
|
if gl.getdisplaymode() in (DMRGB, DMRGBDOUBLE):
|
|
gl.RGBcolor(r, g, b)
|
|
gl.clear()
|
|
return
|
|
index = self.color0[0]
|
|
self.fixcolor0 = 1
|
|
gl.mapcolor(index, r, g, b)
|
|
gl.writemask(0xffffffff)
|
|
gl.clear()
|
|
gl.gflush()
|
|
|
|
# Do the hard work for initializing the colormap (internal).
|
|
# This also sets the current color to the first color index
|
|
# used -- the caller should never change this since it is used
|
|
# by clear() and clearto()
|
|
|
|
def _initcmap(self):
|
|
convcolor = choose_conversion(self.format)
|
|
maxbits = gl.getgdesc(GL.GD_BITS_NORM_SNG_CMODE)
|
|
if maxbits > 11:
|
|
maxbits = 11
|
|
c0bits = self.c0bits
|
|
c1bits = self.c1bits
|
|
c2bits = self.c2bits
|
|
if c0bits+c1bits+c2bits > maxbits:
|
|
if self.fallback and c0bits < maxbits:
|
|
# Cannot display frames in this mode, use grey
|
|
self.skipchrom = 1
|
|
c1bits = c2bits = 0
|
|
convcolor = choose_conversion('grey')
|
|
else:
|
|
raise Error, 'Sorry, '+`maxbits`+ \
|
|
' bits max on this machine'
|
|
maxc0 = 1 << c0bits
|
|
maxc1 = 1 << c1bits
|
|
maxc2 = 1 << c2bits
|
|
if self.offset == 0 and maxbits == 11:
|
|
offset = 2048
|
|
else:
|
|
offset = self.offset
|
|
if maxbits <> 11:
|
|
offset = offset & ((1<<maxbits)-1)
|
|
self.color0 = None
|
|
self.fixcolor0 = 0
|
|
for c0 in range(maxc0):
|
|
c0v = c0/float(maxc0-1)
|
|
for c1 in range(maxc1):
|
|
if maxc1 == 1:
|
|
c1v = 0
|
|
else:
|
|
c1v = c1/float(maxc1-1)
|
|
for c2 in range(maxc2):
|
|
if maxc2 == 1:
|
|
c2v = 0
|
|
else:
|
|
c2v = c2/float(maxc2-1)
|
|
index = offset + c0 + (c1<<c0bits) + \
|
|
(c2 << (c0bits+c1bits))
|
|
if index < MAXMAP:
|
|
rv, gv, bv = \
|
|
convcolor(c0v, c1v, c2v)
|
|
r, g, b = int(rv*255.0), \
|
|
int(gv*255.0), \
|
|
int(bv*255.0)
|
|
gl.mapcolor(index, r, g, b)
|
|
if self.color0 == None:
|
|
self.color0 = \
|
|
index, r, g, b
|
|
# Permanently make the first color index current
|
|
gl.color(self.color0[0])
|
|
gl.gflush() # send the colormap changes to the X server
|
|
|
|
|
|
# Class to grab frames from a window.
|
|
# (This has fewer user-settable parameters than Displayer.)
|
|
# It is the caller's responsibility to initialize the window and to
|
|
# ensure that it is current when using grabframe()
|
|
|
|
class Grabber(VideoParams):
|
|
|
|
# XXX The init() method of VideoParams is just fine, for now
|
|
|
|
# Grab a frame.
|
|
# Return (data, chromdata) just like getnextframe().
|
|
|
|
def grabframe(self):
|
|
grabber = choose_grabber(self.format)
|
|
return grabber(self.width, self.height, self.packfactor)
|
|
|
|
|
|
# Read a CMIF video file header.
|
|
# Return (version, values) where version is 0.0, 1.0, 2.0 or 3.0,
|
|
# and values is ready for setinfo().
|
|
# Raise Error if there is an error in the info
|
|
|
|
def readfileheader(fp, filename):
|
|
#
|
|
# Get identifying header
|
|
#
|
|
line = fp.readline(20)
|
|
if line == 'CMIF video 0.0\n':
|
|
version = 0.0
|
|
elif line == 'CMIF video 1.0\n':
|
|
version = 1.0
|
|
elif line == 'CMIF video 2.0\n':
|
|
version = 2.0
|
|
elif line == 'CMIF video 3.0\n':
|
|
version = 3.0
|
|
else:
|
|
# XXX Could be version 0.0 without identifying header
|
|
raise Error, \
|
|
filename + ': Unrecognized file header: ' + `line`[:20]
|
|
#
|
|
# Get color encoding info
|
|
# (The format may change to 'rgb' later when packfactor == 0)
|
|
#
|
|
if version <= 1.0:
|
|
format = 'grey'
|
|
c0bits, c1bits, c2bits = 8, 0, 0
|
|
chrompack = 0
|
|
offset = 0
|
|
elif version == 2.0:
|
|
line = fp.readline()
|
|
try:
|
|
c0bits, c1bits, c2bits, chrompack = eval(line[:-1])
|
|
except:
|
|
raise Error, filename + ': Bad 2.0 color info'
|
|
if c1bits or c2bits:
|
|
format = 'yiq'
|
|
else:
|
|
format = 'grey'
|
|
offset = 0
|
|
elif version == 3.0:
|
|
line = fp.readline()
|
|
try:
|
|
format, rest = eval(line[:-1])
|
|
except:
|
|
raise Error, filename + ': Bad 3.0 color info'
|
|
if format == 'rgb':
|
|
c0bits = c1bits = c2bits = 0
|
|
chrompack = 0
|
|
offset = 0
|
|
elif format == 'grey':
|
|
c0bits = rest
|
|
c1bits = c2bits = 0
|
|
chrompack = 0
|
|
offset = 0
|
|
else:
|
|
# XXX ought to check that the format is valid
|
|
try:
|
|
c0bits, c1bits, c2bits, chrompack, offset = rest
|
|
except:
|
|
raise Error, filename + ': Bad 3.0 color info'
|
|
#
|
|
# Get frame geometry info
|
|
#
|
|
line = fp.readline()
|
|
try:
|
|
x = eval(line[:-1])
|
|
except:
|
|
raise Error, filename + ': Bad (w,h,pf) info'
|
|
if type(x) <> type(()):
|
|
raise Error, filename + ': Bad (w,h,pf) info'
|
|
if len(x) == 3:
|
|
width, height, packfactor = x
|
|
if packfactor == 0 and version < 3.0:
|
|
format = 'rgb'
|
|
c0bits = 0
|
|
elif len(x) == 2 and version <= 1.0:
|
|
width, height = x
|
|
packfactor = 2
|
|
else:
|
|
raise Error, filename + ': Bad (w,h,pf) info'
|
|
#
|
|
# Return (version, values)
|
|
#
|
|
values = (format, width, height, packfactor, \
|
|
c0bits, c1bits, c2bits, offset, chrompack)
|
|
return (version, values)
|
|
|
|
|
|
# Read a *frame* header -- separate functions per version.
|
|
# Return (timecode, datasize, chromdatasize).
|
|
# Raise EOFError if end of data is reached.
|
|
# Raise Error if data is bad.
|
|
|
|
def readv0frameheader(fp):
|
|
line = fp.readline()
|
|
if not line or line == '\n': raise EOFError
|
|
try:
|
|
t = eval(line[:-1])
|
|
except:
|
|
raise Error, 'Bad 0.0 frame header'
|
|
return (t, 0, 0)
|
|
|
|
def readv1frameheader(fp):
|
|
line = fp.readline()
|
|
if not line or line == '\n': raise EOFError
|
|
try:
|
|
t, datasize = eval(line[:-1])
|
|
except:
|
|
raise Error, 'Bad 1.0 frame header'
|
|
return (t, datasize, 0)
|
|
|
|
def readv2frameheader(fp):
|
|
line = fp.readline()
|
|
if not line or line == '\n': raise EOFError
|
|
try:
|
|
t, datasize = eval(line[:-1])
|
|
except:
|
|
raise Error, 'Bad 2.0 frame header'
|
|
return (t, datasize, 0)
|
|
|
|
def readv3frameheader(fp):
|
|
line = fp.readline()
|
|
if not line or line == '\n': raise EOFError
|
|
try:
|
|
t, datasize, chromdatasize = x = eval(line[:-1])
|
|
except:
|
|
raise Error, 'Bad 3.0 frame header'
|
|
return x
|
|
|
|
|
|
# Write a CMIF video file header (always version 3.0)
|
|
|
|
def writefileheader(fp, values):
|
|
(format, width, height, packfactor, \
|
|
c0bits, c1bits, c2bits, offset, chrompack) = values
|
|
#
|
|
# Write identifying header
|
|
#
|
|
fp.write('CMIF video 3.0\n')
|
|
#
|
|
# Write color encoding info
|
|
#
|
|
if format == 'rgb':
|
|
data = ('rgb', 0)
|
|
elif format == 'grey':
|
|
data = ('grey', c0bits)
|
|
else:
|
|
data = (format, (c0bits, c1bits, c2bits, chrompack, offset))
|
|
fp.write(`data`+'\n')
|
|
#
|
|
# Write frame geometry info
|
|
#
|
|
if format == 'rgb':
|
|
packfactor = 0
|
|
elif packfactor == 0:
|
|
packfactor = 1
|
|
data = (width, height, packfactor)
|
|
fp.write(`data`+'\n')
|
|
|
|
|
|
# Basic class for reading CMIF video files
|
|
|
|
class BasicVinFile(VideoParams):
|
|
|
|
def init(self, filename):
|
|
if filename == '-':
|
|
fp = sys.stdin
|
|
else:
|
|
fp = open(filename, 'r')
|
|
return self.initfp(fp, filename)
|
|
|
|
def initfp(self, fp, filename):
|
|
self = VideoParams.init(self)
|
|
self.fp = fp
|
|
self.filename = filename
|
|
self.version, values = readfileheader(fp, filename)
|
|
VideoParams.setinfo(self, values)
|
|
if self.version == 0.0:
|
|
w, h, pf = self.width, self.height, self.packfactor
|
|
if pf == 0:
|
|
self._datasize = w*h*4
|
|
else:
|
|
self._datasize = (w/pf) * (h/pf)
|
|
self._readframeheader = self._readv0frameheader
|
|
elif self.version == 1.0:
|
|
self._readframeheader = readv1frameheader
|
|
elif self.version == 2.0:
|
|
self._readframeheader = readv2frameheader
|
|
elif self.version == 3.0:
|
|
self._readframeheader = readv3frameheader
|
|
else:
|
|
raise Error, \
|
|
filename + ': Bad version: ' + `self.version`
|
|
self.framecount = 0
|
|
self.atframeheader = 1
|
|
self.eofseen = 0
|
|
self.errorseen = 0
|
|
try:
|
|
self.startpos = self.fp.tell()
|
|
self.canseek = 1
|
|
except IOError:
|
|
self.startpos = -1
|
|
self.canseek = 0
|
|
return self
|
|
|
|
def _readv0frameheader(self, fp):
|
|
t, ds, cs = readv0frameheader(fp)
|
|
ds = self._datasize
|
|
return (t, ds, cs)
|
|
|
|
def close(self):
|
|
self.fp.close()
|
|
del self.fp
|
|
del self._readframeheader
|
|
|
|
def setinfo(self, values):
|
|
raise CallError # Can't change info of input file!
|
|
|
|
def setsize(self, size):
|
|
raise CallError # Can't change info of input file!
|
|
|
|
def rewind(self):
|
|
if not self.canseek:
|
|
raise Error, self.filename + ': can\'t seek'
|
|
self.fp.seek(self.startpos)
|
|
self.framecount = 0
|
|
self.atframeheader = 1
|
|
self.eofseen = 0
|
|
self.errorseen = 0
|
|
|
|
def warmcache(self):
|
|
print '[BasicVinFile.warmcache() not implemented]'
|
|
|
|
def printinfo(self):
|
|
print 'File: ', self.filename
|
|
print 'Version: ', self.version
|
|
VideoParams.printinfo(self)
|
|
|
|
def getnextframe(self):
|
|
t, ds, cs = self.getnextframeheader()
|
|
data, cdata = self.getnextframedata(ds, cs)
|
|
return (t, data, cdata)
|
|
|
|
def skipnextframe(self):
|
|
t, ds, cs = self.getnextframeheader()
|
|
self.skipnextframedata(ds, cs)
|
|
return t
|
|
|
|
def getnextframeheader(self):
|
|
if self.eofseen: raise EOFError
|
|
if self.errorseen: raise CallError
|
|
if not self.atframeheader: raise CallError
|
|
self.atframeheader = 0
|
|
try:
|
|
return self._readframeheader(self.fp)
|
|
except Error, msg:
|
|
self.errorseen = 1
|
|
# Patch up the error message
|
|
raise Error, self.filename + ': ' + msg
|
|
except EOFError:
|
|
self.eofseen = 1
|
|
raise EOFError
|
|
|
|
def getnextframedata(self, ds, cs):
|
|
if self.eofseen: raise EOFError
|
|
if self.errorseen: raise CallError
|
|
if self.atframeheader: raise CallError
|
|
if ds:
|
|
data = self.fp.read(ds)
|
|
if len(data) < ds:
|
|
self.eofseen = 1
|
|
raise EOFError
|
|
else:
|
|
data = ''
|
|
if cs:
|
|
cdata = self.fp.read(cs)
|
|
if len(cdata) < cs:
|
|
self.eofseen = 1
|
|
raise EOFError
|
|
else:
|
|
cdata = ''
|
|
self.atframeheader = 1
|
|
self.framecount = self.framecount + 1
|
|
return (data, cdata)
|
|
|
|
def skipnextframedata(self, ds, cs):
|
|
if self.eofseen: raise EOFError
|
|
if self.errorseen: raise CallError
|
|
if self.atframeheader: raise CallError
|
|
# Note that this won't raise EOFError for a partial frame
|
|
# since there is no easy way to tell whether a seek
|
|
# ended up beyond the end of the file
|
|
if self.canseek:
|
|
self.fp.seek(ds + cs, 1) # Relative seek
|
|
else:
|
|
dummy = self.fp.read(ds + cs)
|
|
del dummy
|
|
self.atframeheader = 1
|
|
self.framecount = self.framecount + 1
|
|
|
|
|
|
# Derived class implementing random access
|
|
|
|
class RandomVinFile(BasicVinFile):
|
|
|
|
def initfp(self, fp, filename):
|
|
self = BasicVinFile.initfp(self, fp, filename)
|
|
self.index = []
|
|
return self
|
|
|
|
def warmcache(self):
|
|
if len(self.index) == 0:
|
|
self.rewind()
|
|
while 1:
|
|
try: dummy = self.skipnextframe()
|
|
except EOFError: break
|
|
else:
|
|
print '[RandomVinFile.warmcache(): too late]'
|
|
self.rewind()
|
|
|
|
def getnextframeheader(self):
|
|
if self.framecount < len(self.index):
|
|
return self._getindexframeheader(self.framecount)
|
|
if self.framecount > len(self.index):
|
|
raise AssertError, \
|
|
'managed to bypass index?!?'
|
|
rv = BasicVinFile.getnextframeheader(self)
|
|
if self.canseek:
|
|
pos = self.fp.tell()
|
|
self.index.append(rv, pos)
|
|
return rv
|
|
|
|
def getrandomframe(self, i):
|
|
t, ds, cs = self.getrandomframeheader(i)
|
|
data, cdata = self.getnextframedata()
|
|
return t, ds, cs
|
|
|
|
def getrandomframeheader(self, i):
|
|
if i < 0: raise ValueError, 'negative frame index'
|
|
if not self.canseek:
|
|
raise Error, self.filename + ': can\'t seek'
|
|
if i < len(self.index):
|
|
return self._getindexframeheader(i)
|
|
if len(self.index) > 0:
|
|
rv = self.getrandomframeheader(len(self.index)-1)
|
|
else:
|
|
self.rewind()
|
|
rv = self.getnextframeheader()
|
|
while i > self.framecount:
|
|
self.skipnextframedata()
|
|
rv = self.getnextframeheader()
|
|
return rv
|
|
|
|
def _getindexframeheader(self, i):
|
|
(rv, pos) = self.index[i]
|
|
self.fp.seek(pos)
|
|
self.framecount = i
|
|
self.atframeheader = 0
|
|
self.eofseen = 0
|
|
self.errorseen = 0
|
|
return rv
|
|
|
|
|
|
# Basic class for writing CMIF video files
|
|
|
|
class BasicVoutFile(VideoParams):
|
|
|
|
def init(self, filename):
|
|
if filename == '-':
|
|
fp = sys.stdout
|
|
else:
|
|
fp = open(filename, 'w')
|
|
return self.initfp(fp, filename)
|
|
|
|
def initfp(self, fp, filename):
|
|
self = VideoParams.init(self)
|
|
self.fp = fp
|
|
self.filename = filename
|
|
self.version = 3.0 # In case anyone inquries
|
|
self.headerwritten = 0
|
|
return self
|
|
|
|
def flush(self):
|
|
self.fp.flush()
|
|
|
|
def close(self):
|
|
self.fp.close()
|
|
del self.fp
|
|
|
|
def setinfo(self, values):
|
|
if self.headerwritten: raise CallError
|
|
VideoParams.setinfo(self, values)
|
|
|
|
def writeheader(self):
|
|
if self.headerwritten: raise CallError
|
|
writefileheader(self.fp, self.getinfo())
|
|
self.headerwritten = 1
|
|
self.atheader = 1
|
|
self.framecount = 0
|
|
|
|
def rewind(self):
|
|
self.fp.seek(0)
|
|
self.headerwritten = 0
|
|
self.atheader = 1
|
|
self.framecount = 0
|
|
|
|
def printinfo(self):
|
|
print 'File: ', self.filename
|
|
VideoParams.printinfo(self)
|
|
|
|
def writeframe(self, t, data, cdata):
|
|
if data: ds = len(data)
|
|
else: ds = 0
|
|
if cdata: cs = len(cdata)
|
|
else: cs = 0
|
|
self.writeframeheader(t, ds, cs)
|
|
self.writeframedata(data, cdata)
|
|
|
|
def writeframeheader(self, t, ds, cs):
|
|
if not self.headerwritten: self.writeheader()
|
|
if not self.atheader: raise CallError
|
|
data = `(t, ds, cs)`
|
|
n = len(data)
|
|
if n < 63: data = data + ' '*(63-n)
|
|
self.fp.write(data + '\n')
|
|
self.atheader = 0
|
|
|
|
def writeframedata(self, data, cdata):
|
|
if not self.headerwritten or self.atheader: raise CallError
|
|
if data: self.fp.write(data)
|
|
if cdata: self.fp.write(cdata)
|
|
self.atheader = 1
|
|
self.framecount = self.framecount + 1
|
|
|
|
|
|
# Classes that combine files with displayers and/or grabbers:
|
|
|
|
class VinFile(RandomVinFile, Displayer):
|
|
|
|
def initfp(self, fp, filename):
|
|
self = Displayer.init(self)
|
|
return RandomVinFile.initfp(self, fp, filename)
|
|
|
|
def shownextframe(self):
|
|
t, data, cdata = self.getnextframe()
|
|
self.showframe(data, cdata)
|
|
return t
|
|
|
|
|
|
class VoutFile(BasicVoutFile, Displayer, Grabber):
|
|
|
|
def initfp(self, fp, filename):
|
|
self = Displayer.init(self)
|
|
## self = Grabber.init(self) # XXX not needed
|
|
return BasicVoutFile.initfp(self, fp, filename)
|
|
|
|
|
|
# Simple test program (VinFile only)
|
|
|
|
def test():
|
|
import time
|
|
if sys.argv[1:]: filename = sys.argv[1]
|
|
else: filename = 'film.video'
|
|
vin = VinFile().init(filename)
|
|
vin.printinfo()
|
|
gl.foreground()
|
|
gl.prefsize(vin.getsize())
|
|
wid = gl.winopen(filename)
|
|
vin.initcolormap()
|
|
t0 = time.millitimer()
|
|
while 1:
|
|
try: t, data, cdata = vin.getnextframe()
|
|
except EOFError: break
|
|
dt = t0 + t - time.millitimer()
|
|
if dt > 0: time.millisleep(dt)
|
|
vin.showframe(data, cdata)
|
|
time.sleep(2)
|