| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 1 | # Stuff to parse AIFF-C and AIFF files. | 
|  | 2 | # | 
|  | 3 | # Unless explicitly stated otherwise, the description below is true | 
|  | 4 | # both for AIFF-C files and AIFF files. | 
|  | 5 | # | 
|  | 6 | # An AIFF-C file has the following structure. | 
|  | 7 | # | 
|  | 8 | #	+-----------------+ | 
|  | 9 | #	| FORM            | | 
|  | 10 | #	+-----------------+ | 
|  | 11 | #	| <size>          | | 
|  | 12 | #	+----+------------+ | 
|  | 13 | #	|    | AIFC       | | 
|  | 14 | #	|    +------------+ | 
|  | 15 | #	|    | <chunks>   | | 
|  | 16 | #	|    |    .       | | 
|  | 17 | #	|    |    .       | | 
|  | 18 | #	|    |    .       | | 
|  | 19 | #	+----+------------+ | 
|  | 20 | # | 
|  | 21 | # An AIFF file has the string "AIFF" instead of "AIFC". | 
|  | 22 | # | 
|  | 23 | # A chunk consists of an identifier (4 bytes) followed by a size (4 bytes, | 
|  | 24 | # big endian order), followed by the data.  The size field does not include | 
|  | 25 | # the size of the 8 byte header. | 
|  | 26 | # | 
|  | 27 | # The following chunk types are recognized. | 
|  | 28 | # | 
|  | 29 | #	FVER | 
|  | 30 | #		<version number of AIFF-C defining document> (AIFF-C only). | 
|  | 31 | #	MARK | 
|  | 32 | #		<# of markers> (2 bytes) | 
|  | 33 | #		list of markers: | 
|  | 34 | #			<marker ID> (2 bytes, must be > 0) | 
|  | 35 | #			<position> (4 bytes) | 
|  | 36 | #			<marker name> ("pstring") | 
|  | 37 | #	COMM | 
|  | 38 | #		<# of channels> (2 bytes) | 
|  | 39 | #		<# of sound frames> (4 bytes) | 
|  | 40 | #		<size of the samples> (2 bytes) | 
|  | 41 | #		<sampling frequency> (10 bytes, IEEE 80-bit extended | 
|  | 42 | #			floating point) | 
|  | 43 | #		if AIFF-C files only: | 
|  | 44 | #		<compression type> (4 bytes) | 
|  | 45 | #		<human-readable version of compression type> ("pstring") | 
|  | 46 | #	SSND | 
|  | 47 | #		<offset> (4 bytes, not used by this program) | 
|  | 48 | #		<blocksize> (4 bytes, not used by this program) | 
|  | 49 | #		<sound data> | 
|  | 50 | # | 
|  | 51 | # A pstring consists of 1 byte length, a string of characters, and 0 or 1 | 
|  | 52 | # byte pad to make the total length even. | 
|  | 53 | # | 
|  | 54 | # Usage. | 
|  | 55 | # | 
|  | 56 | # Reading AIFF files: | 
|  | 57 | #	f = aifc.open(file, 'r') | 
|  | 58 | # or | 
|  | 59 | #	f = aifc.openfp(filep, 'r') | 
|  | 60 | # where file is the name of a file and filep is an open file pointer. | 
|  | 61 | # The open file pointer must have methods  read(), seek(), and | 
|  | 62 | # close().  In some types of audio files, if the setpos() method is | 
|  | 63 | # not used, the seek() method is not necessary. | 
|  | 64 | # | 
|  | 65 | # This returns an instance of a class with the following public methods: | 
|  | 66 | #	getnchannels()	-- returns number of audio channels (1 for | 
|  | 67 | #			   mono, 2 for stereo) | 
|  | 68 | #	getsampwidth()	-- returns sample width in bytes | 
|  | 69 | #	getframerate()	-- returns sampling frequency | 
|  | 70 | #	getnframes()	-- returns number of audio frames | 
|  | 71 | #	getcomptype()	-- returns compression type ('NONE' for AIFF files) | 
|  | 72 | #	getcompname()	-- returns human-readable version of | 
|  | 73 | #			   compression type ('not compressed' for AIFF files) | 
|  | 74 | #	getparams()	-- returns a tuple consisting of all of the | 
|  | 75 | #			   above in the above order | 
|  | 76 | #	getmarkers()	-- get the list of marks in the audio file or None | 
|  | 77 | #			   if there are no marks | 
|  | 78 | #	getmark(id)	-- get mark with the specified id (raises an error | 
|  | 79 | #			   if the mark does not exist) | 
|  | 80 | #	readframes(n)	-- returns at most n frames of audio | 
|  | 81 | #	rewind()	-- rewind to the beginning of the audio stream | 
|  | 82 | #	setpos(pos)	-- seek to the specified position | 
|  | 83 | #	tell()		-- return the current position | 
| Guido van Rossum | 17ed1ae | 1993-06-01 13:21:04 +0000 | [diff] [blame] | 84 | #	close()		-- close the instance (make it unusable) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 85 | # The position returned by tell(), the position given to setpos() and | 
|  | 86 | # the position of marks are all compatible and have nothing to do with | 
|  | 87 | # the actual postion in the file. | 
|  | 88 | # | 
|  | 89 | # Writing AIFF files: | 
|  | 90 | #	f = aifc.open(file, 'w') | 
|  | 91 | # or | 
|  | 92 | #	f = aifc.openfp(filep, 'w') | 
|  | 93 | # where file is the name of a file and filep is an open file pointer. | 
|  | 94 | # The open file pointer must have methods write(), tell(), seek(), and | 
|  | 95 | # close(). | 
|  | 96 | # | 
|  | 97 | # This returns an instance of a class with the following public methods: | 
|  | 98 | #	aiff()		-- create an AIFF file (AIFF-C default) | 
|  | 99 | #	aifc()		-- create an AIFF-C file | 
|  | 100 | #	setnchannels(n)	-- set the number of channels | 
|  | 101 | #	setsampwidth(n)	-- set the sample width | 
|  | 102 | #	setframerate(n)	-- set the frame rate | 
|  | 103 | #	setnframes(n)	-- set the number of frames | 
|  | 104 | #	setcomptype(type, name) | 
|  | 105 | #			-- set the compression type and the | 
|  | 106 | #			   human-readable compression type | 
|  | 107 | #	setparams(nchannels, sampwidth, framerate, nframes, comptype, compname) | 
|  | 108 | #			-- set all parameters at once | 
|  | 109 | #	setmark(id, pos, name) | 
|  | 110 | #			-- add specified mark to the list of marks | 
|  | 111 | #	tell()		-- return current position in output file (useful | 
|  | 112 | #			   in combination with setmark()) | 
|  | 113 | #	writeframesraw(data) | 
|  | 114 | #			-- write audio frames without pathing up the | 
|  | 115 | #			   file header | 
|  | 116 | #	writeframes(data) | 
|  | 117 | #			-- write audio frames and patch up the file header | 
|  | 118 | #	close()		-- patch up the file header and close the | 
|  | 119 | #			   output file | 
|  | 120 | # You should set the parameters before the first writeframesraw or | 
|  | 121 | # writeframes.  The total number of frames does not need to be set, | 
|  | 122 | # but when it is set to the correct value, the header does not have to | 
|  | 123 | # be patched up. | 
|  | 124 | # It is best to first set all parameters, perhaps possibly the | 
|  | 125 | # compression type, and the write audio frames using writeframesraw. | 
|  | 126 | # When all frames have been written, either call writeframes('') or | 
|  | 127 | # close() to patch up the sizes in the header. | 
|  | 128 | # Marks can be added anytime.  If there are any marks, ypu must call | 
|  | 129 | # close() after all frames have been written. | 
|  | 130 | # | 
|  | 131 | # When a file is opened with the extension '.aiff', an AIFF file is | 
|  | 132 | # written, otherwise an AIFF-C file is written.  This default can be | 
|  | 133 | # changed by calling aiff() or aifc() before the first writeframes or | 
|  | 134 | # writeframesraw. | 
|  | 135 |  | 
|  | 136 | import builtin | 
|  | 137 | import AL | 
|  | 138 | try: | 
|  | 139 | import CL | 
|  | 140 | except ImportError: | 
|  | 141 | pass | 
|  | 142 |  | 
|  | 143 | Error = 'aifc.Error' | 
|  | 144 |  | 
|  | 145 | _AIFC_version = 0xA2805140		# Version 1 of AIFF-C | 
|  | 146 |  | 
|  | 147 | _skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \ | 
|  | 148 | 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO' | 
|  | 149 |  | 
|  | 150 | _nchannelslist = [(1, AL.MONO), (2, AL.STEREO)] | 
|  | 151 | _sampwidthlist = [(8, AL.SAMPLE_8), (16, AL.SAMPLE_16), (24, AL.SAMPLE_24)] | 
|  | 152 | _frameratelist = [(48000, AL.RATE_48000), \ | 
|  | 153 | (44100, AL.RATE_44100), \ | 
|  | 154 | (32000, AL.RATE_32000), \ | 
|  | 155 | (22050, AL.RATE_22050), \ | 
|  | 156 | (16000, AL.RATE_16000), \ | 
|  | 157 | (11025, AL.RATE_11025), \ | 
|  | 158 | ( 8000,  AL.RATE_8000)] | 
|  | 159 |  | 
|  | 160 | def _convert1(value, list): | 
|  | 161 | for t in list: | 
|  | 162 | if value == t[0]: | 
|  | 163 | return t[1] | 
|  | 164 | raise Error, 'unknown parameter value' | 
|  | 165 |  | 
|  | 166 | def _convert2(value, list): | 
|  | 167 | for t in list: | 
|  | 168 | if value == t[1]: | 
|  | 169 | return t[0] | 
|  | 170 | raise Error, 'unknown parameter value' | 
|  | 171 |  | 
|  | 172 | def _read_long(file): | 
|  | 173 | x = 0L | 
|  | 174 | for i in range(4): | 
|  | 175 | byte = file.read(1) | 
|  | 176 | if byte == '': | 
|  | 177 | raise EOFError | 
|  | 178 | x = x*256 + ord(byte) | 
|  | 179 | if x >= 0x80000000L: | 
|  | 180 | x = x - 0x100000000L | 
|  | 181 | return int(x) | 
|  | 182 |  | 
|  | 183 | def _read_ulong(file): | 
|  | 184 | x = 0L | 
|  | 185 | for i in range(4): | 
|  | 186 | byte = file.read(1) | 
|  | 187 | if byte == '': | 
|  | 188 | raise EOFError | 
|  | 189 | x = x*256 + ord(byte) | 
|  | 190 | return x | 
|  | 191 |  | 
|  | 192 | def _read_short(file): | 
|  | 193 | x = 0 | 
|  | 194 | for i in range(2): | 
|  | 195 | byte = file.read(1) | 
|  | 196 | if byte == '': | 
|  | 197 | raise EOFError | 
|  | 198 | x = x*256 + ord(byte) | 
|  | 199 | if x >= 0x8000: | 
|  | 200 | x = x - 0x10000 | 
|  | 201 | return x | 
|  | 202 |  | 
|  | 203 | def _read_string(file): | 
|  | 204 | length = ord(file.read(1)) | 
|  | 205 | data = file.read(length) | 
|  | 206 | if length & 1 == 0: | 
|  | 207 | dummy = file.read(1) | 
|  | 208 | return data | 
|  | 209 |  | 
|  | 210 | _HUGE_VAL = 1.79769313486231e+308 # See <limits.h> | 
|  | 211 |  | 
|  | 212 | def _read_float(f): # 10 bytes | 
|  | 213 | import math | 
|  | 214 | expon = _read_short(f) # 2 bytes | 
|  | 215 | sign = 1 | 
|  | 216 | if expon < 0: | 
|  | 217 | sign = -1 | 
|  | 218 | expon = expon + 0x8000 | 
|  | 219 | himant = _read_ulong(f) # 4 bytes | 
|  | 220 | lomant = _read_ulong(f) # 4 bytes | 
|  | 221 | if expon == himant == lomant == 0: | 
|  | 222 | f = 0.0 | 
|  | 223 | elif expon == 0x7FFF: | 
|  | 224 | f = _HUGE_VAL | 
|  | 225 | else: | 
|  | 226 | expon = expon - 16383 | 
|  | 227 | f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63) | 
|  | 228 | return sign * f | 
|  | 229 |  | 
|  | 230 | def _write_short(f, x): | 
|  | 231 | d, m = divmod(x, 256) | 
|  | 232 | f.write(chr(d)) | 
|  | 233 | f.write(chr(m)) | 
|  | 234 |  | 
|  | 235 | def _write_long(f, x): | 
|  | 236 | if x < 0: | 
|  | 237 | x = x + 0x100000000L | 
|  | 238 | data = [] | 
|  | 239 | for i in range(4): | 
|  | 240 | d, m = divmod(x, 256) | 
|  | 241 | data.insert(0, m) | 
|  | 242 | x = d | 
|  | 243 | for i in range(4): | 
|  | 244 | f.write(chr(int(data[i]))) | 
|  | 245 |  | 
|  | 246 | def _write_string(f, s): | 
|  | 247 | f.write(chr(len(s))) | 
|  | 248 | f.write(s) | 
|  | 249 | if len(s) & 1 == 0: | 
|  | 250 | f.write(chr(0)) | 
|  | 251 |  | 
|  | 252 | def _write_float(f, x): | 
|  | 253 | import math | 
|  | 254 | if x < 0: | 
|  | 255 | sign = 0x8000 | 
|  | 256 | x = x * -1 | 
|  | 257 | else: | 
|  | 258 | sign = 0 | 
|  | 259 | if x == 0: | 
|  | 260 | expon = 0 | 
|  | 261 | himant = 0 | 
|  | 262 | lomant = 0 | 
|  | 263 | else: | 
|  | 264 | fmant, expon = math.frexp(x) | 
|  | 265 | if expon > 16384 or fmant >= 1:		# Infinity or NaN | 
|  | 266 | expon = sign|0x7FFF | 
|  | 267 | himant = 0 | 
|  | 268 | lomant = 0 | 
|  | 269 | else:					# Finite | 
|  | 270 | expon = expon + 16382 | 
|  | 271 | if expon < 0:			# denormalized | 
|  | 272 | fmant = math.ldexp(fmant, expon) | 
|  | 273 | expon = 0 | 
|  | 274 | expon = expon | sign | 
|  | 275 | fmant = math.ldexp(fmant, 32) | 
|  | 276 | fsmant = math.floor(fmant) | 
|  | 277 | himant = long(fsmant) | 
|  | 278 | fmant = math.ldexp(fmant - fsmant, 32) | 
|  | 279 | fsmant = math.floor(fmant) | 
|  | 280 | lomant = long(fsmant) | 
|  | 281 | _write_short(f, expon) | 
|  | 282 | _write_long(f, himant) | 
|  | 283 | _write_long(f, lomant) | 
|  | 284 |  | 
| Guido van Rossum | d316607 | 1993-05-24 14:16:22 +0000 | [diff] [blame] | 285 | class Chunk: | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 286 | def init(self, file): | 
|  | 287 | self.file = file | 
|  | 288 | self.chunkname = self.file.read(4) | 
|  | 289 | if len(self.chunkname) < 4: | 
|  | 290 | raise EOFError | 
|  | 291 | self.chunksize = _read_long(self.file) | 
|  | 292 | self.size_read = 0 | 
|  | 293 | self.offset = self.file.tell() | 
|  | 294 | return self | 
|  | 295 |  | 
|  | 296 | def rewind(self): | 
|  | 297 | self.file.seek(self.offset, 0) | 
|  | 298 | self.size_read = 0 | 
|  | 299 |  | 
|  | 300 | def setpos(self, pos): | 
|  | 301 | if pos < 0 or pos > self.chunksize: | 
|  | 302 | raise RuntimeError | 
|  | 303 | self.file.seek(self.offset + pos, 0) | 
|  | 304 | self.size_read = pos | 
|  | 305 |  | 
|  | 306 | def read(self, length): | 
|  | 307 | if self.size_read >= self.chunksize: | 
|  | 308 | return '' | 
|  | 309 | if length > self.chunksize - self.size_read: | 
|  | 310 | length = self.chunksize - self.size_read | 
|  | 311 | data = self.file.read(length) | 
|  | 312 | self.size_read = self.size_read + len(data) | 
|  | 313 | return data | 
|  | 314 |  | 
|  | 315 | def skip(self): | 
|  | 316 | try: | 
|  | 317 | self.file.seek(self.chunksize - self.size_read, 1) | 
|  | 318 | except RuntimeError: | 
|  | 319 | while self.size_read < self.chunksize: | 
|  | 320 | dummy = self.read(8192) | 
|  | 321 | if not dummy: | 
|  | 322 | raise EOFError | 
|  | 323 | if self.chunksize & 1: | 
|  | 324 | dummy = self.read(1) | 
|  | 325 |  | 
| Guido van Rossum | d316607 | 1993-05-24 14:16:22 +0000 | [diff] [blame] | 326 | class Aifc_read: | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 327 | # Variables used in this class: | 
|  | 328 | # | 
|  | 329 | # These variables are available to the user though appropriate | 
|  | 330 | # methods of this class: | 
|  | 331 | # _file -- the open file with methods read(), close(), and seek() | 
|  | 332 | #		set through the init() ir initfp() method | 
|  | 333 | # _nchannels -- the number of audio channels | 
|  | 334 | #		available through the getnchannels() method | 
|  | 335 | # _nframes -- the number of audio frames | 
|  | 336 | #		available through the getnframes() method | 
|  | 337 | # _sampwidth -- the number of bytes per audio sample | 
|  | 338 | #		available through the getsampwidth() method | 
|  | 339 | # _framerate -- the sampling frequency | 
|  | 340 | #		available through the getframerate() method | 
|  | 341 | # _comptype -- the AIFF-C compression type ('NONE' if AIFF) | 
|  | 342 | #		available through the getcomptype() method | 
|  | 343 | # _compname -- the human-readable AIFF-C compression type | 
|  | 344 | #		available through the getcomptype() method | 
|  | 345 | # _markers -- the marks in the audio file | 
|  | 346 | #		available through the getmarkers() and getmark() | 
|  | 347 | #		methods | 
|  | 348 | # _soundpos -- the position in the audio stream | 
|  | 349 | #		available through the tell() method, set through the | 
|  | 350 | #		tell() method | 
|  | 351 | # | 
|  | 352 | # These variables are used internally only: | 
|  | 353 | # _version -- the AIFF-C version number | 
|  | 354 | # _decomp -- the decompressor from builtin module cl | 
|  | 355 | # _comm_chunk_read -- 1 iff the COMM chunk has been read | 
|  | 356 | # _aifc -- 1 iff reading an AIFF-C file | 
|  | 357 | # _ssnd_seek_needed -- 1 iff positioned correctly in audio | 
|  | 358 | #		file for readframes() | 
|  | 359 | # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 360 | # _framesize -- size of one frame in the file | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 361 | def initfp(self, file): | 
|  | 362 | self._file = file | 
|  | 363 | self._version = 0 | 
|  | 364 | self._decomp = None | 
|  | 365 | self._markers = [] | 
|  | 366 | self._soundpos = 0 | 
|  | 367 | form = self._file.read(4) | 
|  | 368 | if form != 'FORM': | 
|  | 369 | raise Error, 'file does not start with FORM id' | 
|  | 370 | formlength = _read_long(self._file) | 
|  | 371 | if formlength <= 0: | 
|  | 372 | raise Error, 'invalid FORM chunk data size' | 
|  | 373 | formdata = self._file.read(4) | 
|  | 374 | formlength = formlength - 4 | 
|  | 375 | if formdata == 'AIFF': | 
|  | 376 | self._aifc = 0 | 
|  | 377 | elif formdata == 'AIFC': | 
|  | 378 | self._aifc = 1 | 
|  | 379 | else: | 
|  | 380 | raise Error, 'not an AIFF or AIFF-C file' | 
|  | 381 | self._comm_chunk_read = 0 | 
| Sjoerd Mullender | 7564a64 | 1993-01-22 14:26:28 +0000 | [diff] [blame] | 382 | while formlength > 0: | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 383 | self._ssnd_seek_needed = 1 | 
| Sjoerd Mullender | 8d733a0 | 1993-01-29 12:01:00 +0000 | [diff] [blame] | 384 | #DEBUG: SGI's soundfiler has a bug.  There should | 
|  | 385 | # be no need to check for EOF here. | 
|  | 386 | try: | 
|  | 387 | chunk = Chunk().init(self._file) | 
|  | 388 | except EOFError: | 
|  | 389 | if formlength == 8: | 
|  | 390 | print 'Warning: FORM chunk size too large' | 
|  | 391 | formlength = 0 | 
|  | 392 | break | 
|  | 393 | raise EOFError # different error, raise exception | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 394 | formlength = formlength - 8 - chunk.chunksize | 
|  | 395 | if chunk.chunksize & 1: | 
|  | 396 | formlength = formlength - 1 | 
|  | 397 | if chunk.chunkname == 'COMM': | 
|  | 398 | self._read_comm_chunk(chunk) | 
|  | 399 | self._comm_chunk_read = 1 | 
|  | 400 | elif chunk.chunkname == 'SSND': | 
|  | 401 | self._ssnd_chunk = chunk | 
|  | 402 | dummy = chunk.read(8) | 
|  | 403 | self._ssnd_seek_needed = 0 | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 404 | elif chunk.chunkname == 'FVER': | 
|  | 405 | self._version = _read_long(chunk) | 
|  | 406 | elif chunk.chunkname == 'MARK': | 
|  | 407 | self._readmark(chunk) | 
|  | 408 | elif chunk.chunkname in _skiplist: | 
|  | 409 | pass | 
|  | 410 | else: | 
|  | 411 | raise Error, 'unrecognized chunk type '+chunk.chunkname | 
| Sjoerd Mullender | 93f0740 | 1993-01-26 09:24:37 +0000 | [diff] [blame] | 412 | if formlength > 0: | 
|  | 413 | chunk.skip() | 
| Sjoerd Mullender | 7564a64 | 1993-01-22 14:26:28 +0000 | [diff] [blame] | 414 | if not self._comm_chunk_read or not self._ssnd_chunk: | 
|  | 415 | raise Error, 'COMM chunk and/or SSND chunk missing' | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 416 | if self._aifc and self._decomp: | 
|  | 417 | params = [CL.ORIGINAL_FORMAT, 0, \ | 
|  | 418 | CL.BITS_PER_COMPONENT, 0, \ | 
|  | 419 | CL.FRAME_RATE, self._framerate] | 
|  | 420 | if self._nchannels == AL.MONO: | 
|  | 421 | params[1] = CL.MONO | 
|  | 422 | else: | 
|  | 423 | params[1] = CL.STEREO_INTERLEAVED | 
|  | 424 | if self._sampwidth == AL.SAMPLE_8: | 
|  | 425 | params[3] = 8 | 
|  | 426 | elif self._sampwidth == AL.SAMPLE_16: | 
|  | 427 | params[3] = 16 | 
|  | 428 | else: | 
|  | 429 | params[3] = 24 | 
|  | 430 | self._decomp.SetParams(params) | 
|  | 431 | return self | 
|  | 432 |  | 
|  | 433 | def init(self, filename): | 
|  | 434 | return self.initfp(builtin.open(filename, 'r')) | 
|  | 435 |  | 
|  | 436 | # | 
|  | 437 | # User visible methods. | 
|  | 438 | # | 
|  | 439 | def getfp(self): | 
|  | 440 | return self._file | 
|  | 441 |  | 
|  | 442 | def rewind(self): | 
|  | 443 | self._ssnd_seek_needed = 1 | 
|  | 444 | self._soundpos = 0 | 
|  | 445 |  | 
|  | 446 | def close(self): | 
|  | 447 | if self._decomp: | 
|  | 448 | self._decomp.CloseDecompressor() | 
|  | 449 | self._decomp = None | 
|  | 450 | self._file.close() | 
|  | 451 | self._file = None | 
|  | 452 |  | 
|  | 453 | def tell(self): | 
|  | 454 | return self._soundpos | 
|  | 455 |  | 
|  | 456 | def getnchannels(self): | 
|  | 457 | return self._nchannels | 
|  | 458 |  | 
|  | 459 | def getnframes(self): | 
|  | 460 | return self._nframes | 
|  | 461 |  | 
|  | 462 | def getsampwidth(self): | 
|  | 463 | return self._sampwidth | 
|  | 464 |  | 
|  | 465 | def getframerate(self): | 
|  | 466 | return self._framerate | 
|  | 467 |  | 
|  | 468 | def getcomptype(self): | 
|  | 469 | return self._comptype | 
|  | 470 |  | 
|  | 471 | def getcompname(self): | 
|  | 472 | return self._compname | 
|  | 473 |  | 
|  | 474 | ##	def getversion(self): | 
|  | 475 | ##		return self._version | 
|  | 476 |  | 
|  | 477 | def getparams(self): | 
|  | 478 | return self._nchannels, self._sampwidth, self._framerate, \ | 
|  | 479 | self._nframes, self._comptype, self._compname | 
|  | 480 |  | 
|  | 481 | def getmarkers(self): | 
|  | 482 | if len(self._markers) == 0: | 
|  | 483 | return None | 
|  | 484 | return self._markers | 
|  | 485 |  | 
|  | 486 | def getmark(self, id): | 
|  | 487 | for marker in self._markers: | 
|  | 488 | if id == marker[0]: | 
|  | 489 | return marker | 
|  | 490 | raise Error, 'marker ' + `id` + ' does not exist' | 
|  | 491 |  | 
|  | 492 | def setpos(self, pos): | 
|  | 493 | if pos < 0 or pos > self._nframes: | 
|  | 494 | raise Error, 'position not in range' | 
|  | 495 | self._soundpos = pos | 
|  | 496 | self._ssnd_seek_needed = 1 | 
|  | 497 |  | 
|  | 498 | def readframes(self, nframes): | 
|  | 499 | if self._ssnd_seek_needed: | 
|  | 500 | self._ssnd_chunk.rewind() | 
|  | 501 | dummy = self._ssnd_chunk.read(8) | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 502 | pos = self._soundpos * self._framesize | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 503 | if pos: | 
|  | 504 | self._ssnd_chunk.setpos(pos + 8) | 
|  | 505 | self._ssnd_seek_needed = 0 | 
| Sjoerd Mullender | 8d733a0 | 1993-01-29 12:01:00 +0000 | [diff] [blame] | 506 | if nframes == 0: | 
|  | 507 | return '' | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 508 | data = self._ssnd_chunk.read(nframes * self._framesize) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 509 | if self._decomp and data: | 
| Sjoerd Mullender | 4ab6ff8 | 1993-02-05 13:43:44 +0000 | [diff] [blame] | 510 | dummy = self._decomp.SetParam(CL.FRAME_BUFFER_SIZE, \ | 
|  | 511 | len(data) * 2) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 512 | data = self._decomp.Decompress(len(data) / self._nchannels, data) | 
|  | 513 | self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth) | 
|  | 514 | return data | 
|  | 515 |  | 
|  | 516 | # | 
|  | 517 | # Internal methods. | 
|  | 518 | # | 
|  | 519 | def _read_comm_chunk(self, chunk): | 
|  | 520 | nchannels = _read_short(chunk) | 
|  | 521 | self._nchannels = _convert1(nchannels, _nchannelslist) | 
|  | 522 | self._nframes = _read_long(chunk) | 
|  | 523 | sampwidth = _read_short(chunk) | 
|  | 524 | self._sampwidth = _convert1(sampwidth, _sampwidthlist) | 
|  | 525 | framerate = _read_float(chunk) | 
|  | 526 | self._framerate = _convert1(framerate, _frameratelist) | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 527 | self._framesize = self._nchannels * self._sampwidth | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 528 | if self._aifc: | 
|  | 529 | #DEBUG: SGI's soundeditor produces a bad size :-( | 
|  | 530 | kludge = 0 | 
|  | 531 | if chunk.chunksize == 18: | 
|  | 532 | kludge = 1 | 
|  | 533 | print 'Warning: bad COMM chunk size' | 
|  | 534 | chunk.chunksize = 23 | 
|  | 535 | #DEBUG end | 
|  | 536 | self._comptype = chunk.read(4) | 
|  | 537 | #DEBUG start | 
|  | 538 | if kludge: | 
|  | 539 | length = ord(chunk.file.read(1)) | 
|  | 540 | if length & 1 == 0: | 
|  | 541 | length = length + 1 | 
|  | 542 | chunk.chunksize = chunk.chunksize + length | 
|  | 543 | chunk.file.seek(-1, 1) | 
|  | 544 | #DEBUG end | 
|  | 545 | self._compname = _read_string(chunk) | 
|  | 546 | if self._comptype != 'NONE': | 
|  | 547 | try: | 
|  | 548 | import cl, CL | 
|  | 549 | except ImportError: | 
|  | 550 | raise Error, 'cannot read compressed AIFF-C files' | 
|  | 551 | if self._comptype == 'ULAW': | 
|  | 552 | scheme = CL.G711_ULAW | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 553 | self._framesize = self._framesize / 2 | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 554 | elif self._comptype == 'ALAW': | 
|  | 555 | scheme = CL.G711_ALAW | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 556 | self._framesize = self._framesize / 2 | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 557 | else: | 
|  | 558 | raise Error, 'unsupported compression type' | 
|  | 559 | self._decomp = cl.OpenDecompressor(scheme) | 
|  | 560 | else: | 
|  | 561 | self._comptype = 'NONE' | 
|  | 562 | self._compname = 'not compressed' | 
|  | 563 |  | 
|  | 564 | def _readmark(self, chunk): | 
|  | 565 | nmarkers = _read_short(chunk) | 
|  | 566 | for i in range(nmarkers): | 
|  | 567 | id = _read_short(chunk) | 
|  | 568 | pos = _read_long(chunk) | 
|  | 569 | name = _read_string(chunk) | 
|  | 570 | self._markers.append((id, pos, name)) | 
|  | 571 |  | 
| Guido van Rossum | d316607 | 1993-05-24 14:16:22 +0000 | [diff] [blame] | 572 | class Aifc_write: | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 573 | # Variables used in this class: | 
|  | 574 | # | 
|  | 575 | # These variables are user settable through appropriate methods | 
|  | 576 | # of this class: | 
|  | 577 | # _file -- the open file with methods write(), close(), tell(), seek() | 
|  | 578 | #		set through the init() or initfp() method | 
|  | 579 | # _comptype -- the AIFF-C compression type ('NONE' in AIFF) | 
|  | 580 | #		set through the setcomptype() or setparams() method | 
|  | 581 | # _compname -- the human-readable AIFF-C compression type | 
|  | 582 | #		set through the setcomptype() or setparams() method | 
|  | 583 | # _nchannels -- the number of audio channels | 
|  | 584 | #		set through the setnchannels() or setparams() method | 
|  | 585 | # _sampwidth -- the number of bytes per audio sample | 
|  | 586 | #		set through the setsampwidth() or setparams() method | 
|  | 587 | # _framerate -- the sampling frequency | 
|  | 588 | #		set through the setframerate() or setparams() method | 
|  | 589 | # _nframes -- the number of audio frames written to the header | 
|  | 590 | #		set through the setnframes() or setparams() method | 
|  | 591 | # _aifc -- whether we're writing an AIFF-C file or an AIFF file | 
|  | 592 | #		set through the aifc() method, reset through the | 
|  | 593 | #		aiff() method | 
|  | 594 | # | 
|  | 595 | # These variables are used internally only: | 
|  | 596 | # _version -- the AIFF-C version number | 
|  | 597 | # _comp -- the compressor from builtin module cl | 
|  | 598 | # _nframeswritten -- the number of audio frames actually written | 
|  | 599 | # _datalength -- the size of the audio samples written to the header | 
|  | 600 | # _datawritten -- the size of the audio samples actually written | 
|  | 601 |  | 
|  | 602 | def init(self, filename): | 
|  | 603 | self = self.initfp(builtin.open(filename, 'w')) | 
|  | 604 | if filename[-5:] == '.aiff': | 
|  | 605 | self._aifc = 0 | 
|  | 606 | else: | 
|  | 607 | self._aifc = 1 | 
|  | 608 | return self | 
|  | 609 |  | 
|  | 610 | def initfp(self, file): | 
|  | 611 | self._file = file | 
|  | 612 | self._version = _AIFC_version | 
|  | 613 | self._comptype = 'NONE' | 
|  | 614 | self._compname = 'not compressed' | 
|  | 615 | self._comp = None | 
|  | 616 | self._nchannels = 0 | 
|  | 617 | self._sampwidth = 0 | 
|  | 618 | self._framerate = 0 | 
|  | 619 | self._nframes = 0 | 
|  | 620 | self._nframeswritten = 0 | 
|  | 621 | self._datawritten = 0 | 
| Guido van Rossum | 52fc1f6 | 1993-06-17 12:38:10 +0000 | [diff] [blame^] | 622 | self._datalength = 0 | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 623 | self._markers = [] | 
|  | 624 | self._marklength = 0 | 
|  | 625 | self._aifc = 1		# AIFF-C is default | 
|  | 626 | return self | 
|  | 627 |  | 
|  | 628 | # | 
|  | 629 | # User visible methods. | 
|  | 630 | # | 
|  | 631 | def aiff(self): | 
|  | 632 | if self._nframeswritten: | 
|  | 633 | raise Error, 'cannot change parameters after starting to write' | 
|  | 634 | self._aifc = 0 | 
|  | 635 |  | 
|  | 636 | def aifc(self): | 
|  | 637 | if self._nframeswritten: | 
|  | 638 | raise Error, 'cannot change parameters after starting to write' | 
|  | 639 | self._aifc = 1 | 
|  | 640 |  | 
|  | 641 | def setnchannels(self, nchannels): | 
|  | 642 | if self._nframeswritten: | 
|  | 643 | raise Error, 'cannot change parameters after starting to write' | 
| Sjoerd Mullender | 4ab6ff8 | 1993-02-05 13:43:44 +0000 | [diff] [blame] | 644 | dummy = _convert2(nchannels, _nchannelslist) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 645 | self._nchannels = nchannels | 
|  | 646 |  | 
|  | 647 | def getnchannels(self): | 
|  | 648 | if not self._nchannels: | 
|  | 649 | raise Error, 'number of channels not set' | 
|  | 650 | return self._nchannels | 
|  | 651 |  | 
|  | 652 | def setsampwidth(self, sampwidth): | 
|  | 653 | if self._nframeswritten: | 
|  | 654 | raise Error, 'cannot change parameters after starting to write' | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 655 | dummy = _convert2(sampwidth, _sampwidthlist) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 656 | self._sampwidth = sampwidth | 
|  | 657 |  | 
|  | 658 | def getsampwidth(self): | 
|  | 659 | if not self._sampwidth: | 
|  | 660 | raise Error, 'sample width not set' | 
|  | 661 | return self._sampwidth | 
|  | 662 |  | 
|  | 663 | def setframerate(self, framerate): | 
|  | 664 | if self._nframeswritten: | 
|  | 665 | raise Error, 'cannot change parameters after starting to write' | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 666 | dummy = _convert2(framerate, _frameratelist) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 667 | self._framerate = framerate | 
|  | 668 |  | 
|  | 669 | def getframerate(self): | 
|  | 670 | if not self._framerate: | 
|  | 671 | raise Error, 'frame rate not set' | 
|  | 672 | return self._framerate | 
|  | 673 |  | 
|  | 674 | def setnframes(self, nframes): | 
|  | 675 | if self._nframeswritten: | 
|  | 676 | raise Error, 'cannot change parameters after starting to write' | 
|  | 677 | self._nframes = nframes | 
|  | 678 |  | 
|  | 679 | def getnframes(self): | 
|  | 680 | return self._nframeswritten | 
|  | 681 |  | 
|  | 682 | def setcomptype(self, comptype, compname): | 
|  | 683 | if self._nframeswritten: | 
|  | 684 | raise Error, 'cannot change parameters after starting to write' | 
|  | 685 | if comptype not in ('NONE', 'ULAW', 'ALAW'): | 
|  | 686 | raise Error, 'unsupported compression type' | 
|  | 687 | self._comptype = comptype | 
|  | 688 | self._compname = compname | 
|  | 689 |  | 
|  | 690 | def getcomptype(self): | 
|  | 691 | return self._comptype | 
|  | 692 |  | 
|  | 693 | def getcompname(self): | 
|  | 694 | return self._compname | 
|  | 695 |  | 
|  | 696 | ##	def setversion(self, version): | 
|  | 697 | ##		if self._nframeswritten: | 
|  | 698 | ##			raise Error, 'cannot change parameters after starting to write' | 
|  | 699 | ##		self._version = version | 
|  | 700 |  | 
|  | 701 | def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)): | 
|  | 702 | if self._nframeswritten: | 
|  | 703 | raise Error, 'cannot change parameters after starting to write' | 
|  | 704 | if comptype not in ('NONE', 'ULAW', 'ALAW'): | 
|  | 705 | raise Error, 'unsupported compression type' | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 706 | dummy = _convert2(nchannels, _nchannelslist) | 
|  | 707 | dummy = _convert2(sampwidth, _sampwidthlist) | 
|  | 708 | dummy = _convert2(framerate, _frameratelist) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 709 | self._nchannels = nchannels | 
|  | 710 | self._sampwidth = sampwidth | 
|  | 711 | self._framerate = framerate | 
|  | 712 | self._nframes = nframes | 
|  | 713 | self._comptype = comptype | 
|  | 714 | self._compname = compname | 
|  | 715 |  | 
|  | 716 | def getparams(self): | 
|  | 717 | if not self._nchannels or not self._sampwidth or not self._framerate: | 
|  | 718 | raise Error, 'not all parameters set' | 
|  | 719 | return self._nchannels, self._sampwidth, self._framerate, \ | 
|  | 720 | self._nframes, self._comptype, self._compname | 
|  | 721 |  | 
| Sjoerd Mullender | 7564a64 | 1993-01-22 14:26:28 +0000 | [diff] [blame] | 722 | def setmark(self, id, pos, name): | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 723 | if id <= 0: | 
|  | 724 | raise Error, 'marker ID must be > 0' | 
|  | 725 | if pos < 0: | 
|  | 726 | raise Error, 'marker position must be >= 0' | 
|  | 727 | if type(name) != type(''): | 
|  | 728 | raise Error, 'marker name must be a string' | 
|  | 729 | for i in range(len(self._markers)): | 
|  | 730 | if id == self._markers[i][0]: | 
|  | 731 | self._markers[i] = id, pos, name | 
|  | 732 | return | 
|  | 733 | self._markers.append((id, pos, name)) | 
|  | 734 |  | 
|  | 735 | def getmark(self, id): | 
|  | 736 | for marker in self._markers: | 
|  | 737 | if id == marker[0]: | 
|  | 738 | return marker | 
|  | 739 | raise Error, 'marker ' + `id` + ' does not exist' | 
|  | 740 |  | 
|  | 741 | def getmarkers(self): | 
|  | 742 | if len(self._markers) == 0: | 
|  | 743 | return None | 
|  | 744 | return self._markers | 
|  | 745 |  | 
|  | 746 | def writeframesraw(self, data): | 
| Guido van Rossum | 52fc1f6 | 1993-06-17 12:38:10 +0000 | [diff] [blame^] | 747 | self._ensure_header_written(len(data)) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 748 | nframes = len(data) / (self._sampwidth * self._nchannels) | 
|  | 749 | if self._comp: | 
| Sjoerd Mullender | 4ab6ff8 | 1993-02-05 13:43:44 +0000 | [diff] [blame] | 750 | dummy = self._comp.SetParam(CL.FRAME_BUFFER_SIZE, \ | 
|  | 751 | len(data)) | 
|  | 752 | dummy = self._comp.SetParam(CL.COMPRESSED_BUFFER_SIZE,\ | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 753 | len(data)) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 754 | data = self._comp.Compress(nframes, data) | 
|  | 755 | self._file.write(data) | 
|  | 756 | self._nframeswritten = self._nframeswritten + nframes | 
|  | 757 | self._datawritten = self._datawritten + len(data) | 
|  | 758 |  | 
|  | 759 | def writeframes(self, data): | 
|  | 760 | self.writeframesraw(data) | 
|  | 761 | if self._nframeswritten != self._nframes or \ | 
|  | 762 | self._datalength != self._datawritten: | 
|  | 763 | self._patchheader() | 
|  | 764 |  | 
|  | 765 | def close(self): | 
| Guido van Rossum | 52fc1f6 | 1993-06-17 12:38:10 +0000 | [diff] [blame^] | 766 | self._ensure_header_written(0) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 767 | if self._datawritten & 1: | 
|  | 768 | # quick pad to even size | 
|  | 769 | self._file.write(chr(0)) | 
|  | 770 | self._datawritten = self._datawritten + 1 | 
|  | 771 | self._writemarkers() | 
|  | 772 | if self._nframeswritten != self._nframes or \ | 
|  | 773 | self._datalength != self._datawritten or \ | 
|  | 774 | self._marklength: | 
|  | 775 | self._patchheader() | 
|  | 776 | if self._comp: | 
|  | 777 | self._comp.CloseCompressor() | 
|  | 778 | self._comp = None | 
|  | 779 | self._file.close() | 
|  | 780 | self._file = None | 
|  | 781 |  | 
|  | 782 | # | 
|  | 783 | # Internal methods. | 
|  | 784 | # | 
| Guido van Rossum | 52fc1f6 | 1993-06-17 12:38:10 +0000 | [diff] [blame^] | 785 | def _ensure_header_written(self, datasize): | 
|  | 786 | if not self._nframeswritten: | 
|  | 787 | if self._comptype in ('ULAW', 'ALAW'): | 
|  | 788 | if not self._sampwidth: | 
|  | 789 | self._sampwidth = AL.SAMPLE_16 | 
|  | 790 | if self._sampwidth != AL.SAMPLE_16: | 
|  | 791 | raise Error, 'sample width must be 2 when compressing with ULAW or ALAW' | 
|  | 792 | if not self._nchannels: | 
|  | 793 | raise Error, '# channels not specified' | 
|  | 794 | if not self._sampwidth: | 
|  | 795 | raise Error, 'sample width not specified' | 
|  | 796 | if not self._framerate: | 
|  | 797 | raise Error, 'sampling rate not specified' | 
|  | 798 | self._write_header(datasize) | 
|  | 799 |  | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 800 | def _write_header(self, initlength): | 
|  | 801 | if self._aifc and self._comptype != 'NONE': | 
|  | 802 | try: | 
|  | 803 | import cl, CL | 
|  | 804 | except ImportError: | 
|  | 805 | raise Error, 'cannot write compressed AIFF-C files' | 
|  | 806 | if self._comptype == 'ULAW': | 
|  | 807 | scheme = CL.G711_ULAW | 
|  | 808 | elif self._comptype == 'ALAW': | 
|  | 809 | scheme = CL.G711_ALAW | 
|  | 810 | else: | 
|  | 811 | raise Error, 'unsupported compression type' | 
|  | 812 | self._comp = cl.OpenCompressor(scheme) | 
|  | 813 | params = [CL.ORIGINAL_FORMAT, 0, \ | 
|  | 814 | CL.BITS_PER_COMPONENT, 0, \ | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 815 | CL.FRAME_RATE, self._framerate, \ | 
|  | 816 | CL.FRAME_BUFFER_SIZE, 100, \ | 
|  | 817 | CL.COMPRESSED_BUFFER_SIZE, 100] | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 818 | if self._nchannels == AL.MONO: | 
|  | 819 | params[1] = CL.MONO | 
|  | 820 | else: | 
|  | 821 | params[1] = CL.STEREO_INTERLEAVED | 
|  | 822 | if self._sampwidth == AL.SAMPLE_8: | 
|  | 823 | params[3] = 8 | 
|  | 824 | elif self._sampwidth == AL.SAMPLE_16: | 
|  | 825 | params[3] = 16 | 
|  | 826 | else: | 
|  | 827 | params[3] = 24 | 
|  | 828 | self._comp.SetParams(params) | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 829 | # the compressor produces a header which we ignore | 
|  | 830 | dummy = self._comp.Compress(0, '') | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 831 | self._file.write('FORM') | 
|  | 832 | if not self._nframes: | 
|  | 833 | self._nframes = initlength / (self._nchannels * self._sampwidth) | 
|  | 834 | self._datalength = self._nframes * self._nchannels * self._sampwidth | 
|  | 835 | if self._datalength & 1: | 
|  | 836 | self._datalength = self._datalength + 1 | 
|  | 837 | if self._aifc and self._comptype in ('ULAW', 'ALAW'): | 
|  | 838 | self._datalength = self._datalength / 2 | 
|  | 839 | if self._datalength & 1: | 
|  | 840 | self._datalength = self._datalength + 1 | 
|  | 841 | self._form_length_pos = self._file.tell() | 
|  | 842 | commlength = self._write_form_length(self._datalength) | 
|  | 843 | if self._aifc: | 
|  | 844 | self._file.write('AIFC') | 
|  | 845 | self._file.write('FVER') | 
|  | 846 | _write_long(self._file, 4) | 
|  | 847 | _write_long(self._file, self._version) | 
|  | 848 | else: | 
|  | 849 | self._file.write('AIFF') | 
|  | 850 | self._file.write('COMM') | 
|  | 851 | _write_long(self._file, commlength) | 
| Sjoerd Mullender | 3a99727 | 1993-02-04 16:43:28 +0000 | [diff] [blame] | 852 | _write_short(self._file, _convert2(self._nchannels, _nchannelslist)) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 853 | self._nframes_pos = self._file.tell() | 
|  | 854 | _write_long(self._file, self._nframes) | 
|  | 855 | _write_short(self._file, _convert2(self._sampwidth, _sampwidthlist)) | 
|  | 856 | _write_float(self._file, _convert2(self._framerate, _frameratelist)) | 
|  | 857 | if self._aifc: | 
|  | 858 | self._file.write(self._comptype) | 
|  | 859 | _write_string(self._file, self._compname) | 
|  | 860 | self._file.write('SSND') | 
|  | 861 | self._ssnd_length_pos = self._file.tell() | 
|  | 862 | _write_long(self._file, self._datalength + 8) | 
|  | 863 | _write_long(self._file, 0) | 
|  | 864 | _write_long(self._file, 0) | 
|  | 865 |  | 
|  | 866 | def _write_form_length(self, datalength): | 
|  | 867 | if self._aifc: | 
|  | 868 | commlength = 18 + 5 + len(self._compname) | 
|  | 869 | if commlength & 1: | 
|  | 870 | commlength = commlength + 1 | 
|  | 871 | verslength = 12 | 
|  | 872 | else: | 
|  | 873 | commlength = 18 | 
|  | 874 | verslength = 0 | 
|  | 875 | _write_long(self._file, 4 + verslength + self._marklength + \ | 
|  | 876 | 8 + commlength + 16 + datalength) | 
|  | 877 | return commlength | 
|  | 878 |  | 
|  | 879 | def _patchheader(self): | 
|  | 880 | curpos = self._file.tell() | 
|  | 881 | if self._datawritten & 1: | 
|  | 882 | datalength = self._datawritten + 1 | 
|  | 883 | self._file.write(chr(0)) | 
|  | 884 | else: | 
|  | 885 | datalength = self._datawritten | 
|  | 886 | if datalength == self._datalength and \ | 
| Sjoerd Mullender | 7564a64 | 1993-01-22 14:26:28 +0000 | [diff] [blame] | 887 | self._nframes == self._nframeswritten and \ | 
|  | 888 | self._marklength == 0: | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 889 | self._file.seek(curpos, 0) | 
|  | 890 | return | 
|  | 891 | self._file.seek(self._form_length_pos, 0) | 
|  | 892 | dummy = self._write_form_length(datalength) | 
|  | 893 | self._file.seek(self._nframes_pos, 0) | 
|  | 894 | _write_long(self._file, self._nframeswritten) | 
|  | 895 | self._file.seek(self._ssnd_length_pos, 0) | 
|  | 896 | _write_long(self._file, datalength + 8) | 
|  | 897 | self._file.seek(curpos, 0) | 
|  | 898 | self._nframes = self._nframeswritten | 
|  | 899 | self._datalength = datalength | 
|  | 900 |  | 
|  | 901 | def _writemarkers(self): | 
|  | 902 | if len(self._markers) == 0: | 
|  | 903 | return | 
|  | 904 | self._file.write('MARK') | 
|  | 905 | length = 2 | 
|  | 906 | for marker in self._markers: | 
|  | 907 | id, pos, name = marker | 
|  | 908 | length = length + len(name) + 1 + 6 | 
|  | 909 | if len(name) & 1 == 0: | 
|  | 910 | length = length + 1 | 
|  | 911 | _write_long(self._file, length) | 
|  | 912 | self._marklength = length + 8 | 
| Sjoerd Mullender | 7564a64 | 1993-01-22 14:26:28 +0000 | [diff] [blame] | 913 | _write_short(self._file, len(self._markers)) | 
| Sjoerd Mullender | eeabe7e | 1993-01-22 12:53:11 +0000 | [diff] [blame] | 914 | for marker in self._markers: | 
|  | 915 | id, pos, name = marker | 
|  | 916 | _write_short(self._file, id) | 
|  | 917 | _write_long(self._file, pos) | 
|  | 918 | _write_string(self._file, name) | 
|  | 919 |  | 
|  | 920 | def open(filename, mode): | 
|  | 921 | if mode == 'r': | 
|  | 922 | return Aifc_read().init(filename) | 
|  | 923 | elif mode == 'w': | 
|  | 924 | return Aifc_write().init(filename) | 
|  | 925 | else: | 
|  | 926 | raise Error, 'mode must be \'r\' or \'w\'' | 
|  | 927 |  | 
|  | 928 | def openfp(filep, mode): | 
|  | 929 | if mode == 'r': | 
|  | 930 | return Aifc_read().initfp(filep) | 
|  | 931 | elif mode == 'w': | 
|  | 932 | return Aifc_write().initfp(filep) | 
|  | 933 | else: | 
|  | 934 | raise Error, 'mode must be \'r\' or \'w\'' |