Home | History | Annotate | Download | only in plat-mac
      1 # Video file reader, using QuickTime
      2 #
      3 # This module was quickly ripped out of another software package, so there is a good
      4 # chance that it does not work as-is and it needs some hacking.
      5 #
      6 # Jack Jansen, August 2000
      7 #
      8 
      9 from warnings import warnpy3k
     10 warnpy3k("In 3.x, the videoreader module is removed.", stacklevel=2)
     11 
     12 
     13 import sys
     14 from Carbon import Qt
     15 from Carbon import QuickTime
     16 from Carbon import Qd
     17 from Carbon import Qdoffs
     18 from Carbon import QDOffscreen
     19 from Carbon import Res
     20 try:
     21     from Carbon import MediaDescr
     22 except ImportError:
     23     def _audiodescr(data):
     24         return None
     25 else:
     26     def _audiodescr(data):
     27         return MediaDescr.SoundDescription.decode(data)
     28 try:
     29     from imgformat import macrgb
     30 except ImportError:
     31     macrgb = "Macintosh RGB format"
     32 import os
     33 # import audio.format
     34 
     35 class VideoFormat:
     36     def __init__(self, name, descr, width, height, format):
     37         self.__name = name
     38         self.__descr = descr
     39         self.__width = width
     40         self.__height = height
     41         self.__format = format
     42 
     43     def getname(self):
     44         return self.__name
     45 
     46     def getdescr(self):
     47         return self.__descr
     48 
     49     def getsize(self):
     50         return self.__width, self.__height
     51 
     52     def getformat(self):
     53         return self.__format
     54 
     55 class _Reader:
     56     def __init__(self, path):
     57         fd = Qt.OpenMovieFile(path, 0)
     58         self.movie, d1, d2 = Qt.NewMovieFromFile(fd, 0, 0)
     59         self.movietimescale = self.movie.GetMovieTimeScale()
     60         try:
     61             self.audiotrack = self.movie.GetMovieIndTrackType(1,
     62                 QuickTime.AudioMediaCharacteristic, QuickTime.movieTrackCharacteristic)
     63             self.audiomedia = self.audiotrack.GetTrackMedia()
     64         except Qt.Error:
     65             self.audiotrack = self.audiomedia = None
     66             self.audiodescr = {}
     67         else:
     68             handle = Res.Handle('')
     69             n = self.audiomedia.GetMediaSampleDescriptionCount()
     70             self.audiomedia.GetMediaSampleDescription(1, handle)
     71             self.audiodescr = _audiodescr(handle.data)
     72             self.audiotimescale = self.audiomedia.GetMediaTimeScale()
     73             del handle
     74 
     75         try:
     76             self.videotrack = self.movie.GetMovieIndTrackType(1,
     77                 QuickTime.VisualMediaCharacteristic, QuickTime.movieTrackCharacteristic)
     78             self.videomedia = self.videotrack.GetTrackMedia()
     79         except Qt.Error:
     80             self.videotrack = self.videomedia = self.videotimescale = None
     81         if self.videotrack:
     82             self.videotimescale = self.videomedia.GetMediaTimeScale()
     83             x0, y0, x1, y1 = self.movie.GetMovieBox()
     84             self.videodescr = {'width':(x1-x0), 'height':(y1-y0)}
     85             self._initgworld()
     86         self.videocurtime = None
     87         self.audiocurtime = None
     88 
     89 
     90     def __del__(self):
     91         self.audiomedia = None
     92         self.audiotrack = None
     93         self.videomedia = None
     94         self.videotrack = None
     95         self.movie = None
     96 
     97     def _initgworld(self):
     98         old_port, old_dev = Qdoffs.GetGWorld()
     99         try:
    100             movie_w = self.videodescr['width']
    101             movie_h = self.videodescr['height']
    102             movie_rect = (0, 0, movie_w, movie_h)
    103             self.gworld = Qdoffs.NewGWorld(32,  movie_rect, None, None, QDOffscreen.keepLocal)
    104             self.pixmap = self.gworld.GetGWorldPixMap()
    105             Qdoffs.LockPixels(self.pixmap)
    106             Qdoffs.SetGWorld(self.gworld.as_GrafPtr(), None)
    107             Qd.EraseRect(movie_rect)
    108             self.movie.SetMovieGWorld(self.gworld.as_GrafPtr(), None)
    109             self.movie.SetMovieBox(movie_rect)
    110             self.movie.SetMovieActive(1)
    111             self.movie.MoviesTask(0)
    112             self.movie.SetMoviePlayHints(QuickTime.hintsHighQuality, QuickTime.hintsHighQuality)
    113             # XXXX framerate
    114         finally:
    115             Qdoffs.SetGWorld(old_port, old_dev)
    116 
    117     def _gettrackduration_ms(self, track):
    118         tracktime = track.GetTrackDuration()
    119         return self._movietime_to_ms(tracktime)
    120 
    121     def _movietime_to_ms(self, time):
    122         value, d1, d2 = Qt.ConvertTimeScale((time, self.movietimescale, None), 1000)
    123         return value
    124 
    125     def _videotime_to_ms(self, time):
    126         value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None), 1000)
    127         return value
    128 
    129     def _audiotime_to_ms(self, time):
    130         value, d1, d2 = Qt.ConvertTimeScale((time, self.audiotimescale, None), 1000)
    131         return value
    132 
    133     def _videotime_to_movietime(self, time):
    134         value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None),
    135                 self.movietimescale)
    136         return value
    137 
    138     def HasAudio(self):
    139         return not self.audiotrack is None
    140 
    141     def HasVideo(self):
    142         return not self.videotrack is None
    143 
    144     def GetAudioDuration(self):
    145         if not self.audiotrack:
    146             return 0
    147         return self._gettrackduration_ms(self.audiotrack)
    148 
    149     def GetVideoDuration(self):
    150         if not self.videotrack:
    151             return 0
    152         return self._gettrackduration_ms(self.videotrack)
    153 
    154     def GetAudioFormat(self):
    155         if not self.audiodescr:
    156             return None, None, None, None, None
    157         bps = self.audiodescr['sampleSize']
    158         nch = self.audiodescr['numChannels']
    159         if nch == 1:
    160             channels = ['mono']
    161         elif nch == 2:
    162             channels = ['left', 'right']
    163         else:
    164             channels = map(lambda x: str(x+1), range(nch))
    165         if bps % 8:
    166             # Funny bits-per sample. We pretend not to understand
    167             blocksize = 0
    168             fpb = 0
    169         else:
    170             # QuickTime is easy (for as far as we support it): samples are always a whole
    171             # number of bytes, so frames are nchannels*samplesize, and there's one frame per block.
    172             blocksize = (bps/8)*nch
    173             fpb = 1
    174         if self.audiodescr['dataFormat'] == 'raw ':
    175             encoding = 'linear-excess'
    176         elif self.audiodescr['dataFormat'] == 'twos':
    177             encoding = 'linear-signed'
    178         else:
    179             encoding = 'quicktime-coding-%s'%self.audiodescr['dataFormat']
    180 ##      return audio.format.AudioFormatLinear('quicktime_audio', 'QuickTime Audio Format',
    181 ##          channels, encoding, blocksize=blocksize, fpb=fpb, bps=bps)
    182         return channels, encoding, blocksize, fpb, bps
    183 
    184     def GetAudioFrameRate(self):
    185         if not self.audiodescr:
    186             return None
    187         return int(self.audiodescr['sampleRate'])
    188 
    189     def GetVideoFormat(self):
    190         width = self.videodescr['width']
    191         height = self.videodescr['height']
    192         return VideoFormat('dummy_format', 'Dummy Video Format', width, height, macrgb)
    193 
    194     def GetVideoFrameRate(self):
    195         tv = self.videocurtime
    196         if tv is None:
    197             tv = 0
    198         flags = QuickTime.nextTimeStep|QuickTime.nextTimeEdgeOK
    199         tv, dur = self.videomedia.GetMediaNextInterestingTime(flags, tv, 1.0)
    200         dur = self._videotime_to_ms(dur)
    201         return int((1000.0/dur)+0.5)
    202 
    203     def ReadAudio(self, nframes, time=None):
    204         if not time is None:
    205             self.audiocurtime = time
    206         flags = QuickTime.nextTimeStep|QuickTime.nextTimeEdgeOK
    207         if self.audiocurtime is None:
    208             self.audiocurtime = 0
    209         tv = self.audiomedia.GetMediaNextInterestingTimeOnly(flags, self.audiocurtime, 1.0)
    210         if tv < 0 or (self.audiocurtime and tv < self.audiocurtime):
    211             return self._audiotime_to_ms(self.audiocurtime), None
    212         h = Res.Handle('')
    213         desc_h = Res.Handle('')
    214         size, actualtime, sampleduration, desc_index, actualcount, flags = \
    215             self.audiomedia.GetMediaSample(h, 0, tv, desc_h, nframes)
    216         self.audiocurtime = actualtime + actualcount*sampleduration
    217         return self._audiotime_to_ms(actualtime), h.data
    218 
    219     def ReadVideo(self, time=None):
    220         if not time is None:
    221             self.videocurtime = time
    222         flags = QuickTime.nextTimeStep
    223         if self.videocurtime is None:
    224             flags = flags | QuickTime.nextTimeEdgeOK
    225             self.videocurtime = 0
    226         tv = self.videomedia.GetMediaNextInterestingTimeOnly(flags, self.videocurtime, 1.0)
    227         if tv < 0 or (self.videocurtime and tv <= self.videocurtime):
    228             return self._videotime_to_ms(self.videocurtime), None
    229         self.videocurtime = tv
    230         moviecurtime = self._videotime_to_movietime(self.videocurtime)
    231         self.movie.SetMovieTimeValue(moviecurtime)
    232         self.movie.MoviesTask(0)
    233         return self._videotime_to_ms(self.videocurtime), self._getpixmapcontent()
    234 
    235     def _getpixmapcontent(self):
    236         """Shuffle the offscreen PixMap data, because it may have funny stride values"""
    237         rowbytes = Qdoffs.GetPixRowBytes(self.pixmap)
    238         width = self.videodescr['width']
    239         height = self.videodescr['height']
    240         start = 0
    241         rv = []
    242         for i in range(height):
    243             nextline = Qdoffs.GetPixMapBytes(self.pixmap, start, width*4)
    244             start = start + rowbytes
    245             rv.append(nextline)
    246         return ''.join(rv)
    247 
    248 def reader(url):
    249     try:
    250         rdr = _Reader(url)
    251     except IOError:
    252         return None
    253     return rdr
    254 
    255 def _test():
    256     import EasyDialogs
    257     try:
    258         from PIL import Image
    259     except ImportError:
    260         Image = None
    261     import MacOS
    262     Qt.EnterMovies()
    263     path = EasyDialogs.AskFileForOpen(message='Video to convert')
    264     if not path: sys.exit(0)
    265     rdr = reader(path)
    266     if not rdr:
    267         sys.exit(1)
    268     dstdir = EasyDialogs.AskFileForSave(message='Name for output folder')
    269     if not dstdir: sys.exit(0)
    270     num = 0
    271     os.mkdir(dstdir)
    272     videofmt = rdr.GetVideoFormat()
    273     imgfmt = videofmt.getformat()
    274     imgw, imgh = videofmt.getsize()
    275     timestamp, data = rdr.ReadVideo()
    276     while data:
    277         fname = 'frame%04.4d.jpg'%num
    278         num = num+1
    279         pname = os.path.join(dstdir, fname)
    280         if not Image: print 'Not',
    281         print 'Writing %s, size %dx%d, %d bytes'%(fname, imgw, imgh, len(data))
    282         if Image:
    283             img = Image.fromstring("RGBA", (imgw, imgh), data)
    284             img.save(pname, 'JPEG')
    285             timestamp, data = rdr.ReadVideo()
    286             MacOS.SetCreatorAndType(pname, 'ogle', 'JPEG')
    287             if num > 20:
    288                 print 'stopping at 20 frames so your disk does not fill up:-)'
    289                 break
    290     print 'Total frames:', num
    291 
    292 if __name__ == '__main__':
    293     _test()
    294     sys.exit(1)
    295