blob: fbb37e12e752f3402aa8d1d98783c13c58b74eb3 [file] [log] [blame]
Jack Jansena647f572000-09-15 13:43:46 +00001# 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#
Benjamin Peterson23681932008-05-12 21:42:13 +00008
9from warnings import warnpy3k
Benjamin Petersona6864e02008-07-14 17:42:17 +000010warnpy3k("In 3.x, the videoreader module is removed.", stacklevel=2)
Benjamin Peterson23681932008-05-12 21:42:13 +000011
12
Jack Jansena647f572000-09-15 13:43:46 +000013import sys
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000014from Carbon import Qt
15from Carbon import QuickTime
16from Carbon import Qd
17from Carbon import Qdoffs
18from Carbon import QDOffscreen
19from Carbon import Res
Jack Jansen57ba55b2003-02-04 15:36:42 +000020try:
Jack Jansen0ae32202003-04-09 13:25:43 +000021 import MediaDescr
Jack Jansen57ba55b2003-02-04 15:36:42 +000022except ImportError:
Jack Jansen0ae32202003-04-09 13:25:43 +000023 def _audiodescr(data):
24 return None
Jack Jansen57ba55b2003-02-04 15:36:42 +000025else:
Jack Jansen0ae32202003-04-09 13:25:43 +000026 def _audiodescr(data):
27 return MediaDescr.SoundDescription.decode(data)
Jack Jansen57ba55b2003-02-04 15:36:42 +000028try:
Jack Jansen0ae32202003-04-09 13:25:43 +000029 from imgformat import macrgb
Jack Jansen57ba55b2003-02-04 15:36:42 +000030except ImportError:
Jack Jansen0ae32202003-04-09 13:25:43 +000031 macrgb = "Macintosh RGB format"
Jack Jansena647f572000-09-15 13:43:46 +000032import os
33# import audio.format
Jack Jansena647f572000-09-15 13:43:46 +000034
35class VideoFormat:
Jack Jansen0ae32202003-04-09 13:25:43 +000036 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
Tim Peters182b5ac2004-07-18 06:16:08 +000042
Jack Jansen0ae32202003-04-09 13:25:43 +000043 def getname(self):
44 return self.__name
Tim Peters182b5ac2004-07-18 06:16:08 +000045
Jack Jansen0ae32202003-04-09 13:25:43 +000046 def getdescr(self):
47 return self.__descr
Tim Peters182b5ac2004-07-18 06:16:08 +000048
Jack Jansen0ae32202003-04-09 13:25:43 +000049 def getsize(self):
50 return self.__width, self.__height
Tim Peters182b5ac2004-07-18 06:16:08 +000051
Jack Jansen0ae32202003-04-09 13:25:43 +000052 def getformat(self):
53 return self.__format
Tim Peters182b5ac2004-07-18 06:16:08 +000054
Jack Jansena647f572000-09-15 13:43:46 +000055class _Reader:
Jack Jansen0ae32202003-04-09 13:25:43 +000056 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
Tim Peters182b5ac2004-07-18 06:16:08 +000074
75 try:
Jack Jansen0ae32202003-04-09 13:25:43 +000076 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
Jack Jansena647f572000-09-15 13:43:46 +000088
Tim Peters182b5ac2004-07-18 06:16:08 +000089
Jack Jansen0ae32202003-04-09 13:25:43 +000090 def __del__(self):
91 self.audiomedia = None
92 self.audiotrack = None
93 self.videomedia = None
94 self.videotrack = None
95 self.movie = None
Tim Peters182b5ac2004-07-18 06:16:08 +000096
Jack Jansen0ae32202003-04-09 13:25:43 +000097 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)
Tim Peters182b5ac2004-07-18 06:16:08 +0000116
Jack Jansen0ae32202003-04-09 13:25:43 +0000117 def _gettrackduration_ms(self, track):
118 tracktime = track.GetTrackDuration()
119 return self._movietime_to_ms(tracktime)
Tim Peters182b5ac2004-07-18 06:16:08 +0000120
Jack Jansen0ae32202003-04-09 13:25:43 +0000121 def _movietime_to_ms(self, time):
122 value, d1, d2 = Qt.ConvertTimeScale((time, self.movietimescale, None), 1000)
123 return value
Tim Peters182b5ac2004-07-18 06:16:08 +0000124
Jack Jansen0ae32202003-04-09 13:25:43 +0000125 def _videotime_to_ms(self, time):
126 value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None), 1000)
127 return value
Tim Peters182b5ac2004-07-18 06:16:08 +0000128
Jack Jansen0ae32202003-04-09 13:25:43 +0000129 def _audiotime_to_ms(self, time):
130 value, d1, d2 = Qt.ConvertTimeScale((time, self.audiotimescale, None), 1000)
131 return value
Tim Peters182b5ac2004-07-18 06:16:08 +0000132
Jack Jansen0ae32202003-04-09 13:25:43 +0000133 def _videotime_to_movietime(self, time):
134 value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None),
135 self.movietimescale)
136 return value
Tim Peters182b5ac2004-07-18 06:16:08 +0000137
Jack Jansen0ae32202003-04-09 13:25:43 +0000138 def HasAudio(self):
139 return not self.audiotrack is None
Tim Peters182b5ac2004-07-18 06:16:08 +0000140
Jack Jansen0ae32202003-04-09 13:25:43 +0000141 def HasVideo(self):
142 return not self.videotrack is None
Tim Peters182b5ac2004-07-18 06:16:08 +0000143
Jack Jansen0ae32202003-04-09 13:25:43 +0000144 def GetAudioDuration(self):
145 if not self.audiotrack:
146 return 0
147 return self._gettrackduration_ms(self.audiotrack)
Jack Jansena647f572000-09-15 13:43:46 +0000148
Jack Jansen0ae32202003-04-09 13:25:43 +0000149 def GetVideoDuration(self):
150 if not self.videotrack:
151 return 0
152 return self._gettrackduration_ms(self.videotrack)
Tim Peters182b5ac2004-07-18 06:16:08 +0000153
Jack Jansen0ae32202003-04-09 13:25:43 +0000154 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']
Tim Peters182b5ac2004-07-18 06:16:08 +0000180## return audio.format.AudioFormatLinear('quicktime_audio', 'QuickTime Audio Format',
Jack Jansen0ae32202003-04-09 13:25:43 +0000181## channels, encoding, blocksize=blocksize, fpb=fpb, bps=bps)
182 return channels, encoding, blocksize, fpb, bps
Tim Peters182b5ac2004-07-18 06:16:08 +0000183
Jack Jansen0ae32202003-04-09 13:25:43 +0000184 def GetAudioFrameRate(self):
185 if not self.audiodescr:
186 return None
187 return int(self.audiodescr['sampleRate'])
Tim Peters182b5ac2004-07-18 06:16:08 +0000188
Jack Jansen0ae32202003-04-09 13:25:43 +0000189 def GetVideoFormat(self):
190 width = self.videodescr['width']
191 height = self.videodescr['height']
192 return VideoFormat('dummy_format', 'Dummy Video Format', width, height, macrgb)
Tim Peters182b5ac2004-07-18 06:16:08 +0000193
Jack Jansen0ae32202003-04-09 13:25:43 +0000194 def GetVideoFrameRate(self):
195 tv = self.videocurtime
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000196 if tv is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000197 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)
Tim Peters182b5ac2004-07-18 06:16:08 +0000202
Jack Jansen0ae32202003-04-09 13:25:43 +0000203 def ReadAudio(self, nframes, time=None):
204 if not time is None:
205 self.audiocurtime = time
206 flags = QuickTime.nextTimeStep|QuickTime.nextTimeEdgeOK
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000207 if self.audiocurtime is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000208 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
Tim Peters182b5ac2004-07-18 06:16:08 +0000218
Jack Jansen0ae32202003-04-09 13:25:43 +0000219 def ReadVideo(self, time=None):
220 if not time is None:
221 self.videocurtime = time
222 flags = QuickTime.nextTimeStep
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000223 if self.videocurtime is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000224 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()
Tim Peters182b5ac2004-07-18 06:16:08 +0000234
Jack Jansen0ae32202003-04-09 13:25:43 +0000235 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 = rv + nextline
246 return rv
Jack Jansena647f572000-09-15 13:43:46 +0000247
248def reader(url):
Jack Jansen0ae32202003-04-09 13:25:43 +0000249 try:
250 rdr = _Reader(url)
251 except IOError:
252 return None
253 return rdr
Jack Jansena647f572000-09-15 13:43:46 +0000254
255def _test():
Jack Jansen0ae32202003-04-09 13:25:43 +0000256 import EasyDialogs
257 try:
258 import img
259 except ImportError:
260 img = 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 img: print 'Not',
281 print 'Writing %s, size %dx%d, %d bytes'%(fname, imgw, imgh, len(data))
282 if img:
283 wrt = img.writer(imgfmt, pname)
284 wrt.width = imgw
285 wrt.height = imgh
286 wrt.write(data)
287 timestamp, data = rdr.ReadVideo()
288 MacOS.SetCreatorAndType(pname, 'ogle', 'JPEG')
Tim Peters182b5ac2004-07-18 06:16:08 +0000289 if num > 20:
Jack Jansen0ae32202003-04-09 13:25:43 +0000290 print 'stopping at 20 frames so your disk does not fill up:-)'
291 break
292 print 'Total frames:', num
Tim Peters182b5ac2004-07-18 06:16:08 +0000293
Jack Jansena647f572000-09-15 13:43:46 +0000294if __name__ == '__main__':
Jack Jansen0ae32202003-04-09 13:25:43 +0000295 _test()
296 sys.exit(1)