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