def parse(cls, mp3_file): """given an MP3 file, returns an ID3v1Comment raises ValueError if the comment is invalid""" from audiotools.bitstream import parse def decode_string(s): return s.rstrip(b"\x00").decode("ascii", "replace") mp3_file.seek(-128, 2) (tag, track_name, artist_name, album_name, year, comment, track_number, genre) = parse("3b 30b 30b 30b 4b 28b 8p 8u 8u", False, mp3_file.read(128)) if tag != b'TAG': raise ValueError(u"invalid ID3v1 tag") return ID3v1Comment(track_name=decode_string(track_name), artist_name=decode_string(artist_name), album_name=decode_string(album_name), year=decode_string(year), comment=decode_string(comment), track_number=track_number, genre=genre)
def read(cls, apefile): """returns an ApeTag object from an APEv2 tagged file object may return None if the file object has no tag""" from audiotools.bitstream import BitstreamReader, parse apefile.seek(-32, 2) tag_footer = apefile.read(32) if len(tag_footer) < 32: # not enough bytes for an ApeV2 tag return None (preamble, version, tag_size, item_count, read_only, item_encoding, is_header, no_footer, has_header) = parse(cls.HEADER_FORMAT, True, tag_footer) if (preamble != b"APETAGEX") or (version != 2000): return None apefile.seek(-tag_size, 2) reader = BitstreamReader(apefile, True) return cls([ApeTagItem.parse(reader) for i in range(item_count)], contains_header=has_header, contains_footer=True)
def parse(cls, mp3_file): """given an MP3 file, returns an ID3v1Comment raises ValueError if the comment is invalid""" from audiotools.bitstream import parse def decode_string(s): return s.rstrip(b"\x00").decode("ascii", "replace") mp3_file.seek(-128, 2) (tag, track_name, artist_name, album_name, year, comment, track_number, genre) = parse("3b 30b 30b 30b 4b 28b 8p 8u 8u", False, mp3_file.read(128)) if (tag != b'TAG'): raise ValueError(u"invalid ID3v1 tag") return ID3v1Comment(track_name=decode_string(track_name), artist_name=decode_string(artist_name), album_name=decode_string(album_name), year=decode_string(year), comment=decode_string(comment), track_number=track_number, genre=genre)
def __titlesets__(self): """return valid audio titleset integers from AUDIO_TS.IFO""" from audiotools.bitstream import parse try: f = open(self.files['AUDIO_TS.IFO'], 'rb') except (KeyError, IOError): from audiotools.text import ERR_DVDA_IOERROR_AUDIO_TS raise InvalidDVDA(ERR_DVDA_IOERROR_AUDIO_TS) try: (identifier, AMG_start_sector, AMGI_end_sector, DVD_version, volume_count, volume_number, disc_side, autoplay, ts_to_sv, video_titlesets, audio_titlesets, provider_information) = parse( "12b 32u 12P 32u 16u 4P 16u 16u 8u 4P 8u 32u 10P 8u 8u 40b", False, f.read(104)) if (identifier != b'DVDAUDIO-AMG'): from audiotools.text import ERR_DVDA_INVALID_AUDIO_TS raise InvalidDVDA(ERR_DVDA_INVALID_AUDIO_TS) for titleset in range(1, audio_titlesets + 1): # ensure there are IFO files and AOBs # for each valid titleset if (("ATS_%2.2d_0.IFO" % (titleset) in self.files.keys()) and ("ATS_%2.2d_1.AOB" % (titleset) in self.files.keys())): yield titleset finally: f.close()
def __init__(self, filename): AudioFile.__init__(self, filename) from audiotools.bitstream import parse from audiotools.text import (ERR_AU_INVALID_HEADER, ERR_AU_UNSUPPORTED_FORMAT) try: with open(filename, "rb") as f: (magic_number, self.__data_offset__, self.__data_size__, encoding_format, self.__sample_rate__, self.__channels__) = parse("4b 5* 32u", False, f.read(24)) except IOError as msg: raise InvalidAU(str(msg)) if (magic_number != b'.snd'): raise InvalidAU(ERR_AU_INVALID_HEADER) try: self.__bits_per_sample__ = {2: 8, 3: 16, 4: 24}[encoding_format] except KeyError: raise InvalidAU(ERR_AU_UNSUPPORTED_FORMAT)
def __init__(self, filename): AudioFile.__init__(self, filename) from audiotools.bitstream import parse from audiotools.text import (ERR_AU_INVALID_HEADER, ERR_AU_UNSUPPORTED_FORMAT) try: with open(filename, "rb") as f: (magic_number, self.__data_offset__, self.__data_size__, encoding_format, self.__sample_rate__, self.__channels__) = parse("4b 5* 32u", False, f.read(24)) except IOError as msg: raise InvalidAU(str(msg)) if magic_number != b'.snd': raise InvalidAU(ERR_AU_INVALID_HEADER) try: self.__bits_per_sample__ = {2: 8, 3: 16, 4: 24}[encoding_format] except KeyError: raise InvalidAU(ERR_AU_UNSUPPORTED_FORMAT)
def __init__(self, filename): """filename is a plain string""" AudioFile.__init__(self, filename) from audiotools.bitstream import parse try: mp3file = open(filename, "rb") except IOError as msg: raise InvalidMP3(str(msg)) try: try: header_bytes = MP3Audio.__find_next_mp3_frame__(mp3file) except IOError: from audiotools.text import ERR_MP3_FRAME_NOT_FOUND raise InvalidMP3(ERR_MP3_FRAME_NOT_FOUND) (frame_sync, mpeg_id, layer, bit_rate, sample_rate, pad, channels) = parse("11u 2u 2u 1p 4u 2u 1u 1p 2u 6p", False, mp3file.read(4)) self.__samplerate__ = self.SAMPLE_RATE[mpeg_id][sample_rate] if self.__samplerate__ is None: from audiotools.text import ERR_MP3_INVALID_SAMPLE_RATE raise InvalidMP3(ERR_MP3_INVALID_SAMPLE_RATE) if channels in (0, 1, 2): self.__channels__ = 2 else: self.__channels__ = 1 first_frame = mp3file.read(self.frame_length(mpeg_id, layer, bit_rate, sample_rate, pad) - 4) if ((b"Xing" in first_frame) and (len(first_frame[first_frame.index(b"Xing"): first_frame.index(b"Xing") + 160]) == 160)): # pull length from Xing header, if present self.__pcm_frames__ = ( parse("32p 32p 32u 32p 832p", 0, first_frame[first_frame.index(b"Xing"): first_frame.index(b"Xing") + 160])[0] * self.PCM_FRAMES_PER_MPEG_FRAME[layer]) else: # otherwise, bounce through file frames from audiotools.bitstream import BitstreamReader reader = BitstreamReader(mp3file, False) self.__pcm_frames__ = 0 try: (frame_sync, mpeg_id, layer, bit_rate, sample_rate, pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p") while frame_sync == 0x7FF: self.__pcm_frames__ += \ self.PCM_FRAMES_PER_MPEG_FRAME[layer] reader.skip_bytes(self.frame_length(mpeg_id, layer, bit_rate, sample_rate, pad) - 4) (frame_sync, mpeg_id, layer, bit_rate, sample_rate, pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p") except IOError: pass except ValueError as err: raise InvalidMP3(err) finally: mp3file.close()
def update_metadata(self, metadata): """takes this track's current MetaData object as returned by get_metadata() and sets this track's metadata with any fields updated in that object raises IOError if unable to write the file """ if metadata is None: return elif not isinstance(metadata, ApeTag): from audiotools.text import ERR_FOREIGN_METADATA raise ValueError(ERR_FOREIGN_METADATA) from audiotools.bitstream import parse, BitstreamWriter from audiotools import transfer_data f = open(self.filename, "r+b") f.seek(-32, 2) tag_footer = f.read(32) if len(tag_footer) < 32: # no existing ApeTag can fit, so append fresh tag f.close() with BitstreamWriter(open(self.filename, "ab"), True) as writer: metadata.build(writer) return (preamble, version, tag_size, item_count, read_only, item_encoding, is_header, no_footer, has_header) = parse(ApeTag.HEADER_FORMAT, True, tag_footer) if (preamble == b'APETAGEX') and (version == 2000): if has_header: old_tag_size = 32 + tag_size else: old_tag_size = tag_size if metadata.total_size() >= old_tag_size: # metadata has grown # so append it to existing file f.seek(-old_tag_size, 2) writer = BitstreamWriter(f, True) metadata.build(writer) writer.close() else: f.close() # metadata has shrunk # so rewrite file with smaller metadata from audiotools import TemporaryFile from os.path import getsize # copy everything but the last "old_tag_size" bytes # from existing file to rewritten file new_apev2 = TemporaryFile(self.filename) with open(self.filename, "rb") as old_apev2: limited_transfer_data( old_apev2.read, new_apev2.write, getsize(self.filename) - old_tag_size) # append new tag to rewritten file with BitstreamWriter(new_apev2, True) as writer: metadata.build(writer) # closing writer closes new_apev2 also else: # no existing metadata, so simply append a fresh tag f.close() with BitstreamWriter(open(self.filename, "ab"), True) as writer: metadata.build(writer)
def __init__(self, filename): """filename is a plain string""" AudioFile.__init__(self, filename) from audiotools.bitstream import parse try: mp3file = open(filename, "rb") except IOError as msg: raise InvalidMP3(str(msg)) try: try: header_bytes = MP3Audio.__find_next_mp3_frame__(mp3file) except IOError: from audiotools.text import ERR_MP3_FRAME_NOT_FOUND raise InvalidMP3(ERR_MP3_FRAME_NOT_FOUND) (frame_sync, mpeg_id, layer, bit_rate, sample_rate, pad, channels) = parse("11u 2u 2u 1p 4u 2u 1u 1p 2u 6p", False, mp3file.read(4)) self.__samplerate__ = self.SAMPLE_RATE[mpeg_id][sample_rate] if self.__samplerate__ is None: from audiotools.text import ERR_MP3_INVALID_SAMPLE_RATE raise InvalidMP3(ERR_MP3_INVALID_SAMPLE_RATE) if channels in (0, 1, 2): self.__channels__ = 2 else: self.__channels__ = 1 first_frame = mp3file.read( self.frame_length(mpeg_id, layer, bit_rate, sample_rate, pad) - 4) if ((b"Xing" in first_frame) and (len(first_frame[first_frame. index(b"Xing"):first_frame.index(b"Xing") + 160]) == 160)): # pull length from Xing header, if present self.__pcm_frames__ = (parse( "32p 32p 32u 32p 832p", 0, first_frame[first_frame.index( b"Xing"):first_frame.index(b"Xing") + 160])[0] * self.PCM_FRAMES_PER_MPEG_FRAME[layer]) else: # otherwise, bounce through file frames from audiotools.bitstream import BitstreamReader reader = BitstreamReader(mp3file, False) self.__pcm_frames__ = 0 try: (frame_sync, mpeg_id, layer, bit_rate, sample_rate, pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p") while frame_sync == 0x7FF: self.__pcm_frames__ += \ self.PCM_FRAMES_PER_MPEG_FRAME[layer] reader.skip_bytes( self.frame_length(mpeg_id, layer, bit_rate, sample_rate, pad) - 4) (frame_sync, mpeg_id, layer, bit_rate, sample_rate, pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p") except IOError: pass except ValueError as err: raise InvalidMP3(err) finally: mp3file.close()
def update_metadata(self, metadata): """takes this track's current MetaData object as returned by get_metadata() and sets this track's metadata with any fields updated in that object raises IOError if unable to write the file """ from audiotools.bitstream import (parse, BitstreamWriter, BitstreamReader) from audiotools import transfer_data if metadata is None: return elif not isinstance(metadata, ApeTag): from audiotools.text import ERR_FOREIGN_METADATA raise ValueError(ERR_FOREIGN_METADATA) elif len(metadata.keys()) == 0: # wipe out entire block of metadata from os import access, R_OK, W_OK if not access(self.filename, R_OK | W_OK): raise IOError(self.filename) with open(self.filename, "rb") as f: f.seek(-32, 2) (preamble, version, tag_size, item_count, read_only, item_encoding, is_header, no_footer, has_header) = BitstreamReader(f, True).parse( ApeTag.HEADER_FORMAT) if (preamble == b'APETAGEX') and (version == 2000): from audiotools import TemporaryFile, transfer_data from os.path import getsize # there's existing metadata to delete # so rewrite file without trailing metadata tag if has_header: old_tag_size = 32 + tag_size else: old_tag_size = tag_size # copy everything but the last "old_tag_size" bytes # from existing file to rewritten file new_apev2 = TemporaryFile(self.filename) old_apev2 = open(self.filename, "rb") limited_transfer_data( old_apev2.read, new_apev2.write, getsize(self.filename) - old_tag_size) old_apev2.close() new_apev2.close() else: # re-set metadata block at end of file f = open(self.filename, "r+b") f.seek(-32, 2) tag_footer = f.read(32) if len(tag_footer) < 32: # no existing ApeTag can fit, so append fresh tag f.close() with BitstreamWriter(open(self.filename, "ab"), True) as writer: metadata.build(writer) return (preamble, version, tag_size, item_count, read_only, item_encoding, is_header, no_footer, has_header) = parse(ApeTag.HEADER_FORMAT, True, tag_footer) if (preamble == b'APETAGEX') and (version == 2000): if has_header: old_tag_size = 32 + tag_size else: old_tag_size = tag_size if metadata.total_size() >= old_tag_size: # metadata has grown # so append it to existing file f.seek(-old_tag_size, 2) writer = BitstreamWriter(f, True) metadata.build(writer) writer.close() else: f.close() # metadata has shrunk # so rewrite file with smaller metadata from audiotools import TemporaryFile from os.path import getsize # copy everything but the last "old_tag_size" bytes # from existing file to rewritten file new_apev2 = TemporaryFile(self.filename) with open(self.filename, "rb") as old_apev2: limited_transfer_data( old_apev2.read, new_apev2.write, getsize(self.filename) - old_tag_size) # append new tag to rewritten file with BitstreamWriter(new_apev2, True) as writer: metadata.build(writer) # closing writer closes new_apev2 also else: # no existing metadata, so simply append a fresh tag f.close() with BitstreamWriter(open(self.filename, "ab"), True) as writer: metadata.build(writer)