1 # Classes to read and write CMIF video files.
2 # (For a description of the CMIF video format, see cmif-file.ms.)
5 # Layers of functionality:
7 # VideoParams: maintain essential parameters of a video file
8 # Displayer: display a frame in a window (with some extra parameters)
9 # BasicVinFile: read a CMIF video file
10 # BasicVoutFile: write a CMIF video file
11 # VinFile: BasicVinFile + Displayer
12 # VoutFile: BasicVoutFile + Displayer
14 # XXX Future extension:
15 # BasicVinoutFile: supports overwriting of individual frames
32 # Exception raised for various occasions
34 Error
= 'VFile.Error' # file format errors
35 CallError
= 'VFile.CallError' # bad call
36 AssertError
= 'VFile.AssertError' # internal malfunction
39 # Max nr. of colormap entries to use
44 # Parametrizations of colormap handling based on color system.
45 # (These functions are used via eval with a constructed argument!)
47 def conv_grey(l
, x
, y
):
48 return colorsys
.yiq_to_rgb(l
, 0, 0)
50 def conv_grey4(l
, x
, y
):
51 return colorsys
.yiq_to_rgb(l
*17, 0, 0)
53 def conv_mono(l
, x
, y
):
54 return colorsys
.yiq_to_rgb(l
*255, 0, 0)
56 def conv_yiq(y
, i
, q
):
57 return colorsys
.yiq_to_rgb(y
, (i
-0.5)*1.2, q
-0.5)
59 def conv_hls(l
, h
, s
):
60 return colorsys
.hls_to_rgb(h
, l
, s
)
62 def conv_hsv(v
, h
, s
):
63 return colorsys
.hsv_to_rgb(h
, s
, v
)
65 def conv_rgb(r
, g
, b
):
66 raise Error
, 'Attempt to make RGB colormap'
68 def conv_rgb8(rgb
, d1
, d2
):
73 return (r
/7.0, g
/7.0, b
/3.0)
75 def conv_jpeg(r
, g
, b
):
76 raise Error
, 'Attempt to make RGB colormap (jpeg)'
78 conv_jpeggrey
= conv_grey
79 conv_grey2
= conv_grey
82 # Choose one of the above based upon a color system name
84 def choose_conversion(format
):
86 return eval('conv_' + format
)
88 raise Error
, 'Unknown color system: ' + `format`
91 # Inverses of the above
93 def inv_grey(r
, g
, b
):
94 y
, i
, q
= colorsys
.rgb_to_yiq(r
, g
, b
)
98 y
, i
, q
= colorsys
.rgb_to_yiq(r
, g
, b
)
99 return y
, i
/1.2 + 0.5, q
+ 0.5
101 def inv_hls(r
, g
, b
):
102 h
, l
, s
= colorsys
.rgb_to_hls(r
, g
, b
)
105 def inv_hsv(r
, g
, b
):
106 h
, s
, v
= colorsys
.rgb_to_hsv(r
, g
, b
)
109 def inv_rgb(r
, g
, b
):
110 raise Error
, 'Attempt to invert RGB colormap'
112 def inv_rgb8(r
, g
, b
):
116 rgb
= ((r
&7) << 5) |
((b
&3) << 3) |
(g
&7)
117 return rgb
/ 255.0, 0, 0
119 def inv_jpeg(r
, g
, b
):
120 raise Error
, 'Attempt to invert RGB colormap (jpeg)'
122 inv_jpeggrey
= inv_grey
125 # Choose one of the above based upon a color system name
127 def choose_inverse(format
):
129 return eval('inv_' + format
)
131 raise Error
, 'Unknown color system: ' + `format`
134 # Predicate to see whether this is an entry level (non-XS) Indigo.
135 # If so we can lrectwrite 8-bit wide pixels into a window in RGB mode
137 def is_entry_indigo():
138 # XXX hack, hack. We should call gl.gversion() but that doesn't
139 # exist in earlier Python versions. Therefore we check the number
140 # of bitplanes *and* the size of the monitor.
141 xmax
= gl
.getgdesc(GL
.GD_XPMAX
)
142 if xmax
<> 1024: return 0
143 ymax
= gl
.getgdesc(GL
.GD_YPMAX
)
144 if ymax
!= 768: return 0
145 r
= gl
.getgdesc(GL
.GD_BITS_NORM_SNG_RED
)
146 g
= gl
.getgdesc(GL
.GD_BITS_NORM_SNG_GREEN
)
147 b
= gl
.getgdesc(GL
.GD_BITS_NORM_SNG_BLUE
)
148 return (r
, g
, b
) == (3, 3, 2)
151 # Predicate to see whether this machine supports pixmode(PM_SIZE) with
154 # XXX Temporarily disabled, since it is unclear which machines support
155 # XXX which pixelsizes.
157 # XXX The XS appears to support 4 bit pixels, but (looking at osview) it
158 # XXX seems as if the conversion is done by the kernel (unpacking ourselves
159 # XXX is faster than using PM_SIZE=4)
161 def support_packed_pixels():
162 return 0 # To be architecture-dependent
166 # Tables listing bits per pixel for some formats
178 bppafterdecomp
= {'jpeg': 32, 'jpeggrey': 8}
181 # Base class to manage video format parameters
185 # Initialize an instance.
186 # Set all parameters to something decent
187 # (except width and height are set to zero)
190 # Essential parameters
191 self
.frozen
= 0 # if set, can't change parameters
192 self
.format
= 'grey' # color system used
193 # Choose from: grey, rgb, rgb8, hsv, yiq, hls, jpeg, jpeggrey,
195 self
.width
= 0 # width of frame
196 self
.height
= 0 # height of frame
197 self
.packfactor
= 1, 1 # expansion using rectzoom
199 self
.c0bits
= 8 # bits in first color dimension
200 self
.c1bits
= 0 # bits in second color dimension
201 self
.c2bits
= 0 # bits in third color dimension
202 self
.offset
= 0 # colormap index offset (XXX ???)
203 self
.chrompack
= 0 # set if separate chrominance data
205 self
.decompressor
= None
207 # Freeze the parameters (disallow changes)
212 # Unfreeze the parameters (allow changes)
217 # Set some values derived from the standard info values
219 def setderived(self
):
220 if self
.frozen
: raise AssertError
221 if bitsperpixel
.has_key(self
.format
):
222 self
.bpp
= bitsperpixel
[self
.format
]
225 xpf
, ypf
= self
.packfactor
228 self
.mirror_image
= (xpf
< 0)
229 self
.upside_down
= (ypf
< 0)
230 self
.realwidth
= self
.width
/ self
.xpf
231 self
.realheight
= self
.height
/ self
.ypf
235 def setcmapinfo(self
):
236 stuff
= 0, 0, 0, 0, 0
237 if self
.format
in ('rgb8', 'grey'):
238 stuff
= 8, 0, 0, 0, 0
239 if self
.format
== 'grey4':
240 stuff
= 4, 0, 0, 0, 0
241 if self
.format
== 'grey2':
242 stuff
= 2, 0, 0, 0, 0
243 if self
.format
== 'mono':
244 stuff
= 1, 0, 0, 0, 0
245 self
.c0bits
, self
.c1bits
, self
.c2bits
, \
246 self
.offset
, self
.chrompack
= stuff
248 # Set the frame width and height (e.g. from gl.getsize())
250 def setsize(self
, width
, height
):
251 if self
.frozen
: raise CallError
252 width
= (width
/self
.xpf
)*self
.xpf
253 height
= (height
/self
.ypf
)*self
.ypf
254 self
.width
, self
.height
= width
, height
257 # Retrieve the frame width and height (e.g. for gl.prefsize())
260 return (self
.width
, self
.height
)
264 def setformat(self
, format
):
265 if self
.frozen
: raise CallError
278 if self
.frozen
: raise CallError
279 if type(pf
) == type(1):
281 if type(pf
) is not type(()) or len(pf
) <> 2: raise CallError
288 return self
.packfactor
292 def setinfo(self
, values
):
293 if self
.frozen
: raise CallError
294 self
.setformat(values
[0])
295 self
.setpf(values
[3])
296 self
.setsize(values
[1], values
[2])
297 (self
.c0bits
, self
.c1bits
, self
.c2bits
, \
298 self
.offset
, self
.chrompack
) = values
[4:9]
299 if self
.format
== 'compress' and len(values
) > 9:
300 self
.compressheader
= values
[9]
303 # Retrieve all parameters in a format suitable for a subsequent
307 return (self
.format
, self
.width
, self
.height
, self
.packfactor
,\
308 self
.c0bits
, self
.c1bits
, self
.c2bits
, self
.offset
, \
311 def getcompressheader(self
):
312 return self
.compressheader
314 def setcompressheader(self
, ch
):
315 self
.compressheader
= ch
317 # Write the relevant bits to stdout
320 print 'Format: ', self
.format
321 print 'Size: ', self
.width
, 'x', self
.height
322 print 'Pack: ', self
.packfactor
, '; chrom:', self
.chrompack
323 print 'Bpp: ', self
.bpp
324 print 'Bits: ', self
.c0bits
, self
.c1bits
, self
.c2bits
325 print 'Offset: ', self
.offset
327 # Calculate data size, if possible
328 # (Not counting frame header or cdata size)
330 def calcframesize(self
):
331 if not self
.bpp
: raise CallError
332 size
= self
.width
/self
.xpf
* self
.height
/self
.ypf
333 size
= (size
* self
.bpp
+ 7) / 8
336 # Decompress a possibly compressed frame. This method is here
337 # since you sometimes want to use it on a VFile instance and sometimes
338 # on a Displayer instance.
340 # XXXX This should also handle jpeg. Actually, the whole mechanism
341 # should be much more of 'ihave/iwant' style, also allowing you to
342 # read, say, greyscale images from a color movie.
344 def decompress(self
, data
):
345 if self
.format
<> 'compress':
347 if not self
.decompressor
:
349 scheme
= cl
.QueryScheme(self
.compressheader
)
350 self
.decompressor
= cl
.OpenDecompressor(scheme
)
351 headersize
= self
.decompressor
.ReadHeader(self
.compressheader
)
352 width
= self
.decompressor
.GetParam(cl
.IMAGE_WIDTH
)
353 height
= self
.decompressor
.GetParam(cl
.IMAGE_HEIGHT
)
354 params
= [cl
.ORIGINAL_FORMAT
, cl
.RGBX
, \
355 cl
.ORIENTATION
, cl
.BOTTOM_UP
, \
356 cl
.FRAME_BUFFER_SIZE
, width
*height
*cl
.BytesPerPixel(cl
.RGBX
)]
357 self
.decompressor
.SetParams(params
)
358 data
= self
.decompressor
.Decompress(1, data
)
362 # Class to display video frames in a window.
363 # It is the caller's responsibility to ensure that the correct window
364 # is current when using showframe(), initcolormap(), clear() and clearto()
366 class Displayer(VideoParams
):
368 # Initialize an instance.
369 # This does not need a current window
373 raise RuntimeError, \
374 'no gl module available, so cannot display'
375 VideoParams
.__init
__(self
)
376 # User-settable parameters
377 self
.magnify
= 1.0 # frame magnification factor
378 self
.xorigin
= 0 # x frame offset
379 self
.yorigin
= 0 # y frame offset (from bottom)
380 self
.quiet
= 0 # if set, don't print messages
381 self
.fallback
= 1 # allow fallback to grey
383 self
.colormapinited
= 0 # must initialize window
384 self
.skipchrom
= 0 # don't skip chrominance data
385 self
.color0
= None # magic, used by clearto()
386 self
.fixcolor0
= 0 # don't need to fix color0
387 self
.mustunpack
= (not support_packed_pixels())
389 # setinfo() must reset some internal flags
391 def setinfo(self
, values
):
392 VideoParams
.setinfo(self
, values
)
393 self
.colormapinited
= 0
398 # Show one frame, initializing the window if necessary
400 def showframe(self
, data
, chromdata
):
401 self
.showpartframe(data
, chromdata
, \
402 (0,0,self
.width
,self
.height
))
404 def showpartframe(self
, data
, chromdata
, (x
,y
,w
,h
)):
406 xpf
, ypf
= self
.xpf
, self
.ypf
408 gl
.pixmode(GL
.PM_TTOB
, 1)
409 if self
.mirror_image
:
410 gl
.pixmode(GL
.PM_RTOL
, 1)
411 if self
.format
in ('jpeg', 'jpeggrey'):
413 data
, width
, height
, bytes
= jpeg
.decompress(data
)
415 elif self
.format
== 'compress':
416 data
= self
.decompress(data
)
418 elif self
.format
in ('mono', 'grey4'):
420 if self
.format
== 'mono':
421 data
= imageop
.mono2grey(data
, \
422 w
/xpf
, h
/ypf
, 0x20, 0xdf)
423 elif self
.format
== 'grey4':
424 data
= imageop
.grey42grey(data
, \
427 elif self
.format
== 'grey2':
428 data
= imageop
.grey22grey(data
, w
/xpf
, h
/ypf
)
430 if not self
.colormapinited
:
433 gl
.mapcolor(self
.color0
)
435 xfactor
= yfactor
= self
.magnify
436 xfactor
= xfactor
* xpf
437 yfactor
= yfactor
* ypf
438 if chromdata
and not self
.skipchrom
:
440 cx
= int(x
*xfactor
*cp
) + self
.xorigin
441 cy
= int(y
*yfactor
*cp
) + self
.yorigin
444 gl
.rectzoom(xfactor
*cp
, yfactor
*cp
)
445 gl
.pixmode(GL
.PM_SIZE
, 16)
446 gl
.writemask(self
.mask
- ((1 << self
.c0bits
) - 1))
447 gl
.lrectwrite(cx
, cy
, cx
+ cw
- 1, cy
+ ch
- 1, \
451 gl
.writemask((1 << self
.c0bits
) - 1)
452 gl
.pixmode(GL
.PM_SIZE
, pmsize
)
457 gl
.rectzoom(xfactor
, yfactor
)
458 x
= int(x
*xfactor
)+self
.xorigin
459 y
= int(y
*yfactor
)+self
.yorigin
460 gl
.lrectwrite(x
, y
, x
+ w
- 1, y
+ h
- 1, data
)
463 # Initialize the window: set RGB or colormap mode as required,
464 # fill in the colormap, and clear the window
466 def initcolormap(self
):
467 self
.colormapinited
= 1
470 if self
.format
in ('rgb', 'jpeg', 'compress'):
472 gl
.RGBcolor(200, 200, 200) # XXX rather light grey
475 # This only works on an Entry-level Indigo from IRIX 4.0.5
476 if self
.format
== 'rgb8' and is_entry_indigo() and \
477 gl
.gversion() == 'GL4DLG-4.0.': # Note trailing '.'!
479 gl
.RGBcolor(200, 200, 200) # XXX rather light grey
481 gl
.pixmode(GL
.PM_SIZE
, 8)
490 sys
.stderr
.write('Initializing color map...')
494 sys
.stderr
.write(' Done.\n')
496 # Set the window in RGB mode (may be overridden for Glx window)
498 def set_rgbmode(self
):
502 # Set the window in colormap mode (may be overridden for Glx window)
508 # Clear the window to a default color
511 if not self
.colormapinited
: raise CallError
512 if gl
.getdisplaymode() in (GET
.DMRGB
, GET
.DMRGBDOUBLE
):
513 gl
.RGBcolor(200, 200, 200) # XXX rather light grey
516 gl
.writemask(0xffffffff)
519 # Clear the window to a given RGB color.
520 # This may steal the first color index used; the next call to
521 # showframe() will restore the intended mapping for that index
523 def clearto(self
, r
, g
, b
):
524 if not self
.colormapinited
: raise CallError
525 if gl
.getdisplaymode() in (GET
.DMRGB
, GET
.DMRGBDOUBLE
):
529 index
= self
.color0
[0]
531 gl
.mapcolor(index
, r
, g
, b
)
532 gl
.writemask(0xffffffff)
536 # Do the hard work for initializing the colormap (internal).
537 # This also sets the current color to the first color index
538 # used -- the caller should never change this since it is used
539 # by clear() and clearto()
543 if self
.format
in ('mono', 'grey4') and self
.mustunpack
:
544 convcolor
= conv_grey
546 convcolor
= choose_conversion(self
.format
)
547 maxbits
= gl
.getgdesc(GL
.GD_BITS_NORM_SNG_CMODE
)
553 if c0bits
+c1bits
+c2bits
> maxbits
:
554 if self
.fallback
and c0bits
< maxbits
:
555 # Cannot display frames in this mode, use grey
558 convcolor
= choose_conversion('grey')
560 raise Error
, 'Sorry, '+`maxbits`
+ \
561 ' bits max on this machine'
565 if self
.offset
== 0 and maxbits
== 11:
570 offset
= offset
& ((1<<maxbits
)-1)
573 for c0
in range(maxc0
):
574 c0v
= c0
/float(maxc0
-1)
575 for c1
in range(maxc1
):
579 c1v
= c1
/float(maxc1
-1)
580 for c2
in range(maxc2
):
584 c2v
= c2
/float(maxc2
-1)
585 index
= offset
+ c0
+ (c1
<<c0bits
) + \
586 (c2
<< (c0bits
+c1bits
))
589 convcolor(c0v
, c1v
, c2v
)
590 r
, g
, b
= int(rv
*255.0), \
593 map.append((index
, r
, g
, b
))
594 if self
.color0
== None:
597 self
.install_colormap(map)
598 # Permanently make the first color index current
599 gl
.color(self
.color0
[0])
601 # Install the colormap in the window (may be overridden for Glx window)
603 def install_colormap(self
, map):
605 sys
.stderr
.write(' Installing ' + `
len(map)`
+ \
609 gl
.gflush() # send the colormap changes to the X server
612 # Read a CMIF video file header.
613 # Return (version, values) where version is 0.0, 1.0, 2.0 or 3.[01],
614 # and values is ready for setinfo().
615 # Raise Error if there is an error in the info
617 def readfileheader(fp
, filename
):
619 # Get identifying header
621 line
= fp
.readline(20)
622 if line
== 'CMIF video 0.0\n':
624 elif line
== 'CMIF video 1.0\n':
626 elif line
== 'CMIF video 2.0\n':
628 elif line
== 'CMIF video 3.0\n':
630 elif line
== 'CMIF video 3.1\n':
633 # XXX Could be version 0.0 without identifying header
635 filename
+ ': Unrecognized file header: ' + `line`
[:20]
636 compressheader
= None
638 # Get color encoding info
639 # (The format may change to 'rgb' later when packfactor == 0)
643 c0bits
, c1bits
, c2bits
= 8, 0, 0
649 c0bits
, c1bits
, c2bits
, chrompack
= eval(line
[:-1])
651 raise Error
, filename
+ ': Bad 2.0 color info'
657 elif version
in (3.0, 3.1):
660 format
, rest
= eval(line
[:-1])
662 raise Error
, filename
+ ': Bad 3.[01] color info'
663 if format
in ('rgb', 'jpeg'):
664 c0bits
= c1bits
= c2bits
= 0
667 elif format
== 'compress':
668 c0bits
= c1bits
= c2bits
= 0
671 compressheader
= rest
672 elif format
in ('grey', 'jpeggrey', 'mono', 'grey2', 'grey4'):
678 # XXX ought to check that the format is valid
680 c0bits
, c1bits
, c2bits
, chrompack
, offset
= rest
682 raise Error
, filename
+ ': Bad 3.[01] color info'
683 if format
== 'xrgb8':
684 format
= 'rgb8' # rgb8 upside-down, for X
689 # Get frame geometry info
695 raise Error
, filename
+ ': Bad (w,h,pf) info'
696 if type(x
) <> type(()):
697 raise Error
, filename
+ ': Bad (w,h,pf) info'
699 width
, height
, packfactor
= x
700 if packfactor
== 0 and version
< 3.0:
703 elif len(x
) == 2 and version
<= 1.0:
707 raise Error
, filename
+ ': Bad (w,h,pf) info'
708 if type(packfactor
) is type(0):
709 if packfactor
== 0: packfactor
= 1
710 xpf
= ypf
= packfactor
712 xpf
, ypf
= packfactor
715 packfactor
= (xpf
, ypf
)
718 width
= (width
/xpf
) * xpf
719 height
= (height
/ypf
) * ypf
721 # Return (version, values)
723 values
= (format
, width
, height
, packfactor
, \
724 c0bits
, c1bits
, c2bits
, offset
, chrompack
, compressheader
)
725 return (version
, values
)
728 # Read a *frame* header -- separate functions per version.
729 # Return (timecode, datasize, chromdatasize).
730 # Raise EOFError if end of data is reached.
731 # Raise Error if data is bad.
733 def readv0frameheader(fp
):
735 if not line
or line
== '\n': raise EOFError
739 raise Error
, 'Bad 0.0 frame header'
742 def readv1frameheader(fp
):
744 if not line
or line
== '\n': raise EOFError
746 t
, datasize
= eval(line
[:-1])
748 raise Error
, 'Bad 1.0 frame header'
749 return (t
, datasize
, 0)
751 def readv2frameheader(fp
):
753 if not line
or line
== '\n': raise EOFError
755 t
, datasize
= eval(line
[:-1])
757 raise Error
, 'Bad 2.0 frame header'
758 return (t
, datasize
, 0)
760 def readv3frameheader(fp
):
762 if not line
or line
== '\n': raise EOFError
764 t
, datasize
, chromdatasize
= x
= eval(line
[:-1])
766 raise Error
, 'Bad 3.[01] frame header'
770 # Write a CMIF video file header (always version 3.1)
772 def writefileheader(fp
, values
):
773 (format
, width
, height
, packfactor
, \
774 c0bits
, c1bits
, c2bits
, offset
, chrompack
) = values
776 # Write identifying header
778 fp
.write('CMIF video 3.1\n')
780 # Write color encoding info
782 if format
in ('rgb', 'jpeg'):
784 elif format
in ('grey', 'jpeggrey', 'mono', 'grey2', 'grey4'):
785 data
= (format
, c0bits
)
787 data
= (format
, (c0bits
, c1bits
, c2bits
, chrompack
, offset
))
788 fp
.write(`data`
+'\n')
790 # Write frame geometry info
792 data
= (width
, height
, packfactor
)
793 fp
.write(`data`
+'\n')
795 def writecompressfileheader(fp
, cheader
, values
):
796 (format
, width
, height
, packfactor
, \
797 c0bits
, c1bits
, c2bits
, offset
, chrompack
) = values
799 # Write identifying header
801 fp
.write('CMIF video 3.1\n')
803 # Write color encoding info
805 data
= (format
, cheader
)
806 fp
.write(`data`
+'\n')
808 # Write frame geometry info
810 data
= (width
, height
, packfactor
)
811 fp
.write(`data`
+'\n')
814 # Basic class for reading CMIF video files
816 class BasicVinFile(VideoParams
):
818 def __init__(self
, filename
):
819 if type(filename
) != type(''):
822 elif filename
== '-':
825 fp
= open(filename
, 'r')
826 self
.initfp(fp
, filename
)
828 def initfp(self
, fp
, filename
):
829 VideoParams
.__init
__(self
)
831 self
.filename
= filename
832 self
.version
, values
= readfileheader(fp
, filename
)
835 if self
.version
== 0.0:
836 w
, h
, pf
= self
.width
, self
.height
, self
.packfactor
838 self
._datasize
= w
*h
*4
840 self
._datasize
= (w
/pf
) * (h
/pf
)
841 self
._readframeheader
= self
._readv
0frameheader
842 elif self
.version
== 1.0:
843 self
._readframeheader
= readv1frameheader
844 elif self
.version
== 2.0:
845 self
._readframeheader
= readv2frameheader
846 elif self
.version
in (3.0, 3.1):
847 self
._readframeheader
= readv3frameheader
850 filename
+ ': Bad version: ' + `self
.version`
852 self
.atframeheader
= 1
856 self
.startpos
= self
.fp
.tell()
862 def _readv0frameheader(self
, fp
):
863 t
, ds
, cs
= readv0frameheader(fp
)
870 del self
._readframeheader
874 raise Error
, self
.filename
+ ': can\'t seek'
875 self
.fp
.seek(self
.startpos
)
877 self
.atframeheader
= 1
882 print '[BasicVinFile.warmcache() not implemented]'
885 print 'File: ', self
.filename
886 print 'Size: ', getfilesize(self
.filename
)
887 print 'Version: ', self
.version
888 VideoParams
.printinfo(self
)
890 def getnextframe(self
):
891 t
, ds
, cs
= self
.getnextframeheader()
892 data
, cdata
= self
.getnextframedata(ds
, cs
)
893 return (t
, data
, cdata
)
895 def skipnextframe(self
):
896 t
, ds
, cs
= self
.getnextframeheader()
897 self
.skipnextframedata(ds
, cs
)
900 def getnextframeheader(self
):
901 if self
.eofseen
: raise EOFError
902 if self
.errorseen
: raise CallError
903 if not self
.atframeheader
: raise CallError
904 self
.atframeheader
= 0
906 return self
._readframeheader
(self
.fp
)
909 # Patch up the error message
910 raise Error
, self
.filename
+ ': ' + msg
915 def getnextframedata(self
, ds
, cs
):
916 if self
.eofseen
: raise EOFError
917 if self
.errorseen
: raise CallError
918 if self
.atframeheader
: raise CallError
920 data
= self
.fp
.read(ds
)
927 cdata
= self
.fp
.read(cs
)
933 self
.atframeheader
= 1
934 self
.framecount
= self
.framecount
+ 1
937 def skipnextframedata(self
, ds
, cs
):
938 if self
.eofseen
: raise EOFError
939 if self
.errorseen
: raise CallError
940 if self
.atframeheader
: raise CallError
941 # Note that this won't raise EOFError for a partial frame
942 # since there is no easy way to tell whether a seek
943 # ended up beyond the end of the file
945 self
.fp
.seek(ds
+ cs
, 1) # Relative seek
947 dummy
= self
.fp
.read(ds
+ cs
)
949 self
.atframeheader
= 1
950 self
.framecount
= self
.framecount
+ 1
953 # Subroutine to return a file's size in bytes
955 def getfilesize(filename
):
958 st
= os
.stat(filename
)
959 return st
[stat
.ST_SIZE
]
964 # Derived class implementing random access and index cached in the file
966 class RandomVinFile(BasicVinFile
):
968 def initfp(self
, fp
, filename
):
969 BasicVinFile
.initfp(self
, fp
, filename
)
973 if len(self
.index
) == 0:
979 print '[RandomVinFile.warmcache(): too late]'
982 def buildcache(self
):
986 try: dummy
= self
.skipnextframe()
987 except EOFError: break
990 def writecache(self
):
991 # Raises IOerror if the file is not seekable & writable!
993 if len(self
.index
) == 0:
995 if len(self
.index
) == 0:
996 raise Error
, self
.filename
+ ': No frames'
998 self
.fp
.write('\n/////CMIF/////\n')
1001 data
= '\n-*-*-CMIF-*-*-\n' + data
+ ' '*(15-len(data
)) + '\n'
1003 marshal
.dump(self
.index
, self
.fp
)
1009 def readcache(self
):
1010 # Raises Error if there is no cache in the file
1012 if len(self
.index
) <> 0:
1014 self
.fp
.seek(-32, 2)
1015 data
= self
.fp
.read()
1016 if data
[:16] <> '\n-*-*-CMIF-*-*-\n' or data
[-1:] <> '\n':
1018 raise Error
, self
.filename
+ ': No cache'
1019 pos
= eval(data
[16:-1])
1022 self
.index
= marshal
.load(self
.fp
)
1025 raise Error
, self
.filename
+ ': Bad cache'
1028 def getnextframeheader(self
):
1029 if self
.framecount
< len(self
.index
):
1030 return self
._getindexframeheader
(self
.framecount
)
1031 if self
.framecount
> len(self
.index
):
1032 raise AssertError
, \
1033 'managed to bypass index?!?'
1034 rv
= BasicVinFile
.getnextframeheader(self
)
1036 pos
= self
.fp
.tell()
1037 self
.index
.append((rv
, pos
))
1040 def getrandomframe(self
, i
):
1041 t
, ds
, cs
= self
.getrandomframeheader(i
)
1042 data
, cdata
= self
.getnextframedata(ds
, cs
)
1043 return t
, data
, cdata
1045 def getrandomframeheader(self
, i
):
1046 if i
< 0: raise ValueError, 'negative frame index'
1047 if not self
.canseek
:
1048 raise Error
, self
.filename
+ ': can\'t seek'
1049 if i
< len(self
.index
):
1050 return self
._getindexframeheader
(i
)
1051 if len(self
.index
) > 0:
1052 rv
= self
.getrandomframeheader(len(self
.index
)-1)
1055 rv
= self
.getnextframeheader()
1056 while i
> self
.framecount
:
1057 self
.skipnextframedata()
1058 rv
= self
.getnextframeheader()
1061 def _getindexframeheader(self
, i
):
1062 (rv
, pos
) = self
.index
[i
]
1065 self
.atframeheader
= 0
1071 # Basic class for writing CMIF video files
1073 class BasicVoutFile(VideoParams
):
1075 def __init__(self
, filename
):
1076 if type(filename
) != type(''):
1079 elif filename
== '-':
1082 fp
= open(filename
, 'w')
1083 self
.initfp(fp
, filename
)
1085 def initfp(self
, fp
, filename
):
1086 VideoParams
.__init
__(self
)
1088 self
.filename
= filename
1089 self
.version
= 3.1 # In case anyone inquries
1098 def prealloc(self
, nframes
):
1099 if not self
.frozen
: raise CallError
1100 data
= '\xff' * (self
.calcframesize() + 64)
1101 pos
= self
.fp
.tell()
1102 for i
in range(nframes
):
1106 def writeheader(self
):
1107 if self
.frozen
: raise CallError
1108 if self
.format
== 'compress':
1109 writecompressfileheader(self
.fp
, self
.compressheader
, \
1112 writefileheader(self
.fp
, self
.getinfo())
1123 def printinfo(self
):
1124 print 'File: ', self
.filename
1125 VideoParams
.printinfo(self
)
1127 def writeframe(self
, t
, data
, cdata
):
1128 if data
: ds
= len(data
)
1130 if cdata
: cs
= len(cdata
)
1132 self
.writeframeheader(t
, ds
, cs
)
1133 self
.writeframedata(data
, cdata
)
1135 def writeframeheader(self
, t
, ds
, cs
):
1136 if not self
.frozen
: self
.writeheader()
1137 if not self
.atheader
: raise CallError
1138 data
= `
(t
, ds
, cs
)`
1140 if n
< 63: data
= data
+ ' '*(63-n
)
1141 self
.fp
.write(data
+ '\n')
1144 def writeframedata(self
, data
, cdata
):
1145 if not self
.frozen
or self
.atheader
: raise CallError
1146 if data
: self
.fp
.write(data
)
1147 if cdata
: self
.fp
.write(cdata
)
1149 self
.framecount
= self
.framecount
+ 1
1152 # Classes that combine files with displayers:
1154 class VinFile(RandomVinFile
, Displayer
):
1156 def initfp(self
, fp
, filename
):
1157 Displayer
.__init
__(self
)
1158 RandomVinFile
.initfp(self
, fp
, filename
)
1160 def shownextframe(self
):
1161 t
, data
, cdata
= self
.getnextframe()
1162 self
.showframe(data
, cdata
)
1166 class VoutFile(BasicVoutFile
, Displayer
):
1168 def initfp(self
, fp
, filename
):
1169 Displayer
.__init
__(self
)
1170 ## Grabber.__init__(self) # XXX not needed
1171 BasicVoutFile
.initfp(self
, fp
, filename
)
1174 # Simple test program (VinFile only)
1178 if sys
.argv
[1:]: filename
= sys
.argv
[1]
1179 else: filename
= 'film.video'
1180 vin
= VinFile(filename
)
1183 gl
.prefsize(vin
.getsize())
1184 wid
= gl
.winopen(filename
)
1188 try: t
, data
, cdata
= vin
.getnextframe()
1189 except EOFError: break
1190 dt
= t0
+ t
- time
.time()
1191 if dt
> 0: time
.time(dt
)
1192 vin
.showframe(data
, cdata
)