• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
9from warnings import warnpy3k
10warnpy3k("In 3.x, the videoreader module is removed.", stacklevel=2)
11
12
13import sys
14from Carbon import Qt
15from Carbon import QuickTime
16from Carbon import Qd
17from Carbon import Qdoffs
18from Carbon import QDOffscreen
19from Carbon import Res
20try:
21    from Carbon import MediaDescr
22except ImportError:
23    def _audiodescr(data):
24        return None
25else:
26    def _audiodescr(data):
27        return MediaDescr.SoundDescription.decode(data)
28try:
29    from imgformat import macrgb
30except ImportError:
31    macrgb = "Macintosh RGB format"
32import os
33# import audio.format
34
35class 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
55class _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
248def reader(url):
249    try:
250        rdr = _Reader(url)
251    except IOError:
252        return None
253    return rdr
254
255def _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
292if __name__ == '__main__':
293    _test()
294    sys.exit(1)
295