Add better error reporting for MemoryErrors caused by str->float conversions.
[python.git] / Lib / plat-mac / videoreader.py
blob9dd7d67b022b99f511e696c1fd77fb38a1e03b78
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
9 from warnings import warnpy3k
10 warnpy3k("In 3.x, the videoreader module is removed.", stacklevel=2)
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
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
43 def getname(self):
44 return self.__name
46 def getdescr(self):
47 return self.__descr
49 def getsize(self):
50 return self.__width, self.__height
52 def getformat(self):
53 return self.__format
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
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
90 def __del__(self):
91 self.audiomedia = None
92 self.audiotrack = None
93 self.videomedia = None
94 self.videotrack = None
95 self.movie = None
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)
117 def _gettrackduration_ms(self, track):
118 tracktime = track.GetTrackDuration()
119 return self._movietime_to_ms(tracktime)
121 def _movietime_to_ms(self, time):
122 value, d1, d2 = Qt.ConvertTimeScale((time, self.movietimescale, None), 1000)
123 return value
125 def _videotime_to_ms(self, time):
126 value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None), 1000)
127 return value
129 def _audiotime_to_ms(self, time):
130 value, d1, d2 = Qt.ConvertTimeScale((time, self.audiotimescale, None), 1000)
131 return value
133 def _videotime_to_movietime(self, time):
134 value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None),
135 self.movietimescale)
136 return value
138 def HasAudio(self):
139 return not self.audiotrack is None
141 def HasVideo(self):
142 return not self.videotrack is None
144 def GetAudioDuration(self):
145 if not self.audiotrack:
146 return 0
147 return self._gettrackduration_ms(self.audiotrack)
149 def GetVideoDuration(self):
150 if not self.videotrack:
151 return 0
152 return self._gettrackduration_ms(self.videotrack)
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
184 def GetAudioFrameRate(self):
185 if not self.audiodescr:
186 return None
187 return int(self.audiodescr['sampleRate'])
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)
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)
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
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()
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)
248 def reader(url):
249 try:
250 rdr = _Reader(url)
251 except IOError:
252 return None
253 return rdr
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
292 if __name__ == '__main__':
293 _test()
294 sys.exit(1)