blob: 42954b7028cfc985647d3d8205ecdbf80f7dd2ac [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#
8import sys
Jack Jansen5a6fdcd2001-08-25 12:15:04 +00009from Carbon import Qt
10from Carbon import QuickTime
11from Carbon import Qd
12from Carbon import Qdoffs
13from Carbon import QDOffscreen
14from Carbon import Res
Jack Jansen57ba55b2003-02-04 15:36:42 +000015try:
Jack Jansen0ae32202003-04-09 13:25:43 +000016 import MediaDescr
Jack Jansen57ba55b2003-02-04 15:36:42 +000017except ImportError:
Jack Jansen0ae32202003-04-09 13:25:43 +000018 def _audiodescr(data):
19 return None
Jack Jansen57ba55b2003-02-04 15:36:42 +000020else:
Jack Jansen0ae32202003-04-09 13:25:43 +000021 def _audiodescr(data):
22 return MediaDescr.SoundDescription.decode(data)
Jack Jansen57ba55b2003-02-04 15:36:42 +000023try:
Jack Jansen0ae32202003-04-09 13:25:43 +000024 from imgformat import macrgb
Jack Jansen57ba55b2003-02-04 15:36:42 +000025except ImportError:
Jack Jansen0ae32202003-04-09 13:25:43 +000026 macrgb = "Macintosh RGB format"
Jack Jansena647f572000-09-15 13:43:46 +000027import os
28# import audio.format
Jack Jansena647f572000-09-15 13:43:46 +000029
30class VideoFormat:
Jack Jansen0ae32202003-04-09 13:25:43 +000031 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
Tim Peters182b5ac2004-07-18 06:16:08 +000037
Jack Jansen0ae32202003-04-09 13:25:43 +000038 def getname(self):
39 return self.__name
Tim Peters182b5ac2004-07-18 06:16:08 +000040
Jack Jansen0ae32202003-04-09 13:25:43 +000041 def getdescr(self):
42 return self.__descr
Tim Peters182b5ac2004-07-18 06:16:08 +000043
Jack Jansen0ae32202003-04-09 13:25:43 +000044 def getsize(self):
45 return self.__width, self.__height
Tim Peters182b5ac2004-07-18 06:16:08 +000046
Jack Jansen0ae32202003-04-09 13:25:43 +000047 def getformat(self):
48 return self.__format
Tim Peters182b5ac2004-07-18 06:16:08 +000049
Jack Jansena647f572000-09-15 13:43:46 +000050class _Reader:
Jack Jansen0ae32202003-04-09 13:25:43 +000051 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
Tim Peters182b5ac2004-07-18 06:16:08 +000069
70 try:
Jack Jansen0ae32202003-04-09 13:25:43 +000071 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
Jack Jansena647f572000-09-15 13:43:46 +000083
Tim Peters182b5ac2004-07-18 06:16:08 +000084
Jack Jansen0ae32202003-04-09 13:25:43 +000085 def __del__(self):
86 self.audiomedia = None
87 self.audiotrack = None
88 self.videomedia = None
89 self.videotrack = None
90 self.movie = None
Tim Peters182b5ac2004-07-18 06:16:08 +000091
Jack Jansen0ae32202003-04-09 13:25:43 +000092 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)
Tim Peters182b5ac2004-07-18 06:16:08 +0000111
Jack Jansen0ae32202003-04-09 13:25:43 +0000112 def _gettrackduration_ms(self, track):
113 tracktime = track.GetTrackDuration()
114 return self._movietime_to_ms(tracktime)
Tim Peters182b5ac2004-07-18 06:16:08 +0000115
Jack Jansen0ae32202003-04-09 13:25:43 +0000116 def _movietime_to_ms(self, time):
117 value, d1, d2 = Qt.ConvertTimeScale((time, self.movietimescale, None), 1000)
118 return value
Tim Peters182b5ac2004-07-18 06:16:08 +0000119
Jack Jansen0ae32202003-04-09 13:25:43 +0000120 def _videotime_to_ms(self, time):
121 value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None), 1000)
122 return value
Tim Peters182b5ac2004-07-18 06:16:08 +0000123
Jack Jansen0ae32202003-04-09 13:25:43 +0000124 def _audiotime_to_ms(self, time):
125 value, d1, d2 = Qt.ConvertTimeScale((time, self.audiotimescale, None), 1000)
126 return value
Tim Peters182b5ac2004-07-18 06:16:08 +0000127
Jack Jansen0ae32202003-04-09 13:25:43 +0000128 def _videotime_to_movietime(self, time):
129 value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None),
130 self.movietimescale)
131 return value
Tim Peters182b5ac2004-07-18 06:16:08 +0000132
Jack Jansen0ae32202003-04-09 13:25:43 +0000133 def HasAudio(self):
134 return not self.audiotrack is None
Tim Peters182b5ac2004-07-18 06:16:08 +0000135
Jack Jansen0ae32202003-04-09 13:25:43 +0000136 def HasVideo(self):
137 return not self.videotrack is None
Tim Peters182b5ac2004-07-18 06:16:08 +0000138
Jack Jansen0ae32202003-04-09 13:25:43 +0000139 def GetAudioDuration(self):
140 if not self.audiotrack:
141 return 0
142 return self._gettrackduration_ms(self.audiotrack)
Jack Jansena647f572000-09-15 13:43:46 +0000143
Jack Jansen0ae32202003-04-09 13:25:43 +0000144 def GetVideoDuration(self):
145 if not self.videotrack:
146 return 0
147 return self._gettrackduration_ms(self.videotrack)
Tim Peters182b5ac2004-07-18 06:16:08 +0000148
Jack Jansen0ae32202003-04-09 13:25:43 +0000149 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']
Tim Peters182b5ac2004-07-18 06:16:08 +0000175## return audio.format.AudioFormatLinear('quicktime_audio', 'QuickTime Audio Format',
Jack Jansen0ae32202003-04-09 13:25:43 +0000176## channels, encoding, blocksize=blocksize, fpb=fpb, bps=bps)
177 return channels, encoding, blocksize, fpb, bps
Tim Peters182b5ac2004-07-18 06:16:08 +0000178
Jack Jansen0ae32202003-04-09 13:25:43 +0000179 def GetAudioFrameRate(self):
180 if not self.audiodescr:
181 return None
182 return int(self.audiodescr['sampleRate'])
Tim Peters182b5ac2004-07-18 06:16:08 +0000183
Jack Jansen0ae32202003-04-09 13:25:43 +0000184 def GetVideoFormat(self):
185 width = self.videodescr['width']
186 height = self.videodescr['height']
187 return VideoFormat('dummy_format', 'Dummy Video Format', width, height, macrgb)
Tim Peters182b5ac2004-07-18 06:16:08 +0000188
Jack Jansen0ae32202003-04-09 13:25:43 +0000189 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)
Tim Peters182b5ac2004-07-18 06:16:08 +0000197
Jack Jansen0ae32202003-04-09 13:25:43 +0000198 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
Tim Peters182b5ac2004-07-18 06:16:08 +0000213
Jack Jansen0ae32202003-04-09 13:25:43 +0000214 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()
Tim Peters182b5ac2004-07-18 06:16:08 +0000229
Jack Jansen0ae32202003-04-09 13:25:43 +0000230 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
Jack Jansena647f572000-09-15 13:43:46 +0000242
243def reader(url):
Jack Jansen0ae32202003-04-09 13:25:43 +0000244 try:
245 rdr = _Reader(url)
246 except IOError:
247 return None
248 return rdr
Jack Jansena647f572000-09-15 13:43:46 +0000249
250def _test():
Jack Jansen0ae32202003-04-09 13:25:43 +0000251 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)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000275 if not img: print('Not', end=' ')
276 print('Writing %s, size %dx%d, %d bytes'%(fname, imgw, imgh, len(data)))
Jack Jansen0ae32202003-04-09 13:25:43 +0000277 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')
Tim Peters182b5ac2004-07-18 06:16:08 +0000284 if num > 20:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000285 print('stopping at 20 frames so your disk does not fill up:-)')
Jack Jansen0ae32202003-04-09 13:25:43 +0000286 break
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000287 print('Total frames:', num)
Tim Peters182b5ac2004-07-18 06:16:08 +0000288
Jack Jansena647f572000-09-15 13:43:46 +0000289if __name__ == '__main__':
Jack Jansen0ae32202003-04-09 13:25:43 +0000290 _test()
291 sys.exit(1)