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 9from warnings import warnpy3k 10warnpy3k("In 3.x, the videoreader module is removed.", stacklevel=2) 11 12 13import sys 14from Carbon import Qt 15from Carbon import QuickTime 16from Carbon import Qd 17from Carbon import Qdoffs 18from Carbon import QDOffscreen 19from Carbon import Res 20try: 21 from Carbon import MediaDescr 22except ImportError: 23 def _audiodescr(data): 24 return None 25else: 26 def _audiodescr(data): 27 return MediaDescr.SoundDescription.decode(data) 28try: 29 from imgformat import macrgb 30except ImportError: 31 macrgb = "Macintosh RGB format" 32import os 33# import audio.format 34 35class 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 55class _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 248def reader(url): 249 try: 250 rdr = _Reader(url) 251 except IOError: 252 return None 253 return rdr 254 255def _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 292if __name__ == '__main__': 293 _test() 294 sys.exit(1) 295