Esempio n. 1
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 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()
Esempio n. 3
0
    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)
Esempio n. 4
0
    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'])
Esempio n. 5
0
    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)
Esempio n. 6
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)
Esempio n. 7
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)
Esempio n. 8
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
Esempio n. 9
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
Esempio n. 10
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))
Esempio n. 11
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)
Esempio n. 12
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))
Esempio n. 13
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
Esempio n. 14
0
    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)
Esempio n. 15
0
    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)
Esempio n. 16
0
    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)
Esempio n. 17
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'])
Esempio n. 18
0
    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)
Esempio n. 19
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)
Esempio n. 20
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"""

    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)
Esempio n. 21
0
    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)
Esempio n. 22
0
 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)
Esempio n. 23
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 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))
Esempio n. 24
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 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))
Esempio n. 25
0
    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()
Esempio n. 26
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 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))
Esempio n. 27
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.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")
Esempio n. 28
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 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")
Esempio n. 29
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 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")
Esempio n. 30
0
    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()
    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))
Esempio n. 32
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 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')

        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)

        try:
            transfer_framelist_data(pcmreader, sub.stdin.write)
        except (IOError, ValueError), err:
            sub.stdin.close()
            sub.wait()
            cls.__unlink__(filename)
            raise EncodingError(str(err))
Esempio n. 33
0
 def channel_mask(self):
     if (self.channels() <= 2):
         return ChannelMask.from_channels(self.channels())
     else:
         return ChannelMask(0)
    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)
Esempio n. 35
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")