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