|
1 # 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 # |
|
8 |
|
9 from warnings import warnpy3k |
|
10 warnpy3k("In 3.x, the videoreader module is removed.", stacklevel=2) |
|
11 |
|
12 |
|
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 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 |
|
34 |
|
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 |
|
42 |
|
43 def getname(self): |
|
44 return self.__name |
|
45 |
|
46 def getdescr(self): |
|
47 return self.__descr |
|
48 |
|
49 def getsize(self): |
|
50 return self.__width, self.__height |
|
51 |
|
52 def getformat(self): |
|
53 return self.__format |
|
54 |
|
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 |
|
74 |
|
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 |
|
88 |
|
89 |
|
90 def __del__(self): |
|
91 self.audiomedia = None |
|
92 self.audiotrack = None |
|
93 self.videomedia = None |
|
94 self.videotrack = None |
|
95 self.movie = None |
|
96 |
|
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) |
|
116 |
|
117 def _gettrackduration_ms(self, track): |
|
118 tracktime = track.GetTrackDuration() |
|
119 return self._movietime_to_ms(tracktime) |
|
120 |
|
121 def _movietime_to_ms(self, time): |
|
122 value, d1, d2 = Qt.ConvertTimeScale((time, self.movietimescale, None), 1000) |
|
123 return value |
|
124 |
|
125 def _videotime_to_ms(self, time): |
|
126 value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None), 1000) |
|
127 return value |
|
128 |
|
129 def _audiotime_to_ms(self, time): |
|
130 value, d1, d2 = Qt.ConvertTimeScale((time, self.audiotimescale, None), 1000) |
|
131 return value |
|
132 |
|
133 def _videotime_to_movietime(self, time): |
|
134 value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None), |
|
135 self.movietimescale) |
|
136 return value |
|
137 |
|
138 def HasAudio(self): |
|
139 return not self.audiotrack is None |
|
140 |
|
141 def HasVideo(self): |
|
142 return not self.videotrack is None |
|
143 |
|
144 def GetAudioDuration(self): |
|
145 if not self.audiotrack: |
|
146 return 0 |
|
147 return self._gettrackduration_ms(self.audiotrack) |
|
148 |
|
149 def GetVideoDuration(self): |
|
150 if not self.videotrack: |
|
151 return 0 |
|
152 return self._gettrackduration_ms(self.videotrack) |
|
153 |
|
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 |
|
183 |
|
184 def GetAudioFrameRate(self): |
|
185 if not self.audiodescr: |
|
186 return None |
|
187 return int(self.audiodescr['sampleRate']) |
|
188 |
|
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) |
|
193 |
|
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) |
|
202 |
|
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 |
|
218 |
|
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() |
|
234 |
|
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 = rv + nextline |
|
246 return rv |
|
247 |
|
248 def reader(url): |
|
249 try: |
|
250 rdr = _Reader(url) |
|
251 except IOError: |
|
252 return None |
|
253 return rdr |
|
254 |
|
255 def _test(): |
|
256 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') |
|
289 if num > 20: |
|
290 print 'stopping at 20 frames so your disk does not fill up:-)' |
|
291 break |
|
292 print 'Total frames:', num |
|
293 |
|
294 if __name__ == '__main__': |
|
295 _test() |
|
296 sys.exit(1) |