def channel_mask(self): """Returns a ChannelMask object of this track's channel layout.""" if (self.channels() <= 2): return ChannelMask.from_channels(self.channels()) else: return ChannelMask(0)
def __init__(self, aiff_file, sample_rate, channels, channel_mask, bits_per_sample, chunk_length, process=None): """aiff_file should be rewound to the start of the SSND chunk.""" alignment = AiffAudio.SSND_ALIGN.parse_stream(aiff_file) PCMReader.__init__(self, file=__capped_stream_reader__( aiff_file, chunk_length - AiffAudio.SSND_ALIGN.sizeof()), sample_rate=sample_rate, channels=channels, channel_mask=channel_mask, bits_per_sample=bits_per_sample, process=process, signed=True, big_endian=True) self.ssnd_chunk_length = chunk_length - 8 standard_channel_mask = ChannelMask(self.channel_mask) aiff_channel_mask = AIFFChannelMask(standard_channel_mask) if (channels in (3, 4, 6)): self.channel_order = [ aiff_channel_mask.channels().index(channel) for channel in standard_channel_mask.channels() ] else: self.channel_order = None
def __init__(self, aiff_file, sample_rate, channels, channel_mask, bits_per_sample, chunk_length, process=None): """aiff_file should be rewound to the start of the SSND chunk.""" alignment = AiffAudio.SSND_ALIGN.parse_stream(aiff_file) PCMReader.__init__(self, file=__capped_stream_reader__( aiff_file, chunk_length - AiffAudio.SSND_ALIGN.sizeof()), sample_rate=sample_rate, channels=channels, channel_mask=channel_mask, bits_per_sample=bits_per_sample, process=process, signed=True, big_endian=True) self.ssnd_chunk_length = chunk_length - 8 standard_channel_mask = ChannelMask(self.channel_mask) aiff_channel_mask = AIFFChannelMask(standard_channel_mask) if (channels in (3, 4, 6)): self.channel_order = [aiff_channel_mask.channels().index(channel) for channel in standard_channel_mask.channels()] else: self.channel_order = None
def channel_mask(self): from audiotools import ChannelMask """returns a ChannelMask object of this track's channel layout""" if (self.channels() <= 2): return ChannelMask.from_channels(self.channels()) else: return ChannelMask(0)
def to_pcm(self): #if mpg123 is available, use that for decoding if (BIN.can_execute(BIN["mpg123"])): sub = subprocess.Popen([BIN["mpg123"],"-qs",self.filename], stdout=subprocess.PIPE, stderr=file(os.devnull,"a")) return PCMReader(sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(ChannelMask.from_channels(self.channels())), process=sub, big_endian=BIG_ENDIAN) else: #if not, use LAME for decoding if (self.filename.endswith("." + self.SUFFIX)): if (BIG_ENDIAN): endian = ['-x'] else: endian = [] sub = subprocess.Popen([BIN['lame']] + endian + \ ["--decode","-t","--quiet", self.filename,"-"], stdout=subprocess.PIPE) return PCMReader(sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(ChannelMask.from_channels(self.channels())), process=sub) else: import tempfile from audiotools import TempWaveReader #copy our file to one that ends with .mp3 tempmp3 = tempfile.NamedTemporaryFile(suffix='.' + self.SUFFIX) f = open(self.filename,'rb') transfer_data(f.read,tempmp3.write) f.close() tempmp3.flush() #decode the mp3 file to a WAVE file wave = tempfile.NamedTemporaryFile(suffix='.wav') returnval = subprocess.call([BIN['lame'],"--decode","--quiet", tempmp3.name,wave.name]) tempmp3.close() if (returnval == 0): #return WAVE file as a stream wave.seek(0,0) return TempWaveReader(wave) else: return PCMReaderError(None, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16)
def channel_mask(self): """returns a ChannelMask object of this track's channel layout""" from audiotools import ChannelMask if self.__channels__ == 1: return ChannelMask(0x4) elif self.__channels__ == 2: return ChannelMask(0x3) else: return ChannelMask(0)
def from_pcm(cls, filename, pcmreader, compression=None): if (compression not in cls.COMPRESSION_MODES): compression = cls.DEFAULT_COMPRESSION devnull = file(os.devnull, 'ab') sub = subprocess.Popen([ BIN['oggenc'], '-Q', '-r', '-B', str(pcmreader.bits_per_sample), '-C', str(pcmreader.channels), '-R', str(pcmreader.sample_rate), '--raw-endianness', str(0), '-q', compression, '-o', filename, '-' ], stdin=subprocess.PIPE, stdout=devnull, stderr=devnull, preexec_fn=ignore_sigint) if ((pcmreader.channels <= 2) or (int(pcmreader.channel_mask) == 0)): transfer_framelist_data(pcmreader, sub.stdin.write) elif (pcmreader.channels <= 8): if (int(pcmreader.channel_mask) in ( 0x7, #FR, FC, FL 0x33, #FR, FL, BR, BL 0x37, #FR, FC, FL, BL, BR 0x3f, #FR, FC, FL, BL, BR, LFE 0x70f, #FL, FC, FR, SL, SR, BC, LFE 0x63f) #FL, FC, FR, SL, SR, BL, BR, LFE ): standard_channel_mask = ChannelMask(pcmreader.channel_mask) vorbis_channel_mask = VorbisChannelMask(standard_channel_mask) else: raise UnsupportedChannelMask() transfer_framelist_data( ReorderedPCMReader(pcmreader, [ standard_channel_mask.channels().index(channel) for channel in vorbis_channel_mask.channels() ]), sub.stdin.write) else: raise UnsupportedChannelMask() try: pcmreader.close() except DecodingError: raise EncodingError() sub.stdin.close() devnull.close() if (sub.wait() == 0): return VorbisAudio(filename) else: raise EncodingError(BIN['oggenc'])
def channel_mask(self): """Returns a ChannelMask object of this track's channel layout.""" if ((self.__channels__ == 1) or (self.__channels__ == 2)): return ChannelMask.from_channels(self.__channels__) else: for (block_id, nondecoder, data) in self.sub_frames(): if ((block_id == 0xD) and not nondecoder): mask = 0 for byte in reversed(map(ord, data[1:])): mask = (mask << 8) | byte return ChannelMask(mask) else: return ChannelMask(0)
def __init__(self, aiff_file, sample_rate, channels, channel_mask, bits_per_sample, total_frames, process=None): """aiff_file should be a file-like object of aiff data sample_rate, channels, channel_mask and bits_per_sample are ints.""" self.file = aiff_file self.sample_rate = sample_rate self.channels = channels self.channel_mask = channel_mask self.bits_per_sample = bits_per_sample self.remaining_frames = total_frames self.bytes_per_frame = self.channels * self.bits_per_sample / 8 self.process = process from .bitstream import BitstreamReader #build a capped reader for the ssnd chunk aiff_reader = BitstreamReader(aiff_file, 0) try: (form, aiff) = aiff_reader.parse("4b 32p 4b") if (form != 'FORM'): raise InvalidAIFF(_(u"Not an AIFF file")) elif (aiff != 'AIFF'): raise InvalidAIFF(_(u"Invalid AIFF file")) while (True): (chunk_id, chunk_size) = aiff_reader.parse("4b 32u") if (chunk_id == 'SSND'): #adjust for the SSND alignment aiff_reader.skip(64) break else: aiff_reader.skip_bytes(chunk_size) if (chunk_size % 2): aiff_reader.skip(8) except IOError: self.read = self.read_error #handle AIFF unusual channel order standard_channel_mask = ChannelMask(self.channel_mask) aiff_channel_mask = AIFFChannelMask(standard_channel_mask) if (channels in (3, 4, 6)): self.channel_order = [aiff_channel_mask.channels().index(channel) for channel in standard_channel_mask.channels()] else: self.channel_order = None
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new MP2Audio object""" from audiotools import (PCMConverter, BufferedPCMReader, ChannelMask, __default_quality__, EncodingError) from audiotools.encoders import encode_mp2 import bisect if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) if pcmreader.sample_rate in (32000, 48000, 44100): sample_rate = pcmreader.sample_rate else: sample_rate = [32000, 32000, 44100, 48000][bisect.bisect([32000, 44100, 48000], pcmreader.sample_rate)] if total_pcm_frames is not None: from audiotools import CounterPCMReader pcmreader = CounterPCMReader(pcmreader) try: encode_mp2(filename, PCMConverter(pcmreader, sample_rate=sample_rate, channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=16), int(compression)) if ((total_pcm_frames is not None) and (total_pcm_frames != pcmreader.frames_written)): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH cls.__unlink__(filename) raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) return MP2Audio(filename) except (ValueError, IOError) as err: cls.__unlink__(filename) raise EncodingError(str(err)) finally: pcmreader.close()
def channel_mask(self): """returns a ChannelMask object of this track's channel layout""" from audiotools import ChannelMask with self.to_pcm() as r: return ChannelMask(r.channel_mask)
def from_pcm(cls, filename, pcmreader, compression="2"): import decimal,bisect if (compression not in cls.COMPRESSION_MODES): compression = cls.DEFAULT_COMPRESSION if ((pcmreader.channels > 2) or (pcmreader.sample_rate not in (32000,48000,44100))): pcmreader = PCMConverter( pcmreader, sample_rate=[32000,32000,44100,48000][bisect.bisect( [32000,44100,48000],pcmreader.sample_rate)], channels=min(pcmreader.channels,2), channel_mask=ChannelMask.from_channels(min(pcmreader.channels,2)), bits_per_sample=16) if (pcmreader.channels > 1): mode = "j" else: mode = "m" #FIXME - not sure if all LAME versions support "--little-endian" # #LAME 3.98 (and up, presumably) handle the byteswap correctly # #LAME 3.97 always uses -x # if (BIG_ENDIAN or (cls.__lame_version__() < (3,98))): # endian = ['-x'] # else: # endian = [] devnull = file(os.devnull,'ab') sub = subprocess.Popen([BIN['lame'],"--quiet", "-r", "-s",str(decimal.Decimal(pcmreader.sample_rate) / 1000), "--bitwidth",str(pcmreader.bits_per_sample), "--signed","--little-endian", "-m",mode, "-V" + str(compression), "-", filename], stdin=subprocess.PIPE, stdout=devnull, stderr=devnull, preexec_fn=ignore_sigint) transfer_framelist_data(pcmreader,sub.stdin.write) try: pcmreader.close() except DecodingError: raise EncodingError() sub.stdin.close() devnull.close() if (sub.wait() == 0): return MP3Audio(filename) else: raise EncodingError(BIN['lame'])
def __init__(self, filename): """filename is a plain string""" from audiotools import ChannelMask AiffContainer.__init__(self, filename) self.__channels__ = 0 self.__bits_per_sample__ = 0 self.__sample_rate__ = 0 self.__channel_mask__ = ChannelMask(0) self.__total_sample_frames__ = 0 from audiotools.bitstream import BitstreamReader try: for chunk in self.chunks(): if chunk.id == b"COMM": try: (self.__channels__, self.__total_sample_frames__, self.__bits_per_sample__, self.__sample_rate__, self.__channel_mask__) = parse_comm( BitstreamReader(chunk.data(), False)) break except IOError: continue except IOError: raise InvalidAIFF("I/O error reading wave")
def channel_mask(self): """returns a ChannelMask object of this track's channel layout""" from audiotools import ChannelMask # M4A seems to use the same channel assignment # as old-style RIFF WAVE/FLAC if self.channels() == 1: return ChannelMask.from_fields(front_center=True) elif self.channels() == 2: return ChannelMask.from_fields(front_left=True, front_right=True) elif self.channels() == 3: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True) elif self.channels() == 4: return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True) elif self.channels() == 5: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, back_left=True, back_right=True) elif self.channels() == 6: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, back_left=True, back_right=True, low_frequency=True) else: return ChannelMask(0)
def channel_mask(self): from audiotools import ChannelMask """returns a ChannelMask object of this track's channel layout""" if self.channels() <= 2: return ChannelMask.from_channels(self.channels()) else: return ChannelMask(0)
def __populate_metadata__(self): #set up some default values self.__bits_per_sample__ = 16 self.__channels__ = 2 self.__channel_mask__ = 0x3 self.__sample_rate__ = 44100 self.__total_frames__ = 0 self.__blocks__ = [] self.__format__ = None #grab a few pieces of technical metadata from the Shorten file itself #which requires a dry-run through the decoder try: decoder = audiotools.decoders.SHNDecoder(self.filename) try: self.__bits_per_sample__ = decoder.bits_per_sample self.__channels__ = decoder.channels (self.__total_frames__, self.__blocks__) = decoder.metadata() finally: decoder.close() try: self.__channel_mask__ = ChannelMask.from_channels( self.__channels__) except ValueError: self.__channel_mask__ = 0 except (ValueError, IOError): #if we hit an error in SHNDecoder while reading #technical metadata, the default values will have to do return #the remainder requires parsing the file's VERBATIM blocks #which may contain Wave, AIFF or Sun AU info if (self.__blocks__[0] is not None): header = cStringIO.StringIO(self.__blocks__[0]) for format in WaveAudio, AiffAudio: header.seek(0, 0) if (format.is_type(header)): self.__format__ = format break if (self.__format__ is WaveAudio): for (chunk_id, chunk_data) in self.__wave_chunks__(): if (chunk_id == 'fmt '): fmt_chunk = WaveAudio.FMT_CHUNK.parse(chunk_data) self.__sample_rate__ = fmt_chunk.sample_rate if (fmt_chunk.compression == 0xFFFE): self.__channel_mask__ = \ WaveAudio.fmt_chunk_to_channel_mask( fmt_chunk.channel_mask) elif (self.__format__ is AiffAudio): for (chunk_id, chunk_data) in self.__aiff_chunks__(): if (chunk_id == 'COMM'): comm_chunk = AiffAudio.COMM_CHUNK.parse(chunk_data) self.__sample_rate__ = comm_chunk.sample_rate
def channel_mask(self): """Returns a ChannelMask object of this track's channel layout.""" if (self.channels() == 1): return ChannelMask.from_fields(front_center=True) elif (self.channels() == 2): return ChannelMask.from_fields(front_left=True, front_right=True) elif (self.channels() == 3): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True) elif (self.channels() == 4): return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True) elif (self.channels() == 5): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, back_left=True, back_right=True) elif (self.channels() == 6): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, back_left=True, back_right=True, low_frequency=True) elif (self.channels() == 7): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, side_left=True, side_right=True, back_center=True, low_frequency=True) elif (self.channels() == 8): return ChannelMask.from_fields(front_left=True, front_right=True, side_left=True, side_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True) else: return ChannelMask(0)
def channel_mask(self): """returns a ChannelMask object of this track's channel layout""" if self.channels() == 1: return ChannelMask.from_fields(front_center=True) elif self.channels() == 2: return ChannelMask.from_fields(front_left=True, front_right=True) elif self.channels() == 3: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True) elif self.channels() == 4: return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True) elif self.channels() == 5: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, back_left=True, back_right=True) elif self.channels() == 6: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, back_left=True, back_right=True, low_frequency=True) elif self.channels() == 7: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, side_left=True, side_right=True, back_center=True, low_frequency=True) elif self.channels() == 8: return ChannelMask.from_fields(front_left=True, front_right=True, side_left=True, side_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True) else: return ChannelMask(0)
def from_pcm(cls, filename, pcmreader, compression=None): """Encodes a new file from PCM data. Takes a filename string, PCMReader object and optional compression level string. Encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new SpeexAudio object.""" import bisect if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if ((pcmreader.bits_per_sample not in (8, 16)) or (pcmreader.channels > 2) or (pcmreader.sample_rate not in (8000, 16000, 32000, 44100))): pcmreader = PCMConverter( pcmreader, sample_rate=[8000, 8000, 16000, 32000, 44100][bisect.bisect([8000, 16000, 32000, 44100], pcmreader.sample_rate)], channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=min(pcmreader.bits_per_sample, 16)) BITS_PER_SAMPLE = { 8: ['--8bit'], 16: ['--16bit'] }[pcmreader.bits_per_sample] CHANNELS = {1: [], 2: ['--stereo']}[pcmreader.channels] devnull = file(os.devnull, "ab") sub = subprocess.Popen([BIN['speexenc'], '--quality', str(compression), '--rate', str(pcmreader.sample_rate), '--le'] + \ BITS_PER_SAMPLE + \ CHANNELS + \ ['-', filename], stdin=subprocess.PIPE, stderr=devnull, preexec_fn=ignore_sigint) try: transfer_framelist_data(pcmreader, sub.stdin.write) except (IOError, ValueError), err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err))
def parse_comm(comm): """given a COMM chunk (without the 8 byte name/size header) returns (channels, total_sample_frames, bits_per_sample, sample_rate, channel_mask) where channel_mask is a ChannelMask object and the rest are ints may raise IOError if an error occurs reading the chunk""" from audiotools import ChannelMask (channels, total_sample_frames, bits_per_sample) = comm.parse("16u 32u 16u") sample_rate = int(parse_ieee_extended(comm)) if channels <= 2: channel_mask = ChannelMask.from_channels(channels) else: channel_mask = ChannelMask(0) return (channels, total_sample_frames, bits_per_sample, sample_rate, channel_mask)
def channel_mask(self): """Returns a ChannelMask object of this track's channel layout.""" #this unusual arrangement is taken from the AIFF specification if (self.channels() <= 2): return ChannelMask.from_channels(self.channels()) elif (self.channels() == 3): return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True) elif (self.channels() == 4): return ChannelMask.from_fields( front_left=True, front_right=True, back_left=True, back_right=True) elif (self.channels() == 6): return ChannelMask.from_fields( front_left=True, side_left=True, front_center=True, front_right=True, side_right=True, back_center=True) else: return ChannelMask(0)
def channel_mask(self): if (self.channels() == 1): return ChannelMask.from_fields(front_center=True) elif (self.channels() == 2): return ChannelMask.from_fields(front_left=True, front_right=True) elif (self.channels() == 3): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True) elif (self.channels() == 4): return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True) elif (self.channels() == 5): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, back_left=True, back_right=True) elif (self.channels() == 6): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, back_left=True, back_right=True, low_frequency=True) elif (self.channels() == 7): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True, side_left=True, side_right=True, back_center=True, low_frequency=True) elif (self.channels() == 8): return ChannelMask.from_fields(front_left=True, front_right=True, side_left=True, side_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True) else: return ChannelMask(0)
def to_pcm(self): devnull = file(os.devnull,'ab') sub = subprocess.Popen([BIN['speexdec'],self.filename,'-'], stdout=subprocess.PIPE, stderr=devnull) return PCMReader( sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), channel_mask=int(ChannelMask.from_channels(self.channels())), bits_per_sample=self.bits_per_sample(), process=sub)
def channel_mask(self): """Returns a ChannelMask object of this track's channel layout.""" # M4A seems to use the same channel assignment # as old-style RIFF WAVE/FLAC if self.channels() == 1: return ChannelMask.from_fields(front_center=True) elif self.channels() == 2: return ChannelMask.from_fields(front_left=True, front_right=True) elif self.channels() == 3: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True) elif self.channels() == 4: return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True) elif self.channels() == 5: return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True, back_left=True, back_right=True ) elif self.channels() == 6: return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True, back_left=True, back_right=True, low_frequency=True, ) else: return ChannelMask(0)
def from_pcm(cls, filename, pcmreader, compression=None): """Encodes a new file from PCM data. Takes a filename string, PCMReader object and optional compression level string. Encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new SpeexAudio object.""" import bisect if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if ((pcmreader.bits_per_sample not in (8, 16)) or (pcmreader.channels > 2) or (pcmreader.sample_rate not in (8000, 16000, 32000, 44100))): pcmreader = PCMConverter( pcmreader, sample_rate=[8000, 8000, 16000, 32000, 44100][bisect.bisect( [8000, 16000, 32000, 44100], pcmreader.sample_rate)], channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=min(pcmreader.bits_per_sample, 16)) BITS_PER_SAMPLE = {8: ['--8bit'], 16: ['--16bit']}[pcmreader.bits_per_sample] CHANNELS = {1: [], 2: ['--stereo']}[pcmreader.channels] devnull = file(os.devnull, "ab") sub = subprocess.Popen([BIN['speexenc'], '--quality', str(compression), '--rate', str(pcmreader.sample_rate), '--le'] + \ BITS_PER_SAMPLE + \ CHANNELS + \ ['-', filename], stdin=subprocess.PIPE, stderr=devnull, preexec_fn=ignore_sigint) try: transfer_framelist_data(pcmreader, sub.stdin.write) except (IOError, ValueError), err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err))
def channel_mask(self): #this unusual arrangement is taken from the AIFF specification if (self.channels() <= 2): return ChannelMask.from_channels(self.channels()) elif (self.channels() == 3): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True) elif (self.channels() == 4): return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True) elif (self.channels() == 6): return ChannelMask.from_fields(front_left=True, side_left=True, front_center=True, front_right=True, side_right=True, back_center=True) else: return ChannelMask(0)
def __populate_metadata__(self): # Alteration for BootTunes: # Since the vast majority of files will match the defaults, and because this method crashes # Windows 7 and locks up Mac, just go with the defaults. self.__bits_per_sample__ = 16 self.__channels__ = 2 self.__channel_mask__ = 0x3 self.__sample_rate__ = 44100 self.__total_frames__ = 0 self.__blocks__ = [] self.__format__ = None return #grab a few pieces of technical metadata from the Shorten file itself #which requires a dry-run through the decoder decoder = audiotools.decoders.SHNDecoder(self.filename) self.__bits_per_sample__ = decoder.bits_per_sample self.__channels__ = decoder.channels (self.__total_frames__, self.__blocks__) = decoder.metadata() decoder.close() #set up some default values self.__sample_rate__ = 44100 try: self.__channel_mask__ = ChannelMask.from_channels(self.__channels__) except ValueError: self.__channel_mask__ = 0 self.__format__ = None #the remainder requires parsing the file's VERBATIM blocks #which may contain Wave, AIFF or Sun AU info if (self.__blocks__[0] is not None): header = cStringIO.StringIO(self.__blocks__[0]) for format in WaveAudio,AiffAudio: header.seek(0,0) if (format.is_type(header)): self.__format__ = format break if (self.__format__ is WaveAudio): for (chunk_id,chunk_data) in self.__wave_chunks__(): if (chunk_id == 'fmt '): fmt_chunk = WaveAudio.FMT_CHUNK.parse(chunk_data) self.__sample_rate__ = fmt_chunk.sample_rate if (fmt_chunk.compression == 0xFFFE): self.__channel_mask__ = WaveAudio.fmt_chunk_to_channel_mask(fmt_chunk.channel_mask) elif (self.__format__ is AiffAudio): for (chunk_id,chunk_data) in self.__aiff_chunks__(): if (chunk_id == 'COMM'): comm_chunk = AiffAudio.COMM_CHUNK.parse(chunk_data) self.__sample_rate__ = comm_chunk.sample_rate
def to_pcm(self): """Returns a PCMReader object containing the track's PCM data.""" devnull = file(os.devnull, 'ab') sub = subprocess.Popen([BIN['speexdec'], self.filename, '-'], stdout=subprocess.PIPE, stderr=devnull) return PCMReader( sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), channel_mask=int(ChannelMask.from_channels(self.channels())), bits_per_sample=self.bits_per_sample(), process=sub)
def channel_mask(self): """returns a ChannelMask object of this track's channel layout""" if self.channels() == 1: return ChannelMask.from_fields(front_center=True) elif self.channels() == 2: return ChannelMask.from_fields(front_left=True, front_right=True) elif self.channels() == 3: return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True) elif self.channels() == 4: return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True) elif self.channels() == 5: return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True, back_left=True, back_right=True ) elif self.channels() == 6: return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True, back_left=True, back_right=True, low_frequency=True, ) elif self.channels() == 7: return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True, side_left=True, side_right=True, back_center=True, low_frequency=True, ) elif self.channels() == 8: return ChannelMask.from_fields( front_left=True, front_right=True, side_left=True, side_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True, ) else: return ChannelMask(0)
def from_pcm(cls, filename, pcmreader, compression=None): import bisect if (compression not in cls.COMPRESSION_MODES): compression = cls.DEFAULT_COMPRESSION if ((pcmreader.bits_per_sample not in (8,16)) or (pcmreader.channels > 2) or (pcmreader.sample_rate not in (8000,16000,32000,44100))): pcmreader = PCMConverter( pcmreader, sample_rate=[8000,8000,16000,32000,44100][bisect.bisect( [8000,16000,32000,44100],pcmreader.sample_rate)], channels=min(pcmreader.channels,2), channel_mask=ChannelMask.from_channels(min(pcmreader.channels,2)), bits_per_sample=min(pcmreader.bits_per_sample,16)) BITS_PER_SAMPLE = {8:['--8bit'], 16:['--16bit']}[pcmreader.bits_per_sample] CHANNELS = {1:[],2:['--stereo']}[pcmreader.channels] devnull = file(os.devnull,"ab") sub = subprocess.Popen([BIN['speexenc'], '--quality',str(compression), '--rate',str(pcmreader.sample_rate), '--le'] + \ BITS_PER_SAMPLE + \ CHANNELS + \ ['-',filename], stdin=subprocess.PIPE, stderr=devnull, preexec_fn=ignore_sigint) transfer_framelist_data(pcmreader,sub.stdin.write) try: pcmreader.close() except DecodingError: raise EncodingError() sub.stdin.close() result = sub.wait() devnull.close() if (result == 0): return SpeexAudio(filename) else: raise EncodingError(BIN['speexenc'])
def to_pcm(self): """returns a PCMReader object containing the track's PCM data""" BIG_ENDIAN = sys.byteorder == 'big' sub = subprocess.Popen([BIN["mpg123"], "-qs", self.filename], stdout=subprocess.PIPE, stderr=file(os.devnull, "a")) return PCMReader(sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(ChannelMask.from_channels( self.channels())), process=sub, big_endian=BIG_ENDIAN)
def channel_mask(self): """Returns a ChannelMask object of this track's channel layout.""" if (self.channels() == 1): return ChannelMask.from_fields( front_center=True) elif (self.channels() == 2): return ChannelMask.from_fields( front_left=True, front_right=True) elif (self.channels() == 3): return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True) elif (self.channels() == 4): return ChannelMask.from_fields( front_left=True, front_right=True, back_left=True, back_right=True) elif (self.channels() == 5): return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True, back_left=True, back_right=True) elif (self.channels() == 6): return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True, back_left=True, back_right=True, low_frequency=True) elif (self.channels() == 7): return ChannelMask.from_fields( front_left=True, front_right=True, front_center=True, side_left=True, side_right=True, back_center=True, low_frequency=True) elif (self.channels() == 8): return ChannelMask.from_fields( front_left=True, front_right=True, side_left=True, side_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True) else: return ChannelMask(0)
def parse_comm(comm): """given a COMM chunk (without the 8 byte name/size header) returns (channels, total_sample_frames, bits_per_sample, sample_rate, channel_mask) where channel_mask is a ChannelMask object and the rest are ints may raise IOError if an error occurs reading the chunk""" (channels, total_sample_frames, bits_per_sample) = comm.parse("16u 32u 16u") sample_rate = int(parse_ieee_extended(comm)) if (channels <= 2): channel_mask = ChannelMask.from_channels(channels) else: channel_mask = ChannelMask(0) return (channels, total_sample_frames, bits_per_sample, sample_rate, channel_mask)
def channel_mask(self): """Returns a ChannelMask object of this track's channel layout.""" return { 1: ChannelMask.from_fields(front_center=True), 2: ChannelMask.from_fields(front_left=True, front_right=True), 3: ChannelMask.from_fields(front_center=True, front_left=True, front_right=True), 4: ChannelMask.from_fields(front_center=True, front_left=True, front_right=True, back_center=True), 5: ChannelMask.from_fields( front_center=True, front_left=True, front_right=True, back_left=True, back_right=True ), 6: ChannelMask.from_fields( front_center=True, front_left=True, front_right=True, back_left=True, back_right=True, low_frequency=True, ), 7: ChannelMask.from_fields( front_center=True, front_left=True, front_right=True, back_left=True, back_right=True, back_center=True, low_frequency=True, ), 8: ChannelMask.from_fields( front_center=True, front_left_of_center=True, front_right_of_center=True, front_left=True, front_right=True, back_left=True, back_right=True, low_frequency=True, ), }.get(self.channels(), ChannelMask(0))
def __init__(self, filename): """filename is a plain string""" from audiotools import ChannelMask WaveContainer.__init__(self, filename) self.__channels__ = 0 self.__sample_rate__ = 0 self.__bits_per_sample__ = 0 self.__data_size__ = 0 self.__channel_mask__ = ChannelMask(0) from audiotools.bitstream import BitstreamReader fmt_read = data_read = False try: for chunk in self.chunks(): if chunk.id == b"fmt ": try: (self.__channels__, self.__sample_rate__, self.__bits_per_sample__, self.__channel_mask__) = parse_fmt( BitstreamReader(chunk.data(), True)) fmt_read = True if fmt_read and data_read: break except IOError: continue except ValueError as err: raise InvalidWave(str(err)) elif chunk.id == b"data": self.__data_size__ = chunk.size() data_read = True if fmt_read and data_read: break except IOError: raise InvalidWave("I/O error reading wave")
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new MP3Audio object""" from audiotools import (PCMConverter, BufferedPCMReader, ChannelMask, __default_quality__, EncodingError) from audiotools.encoders import encode_mp3 if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) try: encode_mp3( filename, BufferedPCMReader( PCMConverter(pcmreader, sample_rate=pcmreader.sample_rate, channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=16)), compression) return MP3Audio(filename) except (ValueError, IOError), err: cls.__unlink__(filename) raise EncodingError(str(err))
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new MP3Audio object""" from audiotools import (PCMConverter, BufferedPCMReader, ChannelMask, __default_quality__, EncodingError) from audiotools.encoders import encode_mp3 if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) try: encode_mp3(filename, BufferedPCMReader( PCMConverter(pcmreader, sample_rate=pcmreader.sample_rate, channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=16)), compression) return MP3Audio(filename) except (ValueError, IOError), err: cls.__unlink__(filename) raise EncodingError(str(err))
def channel_mask(self): fmt_chunk = WaveAudio.FMT_CHUNK.parse(self.__fmt_chunk__()) if (fmt_chunk.compression != 0xFFFE): if (self.__channels__ == 1): return ChannelMask.from_fields(front_center=True) elif (self.__channels__ == 2): return ChannelMask.from_fields(front_left=True, front_right=True) #if we have a multi-channel WavPack file #that's not WAVEFORMATEXTENSIBLE, #assume the channels follow SMPTE/ITU-R recommendations #and hope for the best elif (self.__channels__ == 3): return ChannelMask.from_fields(front_left=True, front_right=True, front_center=True) elif (self.__channels__ == 4): return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True) elif (self.__channels__ == 5): return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True, front_center=True) elif (self.__channels__ == 6): return ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True) else: return ChannelMask(0) else: return WaveAudio.fmt_chunk_to_channel_mask(fmt_chunk.channel_mask)
def from_pcm(cls, filename, pcmreader, compression=None): """Encodes a new file from PCM data. Takes a filename string, PCMReader object and optional compression level string. Encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new M4AAudio object.""" if (compression is None) or (compression not in cls.COMPRESSION_MODES): compression = __default_quality__(cls.NAME) if pcmreader.channels > 2: pcmreader = PCMConverter( pcmreader, sample_rate=pcmreader.sample_rate, channels=2, channel_mask=ChannelMask.from_channels(2), bits_per_sample=pcmreader.bits_per_sample, ) # faac requires files to end with .m4a for some reason if not filename.endswith(".m4a"): import tempfile actual_filename = filename tempfile = tempfile.NamedTemporaryFile(suffix=".m4a") filename = tempfile.name else: actual_filename = tempfile = None devnull = file(os.devnull, "ab") sub = subprocess.Popen( [ BIN["faac"], "-q", compression, "-P", "-R", str(pcmreader.sample_rate), "-B", str(pcmreader.bits_per_sample), "-C", str(pcmreader.channels), "-X", "-o", filename, "-", ], stdin=subprocess.PIPE, stderr=devnull, stdout=devnull, preexec_fn=ignore_sigint, ) # Note: faac handles SIGINT on its own, # so trying to ignore it doesn't work like on most other encoders. try: transfer_framelist_data(pcmreader, sub.stdin.write) except (ValueError, IOError), err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err))
def from_pcm(cls, filename, pcmreader, compression=None): """Encodes a new file from PCM data. Takes a filename string, PCMReader object and optional compression level string. Encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new MP3Audio object.""" import decimal import bisect if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if ((pcmreader.channels > 2) or (pcmreader.sample_rate not in (32000, 48000, 44100))): pcmreader = PCMConverter( pcmreader, sample_rate=[32000, 32000, 44100, 48000][bisect.bisect( [32000, 44100, 48000], pcmreader.sample_rate)], channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=16) if (pcmreader.channels > 1): mode = "j" else: mode = "m" #FIXME - not sure if all LAME versions support "--little-endian" # #LAME 3.98 (and up, presumably) handle the byteswap correctly # #LAME 3.97 always uses -x # if (BIG_ENDIAN or (cls.__lame_version__() < (3,98))): # endian = ['-x'] # else: # endian = [] devnull = file(os.devnull, 'ab') if (str(compression) in map(str, range(0, 10))): compression = ["-V" + str(compression)] else: compression = ["--preset", str(compression)] sub = subprocess.Popen([ BIN['lame'], "--quiet", "-r", "-s", str(decimal.Decimal(pcmreader.sample_rate) / 1000), "--bitwidth", str(pcmreader.bits_per_sample), "--signed", "--little-endian", "-m", mode] + compression + ["-", filename], stdin=subprocess.PIPE, stdout=devnull, stderr=devnull, preexec_fn=ignore_sigint) try: transfer_framelist_data(pcmreader, sub.stdin.write) except (IOError, ValueError), err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err))
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new M4AAudio object""" import subprocess import os from audiotools import PCMConverter from audiotools import transfer_data from audiotools import transfer_framelist_data from audiotools import ignore_sigint from audiotools import EncodingError from audiotools import DecodingError from audiotools import ChannelMask from audiotools import __default_quality__ if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if pcmreader.channels > 2: pcmreader = PCMConverter(pcmreader, sample_rate=pcmreader.sample_rate, channels=2, channel_mask=ChannelMask.from_channels(2), bits_per_sample=pcmreader.bits_per_sample) # faac requires files to end with .m4a for some reason if not filename.endswith(".m4a"): import tempfile actual_filename = filename tempfile = tempfile.NamedTemporaryFile(suffix=".m4a") filename = tempfile.name else: actual_filename = tempfile = None sub = subprocess.Popen( [ BIN['faac'], "-q", compression, "-P", "-R", str(pcmreader.sample_rate), "-B", str(pcmreader.bits_per_sample), "-C", str(pcmreader.channels), "-X", "-o", filename, "-" ], stdin=subprocess.PIPE, stderr=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb"), stdout=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb"), preexec_fn=ignore_sigint) # Note: faac handles SIGINT on its own, # so trying to ignore it doesn't work like on most other encoders. try: if total_pcm_frames is not None: from audiotools import CounterPCMReader pcmreader = CounterPCMReader(pcmreader) transfer_framelist_data(pcmreader, sub.stdin.write) if ((total_pcm_frames is not None) and (total_pcm_frames != pcmreader.frames_written)): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) except (ValueError, IOError) as err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err)) except Exception: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise sub.stdin.close() if sub.wait() == 0: if tempfile is not None: filename = actual_filename f = open(filename, 'wb') tempfile.seek(0, 0) transfer_data(tempfile.read, f.write) f.close() tempfile.close() return M4AAudio(filename) else: if tempfile is not None: tempfile.close() raise EncodingError(u"unable to write file with faac")
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object optional compression level string, and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new AudioFile-compatible object specifying total_pcm_frames, when the number is known in advance, may allow the encoder to work more efficiently but is never required """ import bisect import os import subprocess from audiotools import __default_quality__ from audiotools import transfer_framelist_data from audiotools import EncodingError from audiotools import PCMConverter from audiotools import ChannelMask if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if pcmreader.bits_per_sample not in (8, 16, 24): from audiotools import UnsupportedBitsPerSample raise UnsupportedBitsPerSample(filename, pcmreader.bits_per_sample) if total_pcm_frames is not None: from audiotools import CounterPCMReader counter_reader = CounterPCMReader(pcmreader) else: counter_reader = pcmreader pcmreader = PCMConverter( counter_reader, sample_rate=[8000, 8000, 16000, 32000][bisect.bisect([8000, 16000, 32000], pcmreader.sample_rate)], channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels(min(pcmreader.channels, 2)), bits_per_sample=min(pcmreader.bits_per_sample, 16)) BITS_PER_SAMPLE = { 8: ['--8bit'], 16: ['--16bit'] }[pcmreader.bits_per_sample] CHANNELS = {1: [], 2: ['--stereo']}[pcmreader.channels] sub = subprocess.Popen( [BIN['speexenc'], '--quality', str(compression), '--rate', str(pcmreader.sample_rate), '--le'] + \ BITS_PER_SAMPLE + \ CHANNELS + \ ['-', filename], stdin=subprocess.PIPE, stderr=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb")) try: transfer_framelist_data(pcmreader, sub.stdin.write) except (IOError, ValueError) as err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err)) except Exception as err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise err sub.stdin.close() if sub.wait() == 0: if ((total_pcm_frames is None) or (total_pcm_frames == counter_reader.frames_written)): return SpeexAudio(filename) else: from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH cls.__unlink__(filename) raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) else: raise EncodingError(u"unable to encode file with speexenc")
def __read_info__(self): from audiotools.bitstream import BitstreamReader from audiotools import ChannelMask reader = BitstreamReader(open(self.filename, "rb"), 1) reader.mark() try: (block_id, total_samples, bits_per_sample, mono_output, initial_block, final_block, sample_rate) = reader.parse( "4b 64p 32u 64p 2u 1u 8p 1u 1u 5p 5p 4u 37p") if (block_id != 'wvpk'): from audiotools.text import ERR_WAVPACK_INVALID_HEADER raise InvalidWavPack(ERR_WAVPACK_INVALID_HEADER) if (sample_rate != 0xF): self.__samplerate__ = WavPackAudio.SAMPLING_RATE[sample_rate] else: # if unknown, pull from SAMPLE_RATE sub-block for (block_id, nondecoder, data_size, data) in self.sub_blocks(reader): if ((block_id == 0x7) and nondecoder): self.__samplerate__ = data.read(data_size * 8) break else: # no SAMPLE RATE sub-block found # so pull info from FMT chunk reader.rewind() (self.__samplerate__,) = self.fmt_chunk(reader).parse( "32p 32u") self.__bitspersample__ = [8, 16, 24, 32][bits_per_sample] self.__total_frames__ = total_samples if (initial_block and final_block): if (mono_output): self.__channels__ = 1 self.__channel_mask__ = ChannelMask(0x4) else: self.__channels__ = 2 self.__channel_mask__ = ChannelMask(0x3) else: # if not mono or stereo, pull from CHANNEL INFO sub-block reader.rewind() for (block_id, nondecoder, data_size, data) in self.sub_blocks(reader): if ((block_id == 0xD) and not nondecoder): self.__channels__ = data.read(8) self.__channel_mask__ = ChannelMask( data.read((data_size - 1) * 8)) break else: # no CHANNEL INFO sub-block found # so pull info from FMT chunk reader.rewind() fmt = self.fmt_chunk(reader) compression_code = fmt.read(16) self.__channels__ = fmt.read(16) if (compression_code == 1): # this is theoretically possible # with very old .wav files, # but shouldn't happen in practice self.__channel_mask__ = \ {1: ChannelMask.from_fields(front_center=True), 2: ChannelMask.from_fields(front_left=True, front_right=True), 3: ChannelMask.from_fields(front_left=True, front_right=True, front_center=True), 4: ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True), 5: ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True, front_center=True), 6: ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True) }.get(self.__channels__, ChannelMask(0)) elif (compression_code == 0xFFFE): fmt.skip(128) mask = fmt.read(32) self.__channel_mask__ = ChannelMask(mask) else: from audiotools.text import ERR_WAVPACK_UNSUPPORTED_FMT raise InvalidWavPack(ERR_WAVPACK_UNSUPPORTED_FMT) finally: reader.unmark() reader.close()
def __read_info__(self): from audiotools.bitstream import BitstreamReader from audiotools import ChannelMask reader = BitstreamReader(open(self.filename, "rb"), 1) reader.mark() try: (block_id, total_samples, bits_per_sample, mono_output, initial_block, final_block, sample_rate) = reader.parse( "4b 64p 32u 64p 2u 1u 8p 1u 1u 5p 5p 4u 37p") if (block_id != b"wvpk"): from audiotools.text import ERR_WAVPACK_INVALID_HEADER raise InvalidWavPack(ERR_WAVPACK_INVALID_HEADER) if (sample_rate != 0xF): self.__samplerate__ = WavPackAudio.SAMPLING_RATE[sample_rate] else: # if unknown, pull from SAMPLE_RATE sub-block for (block_id, nondecoder, data_size, data) in self.sub_blocks(reader): if ((block_id == 0x7) and nondecoder): self.__samplerate__ = data.read(data_size * 8) break else: # no SAMPLE RATE sub-block found # so pull info from FMT chunk reader.rewind() (self.__samplerate__,) = self.fmt_chunk(reader).parse( "32p 32u") self.__bitspersample__ = [8, 16, 24, 32][bits_per_sample] self.__total_frames__ = total_samples if (initial_block and final_block): if (mono_output): self.__channels__ = 1 self.__channel_mask__ = ChannelMask(0x4) else: self.__channels__ = 2 self.__channel_mask__ = ChannelMask(0x3) else: # if not mono or stereo, pull from CHANNEL INFO sub-block reader.rewind() for (block_id, nondecoder, data_size, data) in self.sub_blocks(reader): if ((block_id == 0xD) and not nondecoder): self.__channels__ = data.read(8) self.__channel_mask__ = ChannelMask( data.read((data_size - 1) * 8)) break else: # no CHANNEL INFO sub-block found # so pull info from FMT chunk reader.rewind() fmt = self.fmt_chunk(reader) compression_code = fmt.read(16) self.__channels__ = fmt.read(16) if (compression_code == 1): # this is theoretically possible # with very old .wav files, # but shouldn't happen in practice self.__channel_mask__ = \ {1: ChannelMask.from_fields(front_center=True), 2: ChannelMask.from_fields(front_left=True, front_right=True), 3: ChannelMask.from_fields(front_left=True, front_right=True, front_center=True), 4: ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True), 5: ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True, front_center=True), 6: ChannelMask.from_fields(front_left=True, front_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True) }.get(self.__channels__, ChannelMask(0)) elif (compression_code == 0xFFFE): fmt.skip(128) mask = fmt.read(32) self.__channel_mask__ = ChannelMask(mask) else: from audiotools.text import ERR_WAVPACK_UNSUPPORTED_FMT raise InvalidWavPack(ERR_WAVPACK_UNSUPPORTED_FMT) finally: reader.unmark() reader.close()
def parse_fmt(fmt): """given a fmt block BitstreamReader (without the 8 byte header) returns (channels, sample_rate, bits_per_sample, channel_mask) where channel_mask is a ChannelMask object and the rest are ints may raise ValueError if the fmt chunk is invalid or IOError if an error occurs parsing the chunk""" from audiotools import ChannelMask (compression, channels, sample_rate, bytes_per_second, block_align, bits_per_sample) = fmt.parse("16u 16u 32u 32u 16u 16u") if compression == 1: # if we have a multi-channel WAVE file # that's not WAVEFORMATEXTENSIBLE, # assume the channels follow # SMPTE/ITU-R recommendations # and hope for the best if channels == 1: channel_mask = ChannelMask.from_fields( front_center=True) elif channels == 2: channel_mask = ChannelMask.from_fields( front_left=True, front_right=True) elif channels == 3: channel_mask = ChannelMask.from_fields( front_left=True, front_right=True, front_center=True) elif channels == 4: channel_mask = ChannelMask.from_fields( front_left=True, front_right=True, back_left=True, back_right=True) elif channels == 5: channel_mask = ChannelMask.from_fields( front_left=True, front_right=True, back_left=True, back_right=True, front_center=True) elif channels == 6: channel_mask = ChannelMask.from_fields( front_left=True, front_right=True, back_left=True, back_right=True, front_center=True, low_frequency=True) else: channel_mask = ChannelMask(0) return (channels, sample_rate, bits_per_sample, channel_mask) elif compression == 0xFFFE: (cb_size, valid_bits_per_sample, channel_mask, sub_format) = fmt.parse("16u 16u 32u 16b") if (sub_format != (b'\x01\x00\x00\x00\x00\x00\x10\x00' + b'\x80\x00\x00\xaa\x00\x38\x9b\x71')): # FIXME raise ValueError("invalid WAVE sub-format") else: channel_mask = ChannelMask(channel_mask) return (channels, sample_rate, bits_per_sample, channel_mask) else: # FIXME raise ValueError("unsupported WAVE compression")
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object optional compression level string, and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new AudioFile-compatible object specifying total_pcm_frames, when the number is known in advance, may allow the encoder to work more efficiently but is never required """ import bisect import os import subprocess from audiotools import __default_quality__ from audiotools import transfer_framelist_data from audiotools import EncodingError from audiotools import PCMConverter from audiotools import ChannelMask if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if pcmreader.bits_per_sample not in (8, 16, 24): from audiotools import UnsupportedBitsPerSample raise UnsupportedBitsPerSample( filename, pcmreader.bits_per_sample) if total_pcm_frames is not None: from audiotools import CounterPCMReader counter_reader = CounterPCMReader(pcmreader) else: counter_reader = pcmreader pcmreader = PCMConverter( counter_reader, sample_rate=[8000, 8000, 16000, 32000][bisect.bisect( [8000, 16000, 32000], pcmreader.sample_rate)], channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=min(pcmreader.bits_per_sample, 16)) BITS_PER_SAMPLE = {8: ['--8bit'], 16: ['--16bit']}[pcmreader.bits_per_sample] CHANNELS = {1: [], 2: ['--stereo']}[pcmreader.channels] sub = subprocess.Popen( [BIN['speexenc'], '--quality', str(compression), '--rate', str(pcmreader.sample_rate), '--le'] + \ BITS_PER_SAMPLE + \ CHANNELS + \ ['-', filename], stdin=subprocess.PIPE, stderr=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb")) try: transfer_framelist_data(pcmreader, sub.stdin.write) except (IOError, ValueError) as err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err)) except Exception as err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise err sub.stdin.close() if sub.wait() == 0: if ((total_pcm_frames is None) or (total_pcm_frames == counter_reader.frames_written)): return SpeexAudio(filename) else: from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH cls.__unlink__(filename) raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) else: raise EncodingError(u"unable to encode file with speexenc")
except Exception, err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise err elif (pcmreader.channels <= 8): if (int(pcmreader.channel_mask) in (0x7, # FR, FC, FL 0x33, # FR, FL, BR, BL 0x37, # FR, FC, FL, BL, BR 0x3f, # FR, FC, FL, BL, BR, LFE 0x70f, # FL, FC, FR, SL, SR, BC, LFE 0x63f)): # FL, FC, FR, SL, SR, BL, BR, LFE standard_channel_mask = ChannelMask(pcmreader.channel_mask) vorbis_channel_mask = VorbisChannelMask(standard_channel_mask) else: raise UnsupportedChannelMask(filename, int(pcmreader.channel_mask)) try: from . import ReorderedPCMReader transfer_framelist_data( ReorderedPCMReader( pcmreader, [standard_channel_mask.channels().index(channel) for channel in vorbis_channel_mask.channels()]), sub.stdin.write) except (IOError, ValueError), err:
def to_pcm(self): """Returns a PCMReader object containing the track's PCM data.""" #if mpg123 is available, use that for decoding if (BIN.can_execute(BIN["mpg123"])): sub = subprocess.Popen([BIN["mpg123"], "-qs", self.filename], stdout=subprocess.PIPE, stderr=file(os.devnull, "a")) return PCMReader(sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(ChannelMask.from_channels( self.channels())), process=sub, big_endian=BIG_ENDIAN) else: #if not, use LAME for decoding if (self.filename.endswith("." + self.SUFFIX)): if (BIG_ENDIAN): endian = ['-x'] else: endian = [] sub = subprocess.Popen([BIN['lame']] + endian + \ ["--decode", "-t", "--quiet", self.filename, "-"], stdout=subprocess.PIPE) return PCMReader( sub.stdout, sample_rate=self.sample_rate(), channels=self.channels(), bits_per_sample=16, channel_mask=int(self.channel_mask()), process=sub) else: import tempfile from audiotools import TempWaveReader #copy our file to one that ends with .mp3 tempmp3 = tempfile.NamedTemporaryFile(suffix='.' + self.SUFFIX) f = open(self.filename, 'rb') transfer_data(f.read, tempmp3.write) f.close() tempmp3.flush() #decode the mp3 file to a WAVE file wave = tempfile.NamedTemporaryFile(suffix='.wav') returnval = subprocess.call([BIN['lame'], "--decode", "--quiet", tempmp3.name, wave.name]) tempmp3.close() if (returnval == 0): #return WAVE file as a stream wave.seek(0, 0) return TempWaveReader(wave) else: return PCMReaderError( error_message=u"lame exited with error", sample_rate=self.sample_rate(), channels=self.channels(), channel_mask=int(self.channel_mask()), bits_per_sample=16)
sub.stdin.close() sub.wait() cls.__unlink__(filename) raise err elif pcmreader.channels <= 8: if int(pcmreader.channel_mask) in ( 0x7, # FR, FC, FL 0x33, # FR, FL, BR, BL 0x37, # FR, FC, FL, BL, BR 0x3F, # FR, FC, FL, BL, BR, LFE 0x70F, # FL, FC, FR, SL, SR, BC, LFE 0x63F, ): # FL, FC, FR, SL, SR, BL, BR, LFE standard_channel_mask = ChannelMask(pcmreader.channel_mask) vorbis_channel_mask = VorbisChannelMask(standard_channel_mask) else: raise UnsupportedChannelMask(filename, int(pcmreader.channel_mask)) try: transfer_framelist_data( ReorderedPCMReader( pcmreader, [standard_channel_mask.channels().index(channel) for channel in vorbis_channel_mask.channels()], ), sub.stdin.write, ) except (IOError, ValueError), err: sub.stdin.close() sub.wait()
def channel_mask(self): if (self.channels() <= 2): return ChannelMask.from_channels(self.channels()) else: return ChannelMask(0)
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): """encodes a new file from PCM data takes a filename string, PCMReader object, optional compression level string and optional total_pcm_frames integer encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new M4AAudio object""" import subprocess import os from audiotools import PCMConverter from audiotools import transfer_data from audiotools import transfer_framelist_data from audiotools import ignore_sigint from audiotools import EncodingError from audiotools import DecodingError from audiotools import ChannelMask from audiotools import __default_quality__ if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) if pcmreader.bits_per_sample not in {8, 16, 24}: from audiotools import UnsupportedBitsPerSample pcmreader.close() raise UnsupportedBitsPerSample(filename, pcmreader.bits_per_sample) if pcmreader.channels > 2: pcmreader = PCMConverter(pcmreader, sample_rate=pcmreader.sample_rate, channels=2, channel_mask=ChannelMask.from_channels(2), bits_per_sample=pcmreader.bits_per_sample) # faac requires files to end with .m4a for some reason if not filename.endswith(".m4a"): import tempfile actual_filename = filename tempfile = tempfile.NamedTemporaryFile(suffix=".m4a") filename = tempfile.name else: actual_filename = tempfile = None sub = subprocess.Popen( [BIN['faac'], "-q", compression, "-P", "-R", str(pcmreader.sample_rate), "-B", str(pcmreader.bits_per_sample), "-C", str(pcmreader.channels), "-X", "-o", filename, "-"], stdin=subprocess.PIPE, stderr=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb"), stdout=subprocess.DEVNULL if hasattr(subprocess, "DEVNULL") else open(os.devnull, "wb"), preexec_fn=ignore_sigint) # Note: faac handles SIGINT on its own, # so trying to ignore it doesn't work like on most other encoders. try: if total_pcm_frames is not None: from audiotools import CounterPCMReader pcmreader = CounterPCMReader(pcmreader) transfer_framelist_data(pcmreader, sub.stdin.write) if ((total_pcm_frames is not None) and (total_pcm_frames != pcmreader.frames_written)): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) except (ValueError, IOError) as err: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise EncodingError(str(err)) except Exception: sub.stdin.close() sub.wait() cls.__unlink__(filename) raise sub.stdin.close() if sub.wait() == 0: if tempfile is not None: filename = actual_filename f = open(filename, 'wb') tempfile.seek(0, 0) transfer_data(tempfile.read, f.write) f.close() tempfile.close() return M4AAudio(filename) else: if tempfile is not None: tempfile.close() raise EncodingError(u"unable to write file with faac")
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=None): from audiotools import __default_quality__ from audiotools import PCMConverter from audiotools import ChannelMask from audiotools.encoders import encode_mpc if (compression is None) or (compression not in cls.COMPRESSION_MODES): compression = __default_quality__(cls.NAME) if pcmreader.bits_per_sample not in {8, 16, 24}: from audiotools import UnsupportedBitsPerSample pcmreader.close() raise UnsupportedBitsPerSample(filename, pcmreader.bits_per_sample) if pcmreader.sample_rate in (32000, 37800, 44100, 48000): sample_rate = pcmreader.sample_rate if total_pcm_frames is not None: from audiotools import CounterPCMReader pcmreader = CounterPCMReader(pcmreader) else: from bisect import bisect sample_rate = [32000, 32000, 37800, 44100, 48000][bisect([32000, 37800, 44100, 4800], pcmreader.sample_rate)] total_pcm_frames = None try: encode_mpc( filename, PCMConverter(pcmreader, sample_rate=sample_rate, channels=min(pcmreader.channels, 2), channel_mask=int(ChannelMask.from_channels( min(pcmreader.channels, 2))), bits_per_sample=16), float(compression), total_pcm_frames if (total_pcm_frames is not None) else 0) # ensure PCM frames match, if indicated if ((total_pcm_frames is not None) and (total_pcm_frames != pcmreader.frames_written)): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH from audiotools import EncodingError raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) return MPCAudio(filename) except (IOError, ValueError) as err: from audiotools import EncodingError cls.__unlink__(filename) raise EncodingError(str(err)) except Exception: cls.__unlink__(filename) raise finally: pcmreader.close()