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 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 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.append(nextline) 246 return ''.join(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 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 291 292 if __name__ == '__main__': 293 _test() 294 sys.exit(1) 295