videoreader.py 10.2 KB
Newer Older
1 2 3 4 5 6 7
# Video file reader, using QuickTime
#
# This module was quickly ripped out of another software package, so there is a good
# chance that it does not work as-is and it needs some hacking.
#
# Jack Jansen, August 2000
#
8 9

from warnings import warnpy3k
Benjamin Peterson's avatar
Benjamin Peterson committed
10
warnpy3k("In 3.x, the videoreader module is removed.", stacklevel=2)
11 12


13
import sys
14 15 16 17 18 19
from Carbon import Qt
from Carbon import QuickTime
from Carbon import Qd
from Carbon import Qdoffs
from Carbon import QDOffscreen
from Carbon import Res
20
try:
Ronald Oussoren's avatar
Ronald Oussoren committed
21
    from Carbon import MediaDescr
22
except ImportError:
Jack Jansen's avatar
Jack Jansen committed
23 24
    def _audiodescr(data):
        return None
25
else:
Jack Jansen's avatar
Jack Jansen committed
26 27
    def _audiodescr(data):
        return MediaDescr.SoundDescription.decode(data)
28
try:
Jack Jansen's avatar
Jack Jansen committed
29
    from imgformat import macrgb
30
except ImportError:
Jack Jansen's avatar
Jack Jansen committed
31
    macrgb = "Macintosh RGB format"
32 33 34 35
import os
# import audio.format

class VideoFormat:
Jack Jansen's avatar
Jack Jansen committed
36 37 38 39 40 41
    def __init__(self, name, descr, width, height, format):
        self.__name = name
        self.__descr = descr
        self.__width = width
        self.__height = height
        self.__format = format
42

Jack Jansen's avatar
Jack Jansen committed
43 44
    def getname(self):
        return self.__name
45

Jack Jansen's avatar
Jack Jansen committed
46 47
    def getdescr(self):
        return self.__descr
48

Jack Jansen's avatar
Jack Jansen committed
49 50
    def getsize(self):
        return self.__width, self.__height
51

Jack Jansen's avatar
Jack Jansen committed
52 53
    def getformat(self):
        return self.__format
54

55
class _Reader:
Jack Jansen's avatar
Jack Jansen committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
    def __init__(self, path):
        fd = Qt.OpenMovieFile(path, 0)
        self.movie, d1, d2 = Qt.NewMovieFromFile(fd, 0, 0)
        self.movietimescale = self.movie.GetMovieTimeScale()
        try:
            self.audiotrack = self.movie.GetMovieIndTrackType(1,
                QuickTime.AudioMediaCharacteristic, QuickTime.movieTrackCharacteristic)
            self.audiomedia = self.audiotrack.GetTrackMedia()
        except Qt.Error:
            self.audiotrack = self.audiomedia = None
            self.audiodescr = {}
        else:
            handle = Res.Handle('')
            n = self.audiomedia.GetMediaSampleDescriptionCount()
            self.audiomedia.GetMediaSampleDescription(1, handle)
            self.audiodescr = _audiodescr(handle.data)
            self.audiotimescale = self.audiomedia.GetMediaTimeScale()
            del handle
74 75

        try:
Jack Jansen's avatar
Jack Jansen committed
76 77 78 79 80 81 82 83 84 85 86 87
            self.videotrack = self.movie.GetMovieIndTrackType(1,
                QuickTime.VisualMediaCharacteristic, QuickTime.movieTrackCharacteristic)
            self.videomedia = self.videotrack.GetTrackMedia()
        except Qt.Error:
            self.videotrack = self.videomedia = self.videotimescale = None
        if self.videotrack:
            self.videotimescale = self.videomedia.GetMediaTimeScale()
            x0, y0, x1, y1 = self.movie.GetMovieBox()
            self.videodescr = {'width':(x1-x0), 'height':(y1-y0)}
            self._initgworld()
        self.videocurtime = None
        self.audiocurtime = None
88

89

Jack Jansen's avatar
Jack Jansen committed
90 91 92 93 94 95
    def __del__(self):
        self.audiomedia = None
        self.audiotrack = None
        self.videomedia = None
        self.videotrack = None
        self.movie = None
96

Jack Jansen's avatar
Jack Jansen committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    def _initgworld(self):
        old_port, old_dev = Qdoffs.GetGWorld()
        try:
            movie_w = self.videodescr['width']
            movie_h = self.videodescr['height']
            movie_rect = (0, 0, movie_w, movie_h)
            self.gworld = Qdoffs.NewGWorld(32,  movie_rect, None, None, QDOffscreen.keepLocal)
            self.pixmap = self.gworld.GetGWorldPixMap()
            Qdoffs.LockPixels(self.pixmap)
            Qdoffs.SetGWorld(self.gworld.as_GrafPtr(), None)
            Qd.EraseRect(movie_rect)
            self.movie.SetMovieGWorld(self.gworld.as_GrafPtr(), None)
            self.movie.SetMovieBox(movie_rect)
            self.movie.SetMovieActive(1)
            self.movie.MoviesTask(0)
            self.movie.SetMoviePlayHints(QuickTime.hintsHighQuality, QuickTime.hintsHighQuality)
            # XXXX framerate
        finally:
            Qdoffs.SetGWorld(old_port, old_dev)
116

Jack Jansen's avatar
Jack Jansen committed
117 118 119
    def _gettrackduration_ms(self, track):
        tracktime = track.GetTrackDuration()
        return self._movietime_to_ms(tracktime)
120

Jack Jansen's avatar
Jack Jansen committed
121 122 123
    def _movietime_to_ms(self, time):
        value, d1, d2 = Qt.ConvertTimeScale((time, self.movietimescale, None), 1000)
        return value
124

Jack Jansen's avatar
Jack Jansen committed
125 126 127
    def _videotime_to_ms(self, time):
        value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None), 1000)
        return value
128

Jack Jansen's avatar
Jack Jansen committed
129 130 131
    def _audiotime_to_ms(self, time):
        value, d1, d2 = Qt.ConvertTimeScale((time, self.audiotimescale, None), 1000)
        return value
132

Jack Jansen's avatar
Jack Jansen committed
133 134 135 136
    def _videotime_to_movietime(self, time):
        value, d1, d2 = Qt.ConvertTimeScale((time, self.videotimescale, None),
                self.movietimescale)
        return value
137

Jack Jansen's avatar
Jack Jansen committed
138 139
    def HasAudio(self):
        return not self.audiotrack is None
140

Jack Jansen's avatar
Jack Jansen committed
141 142
    def HasVideo(self):
        return not self.videotrack is None
143

Jack Jansen's avatar
Jack Jansen committed
144 145 146 147
    def GetAudioDuration(self):
        if not self.audiotrack:
            return 0
        return self._gettrackduration_ms(self.audiotrack)
148

Jack Jansen's avatar
Jack Jansen committed
149 150 151 152
    def GetVideoDuration(self):
        if not self.videotrack:
            return 0
        return self._gettrackduration_ms(self.videotrack)
153

Jack Jansen's avatar
Jack Jansen committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
    def GetAudioFormat(self):
        if not self.audiodescr:
            return None, None, None, None, None
        bps = self.audiodescr['sampleSize']
        nch = self.audiodescr['numChannels']
        if nch == 1:
            channels = ['mono']
        elif nch == 2:
            channels = ['left', 'right']
        else:
            channels = map(lambda x: str(x+1), range(nch))
        if bps % 8:
            # Funny bits-per sample. We pretend not to understand
            blocksize = 0
            fpb = 0
        else:
            # QuickTime is easy (for as far as we support it): samples are always a whole
            # number of bytes, so frames are nchannels*samplesize, and there's one frame per block.
            blocksize = (bps/8)*nch
            fpb = 1
        if self.audiodescr['dataFormat'] == 'raw ':
            encoding = 'linear-excess'
        elif self.audiodescr['dataFormat'] == 'twos':
            encoding = 'linear-signed'
        else:
            encoding = 'quicktime-coding-%s'%self.audiodescr['dataFormat']
180
##      return audio.format.AudioFormatLinear('quicktime_audio', 'QuickTime Audio Format',
Jack Jansen's avatar
Jack Jansen committed
181 182
##          channels, encoding, blocksize=blocksize, fpb=fpb, bps=bps)
        return channels, encoding, blocksize, fpb, bps
183

Jack Jansen's avatar
Jack Jansen committed
184 185 186 187
    def GetAudioFrameRate(self):
        if not self.audiodescr:
            return None
        return int(self.audiodescr['sampleRate'])
188

Jack Jansen's avatar
Jack Jansen committed
189 190 191 192
    def GetVideoFormat(self):
        width = self.videodescr['width']
        height = self.videodescr['height']
        return VideoFormat('dummy_format', 'Dummy Video Format', width, height, macrgb)
193

Jack Jansen's avatar
Jack Jansen committed
194 195
    def GetVideoFrameRate(self):
        tv = self.videocurtime
196
        if tv is None:
Jack Jansen's avatar
Jack Jansen committed
197 198 199 200 201
            tv = 0
        flags = QuickTime.nextTimeStep|QuickTime.nextTimeEdgeOK
        tv, dur = self.videomedia.GetMediaNextInterestingTime(flags, tv, 1.0)
        dur = self._videotime_to_ms(dur)
        return int((1000.0/dur)+0.5)
202

Jack Jansen's avatar
Jack Jansen committed
203 204 205 206
    def ReadAudio(self, nframes, time=None):
        if not time is None:
            self.audiocurtime = time
        flags = QuickTime.nextTimeStep|QuickTime.nextTimeEdgeOK
207
        if self.audiocurtime is None:
Jack Jansen's avatar
Jack Jansen committed
208 209 210 211 212 213 214 215 216 217
            self.audiocurtime = 0
        tv = self.audiomedia.GetMediaNextInterestingTimeOnly(flags, self.audiocurtime, 1.0)
        if tv < 0 or (self.audiocurtime and tv < self.audiocurtime):
            return self._audiotime_to_ms(self.audiocurtime), None
        h = Res.Handle('')
        desc_h = Res.Handle('')
        size, actualtime, sampleduration, desc_index, actualcount, flags = \
            self.audiomedia.GetMediaSample(h, 0, tv, desc_h, nframes)
        self.audiocurtime = actualtime + actualcount*sampleduration
        return self._audiotime_to_ms(actualtime), h.data
218

Jack Jansen's avatar
Jack Jansen committed
219 220 221 222
    def ReadVideo(self, time=None):
        if not time is None:
            self.videocurtime = time
        flags = QuickTime.nextTimeStep
223
        if self.videocurtime is None:
Jack Jansen's avatar
Jack Jansen committed
224 225 226 227 228 229 230 231 232 233
            flags = flags | QuickTime.nextTimeEdgeOK
            self.videocurtime = 0
        tv = self.videomedia.GetMediaNextInterestingTimeOnly(flags, self.videocurtime, 1.0)
        if tv < 0 or (self.videocurtime and tv <= self.videocurtime):
            return self._videotime_to_ms(self.videocurtime), None
        self.videocurtime = tv
        moviecurtime = self._videotime_to_movietime(self.videocurtime)
        self.movie.SetMovieTimeValue(moviecurtime)
        self.movie.MoviesTask(0)
        return self._videotime_to_ms(self.videocurtime), self._getpixmapcontent()
234

Jack Jansen's avatar
Jack Jansen committed
235 236 237 238 239 240
    def _getpixmapcontent(self):
        """Shuffle the offscreen PixMap data, because it may have funny stride values"""
        rowbytes = Qdoffs.GetPixRowBytes(self.pixmap)
        width = self.videodescr['width']
        height = self.videodescr['height']
        start = 0
Ronald Oussoren's avatar
Ronald Oussoren committed
241
        rv = []
Jack Jansen's avatar
Jack Jansen committed
242 243 244
        for i in range(height):
            nextline = Qdoffs.GetPixMapBytes(self.pixmap, start, width*4)
            start = start + rowbytes
Ronald Oussoren's avatar
Ronald Oussoren committed
245 246
            rv.append(nextline)
        return ''.join(rv)
247 248

def reader(url):
Jack Jansen's avatar
Jack Jansen committed
249 250 251 252 253
    try:
        rdr = _Reader(url)
    except IOError:
        return None
    return rdr
254 255

def _test():
Jack Jansen's avatar
Jack Jansen committed
256 257
    import EasyDialogs
    try:
Ronald Oussoren's avatar
Ronald Oussoren committed
258
        from PIL import Image
Jack Jansen's avatar
Jack Jansen committed
259
    except ImportError:
Ronald Oussoren's avatar
Ronald Oussoren committed
260
        Image = None
Jack Jansen's avatar
Jack Jansen committed
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
    import MacOS
    Qt.EnterMovies()
    path = EasyDialogs.AskFileForOpen(message='Video to convert')
    if not path: sys.exit(0)
    rdr = reader(path)
    if not rdr:
        sys.exit(1)
    dstdir = EasyDialogs.AskFileForSave(message='Name for output folder')
    if not dstdir: sys.exit(0)
    num = 0
    os.mkdir(dstdir)
    videofmt = rdr.GetVideoFormat()
    imgfmt = videofmt.getformat()
    imgw, imgh = videofmt.getsize()
    timestamp, data = rdr.ReadVideo()
    while data:
        fname = 'frame%04.4d.jpg'%num
        num = num+1
        pname = os.path.join(dstdir, fname)
Ronald Oussoren's avatar
Ronald Oussoren committed
280
        if not Image: print 'Not',
Jack Jansen's avatar
Jack Jansen committed
281
        print 'Writing %s, size %dx%d, %d bytes'%(fname, imgw, imgh, len(data))
Ronald Oussoren's avatar
Ronald Oussoren committed
282 283 284
        if Image:
            img = Image.fromstring("RGBA", (imgw, imgh), data)
            img.save(pname, 'JPEG')
Jack Jansen's avatar
Jack Jansen committed
285 286
            timestamp, data = rdr.ReadVideo()
            MacOS.SetCreatorAndType(pname, 'ogle', 'JPEG')
287
            if num > 20:
Jack Jansen's avatar
Jack Jansen committed
288 289 290
                print 'stopping at 20 frames so your disk does not fill up:-)'
                break
    print 'Total frames:', num
291

292
if __name__ == '__main__':
Jack Jansen's avatar
Jack Jansen committed
293 294
    _test()
    sys.exit(1)