class __GIF__(ImageMetrics): HEADER = Con.Struct('header', Con.Const(Con.String('gif', 3), 'GIF'), Con.String('version', 3)) SCREEN_DESCRIPTOR = Con.Struct('logical_screen_descriptor', Con.ULInt16('width'), Con.ULInt16('height'), Con.Embed( Con.BitStruct('packed_fields', Con.Flag('global_color_table'), Con.Bits('color_resolution', 3), Con.Flag('sort'), Con.Bits('global_color_table_size', 3))), Con.Byte('background_color_index'), Con.Byte('pixel_aspect_ratio')) def __init__(self, width, height, color_count): ImageMetrics.__init__(self, width, height, 8, color_count, u'image/gif') @classmethod def parse(cls, file): try: header = cls.HEADER.parse_stream(file) descriptor = cls.SCREEN_DESCRIPTOR.parse_stream(file) return __GIF__(descriptor.width, descriptor.height, 2 ** (descriptor.global_color_table_size + 1)) except Con.ConstError: raise InvalidGIF(_(u'Invalid GIF'))
class VorbisAudio(AudioFile): """An Ogg Vorbis file.""" SUFFIX = "ogg" NAME = SUFFIX DEFAULT_COMPRESSION = "3" COMPRESSION_MODES = tuple([str(i) for i in range(0, 11)]) BINARIES = ("oggenc", "oggdec") REPLAYGAIN_BINARIES = ("vorbisgain", ) OGG_IDENTIFICATION = Con.Struct( "ogg_id", Con.ULInt32("vorbis_version"), Con.Byte("channels"), Con.ULInt32("sample_rate"), Con.ULInt32("bitrate_maximum"), Con.ULInt32("bitrate_nominal"), Con.ULInt32("bitrate_minimum"), Con.Embed( Con.BitStruct("flags", Con.Bits("blocksize_0", 4), Con.Bits("blocksize_1", 4))), Con.Byte("framing")) COMMENT_HEADER = Con.Struct("comment_header", Con.Byte("packet_type"), Con.String("vorbis", 6)) def __init__(self, filename): """filename is a plain string.""" AudioFile.__init__(self, filename) try: self.__read_metadata__() except IOError, msg: raise InvalidVorbis(str(msg))
def __init__(self, name): Con.Adapter.__init__( self, Con.Struct( name, Con.Embed( Con.BitStruct(None, Con.Flag("signed"), Con.Bits("exponent", 15))), Con.UBInt64("mantissa")))
def __init__(self, name): Con.Adapter.__init__( self, Con.BitStruct("mp3_header", Con.Const(Con.Bits("sync", 11), 0x7FF), Con.Bits("mpeg_version", 2), Con.Bits("layer", 2), Con.Flag("no_crc16", 1), Con.Bits("bitrate", 4), Con.Bits("sample_rate", 2), Con.Bits("pad", 1), Con.Bits("private", 1), Con.Bits("channel", 2), Con.Bits("mode_extension", 2), Con.Flag("copyright", 1), Con.Flag("original", 1), Con.Bits("emphasis", 2)))
class MusepackAudio(ApeTaggedAudio, AudioFile): """A Musepack audio file.""" SUFFIX = "mpc" NAME = SUFFIX DEFAULT_COMPRESSION = "standard" COMPRESSION_MODES = ("thumb", "radio", "standard", "extreme", "insane") ###Musepack SV7### #BINARIES = ('mppdec','mppenc') ###Musepack SV8### BINARIES = ('mpcdec', 'mpcenc') MUSEPACK8_HEADER = Con.Struct('musepack8_header', Con.UBInt32('crc32'), Con.Byte('bitstream_version'), NutValue('sample_count'), NutValue('beginning_silence'), Con.Embed(Con.BitStruct( 'flags', Con.Bits('sample_frequency', 3), Con.Bits('max_used_bands', 5), Con.Bits('channel_count', 4), Con.Flag('mid_side_used'), Con.Bits('audio_block_frames', 3)))) #not sure about some of the flag locations #Musepack 7's header is very unusual MUSEPACK7_HEADER = Con.Struct('musepack7_header', Con.Const(Con.String('signature', 3), 'MP+'), Con.Byte('version'), Con.ULInt32('frame_count'), Con.ULInt16('max_level'), Con.Embed( Con.BitStruct('flags', Con.Bits('profile', 4), Con.Bits('link', 2), Con.Bits('sample_frequency', 2), Con.Flag('intensity_stereo'), Con.Flag('midside_stereo'), Con.Bits('maxband', 6))), Con.ULInt16('title_gain'), Con.ULInt16('title_peak'), Con.ULInt16('album_gain'), Con.ULInt16('album_peak'), Con.Embed( Con.BitStruct('more_flags', Con.Bits('unused1', 16), Con.Bits('last_frame_length_low', 4), Con.Flag('true_gapless'), Con.Bits('unused2', 3), Con.Flag('fast_seeking'), Con.Bits('last_frame_length_high', 7))), Con.Bytes('unknown', 3), Con.Byte('encoder_version')) def __init__(self, filename): """filename is a plain string.""" AudioFile.__init__(self, filename) f = file(filename, 'rb') try: if (f.read(4) == 'MPCK'): # a Musepack 8 stream for (key, packet) in Musepack8StreamReader(f).packets(): if (key == 'SH'): header = MusepackAudio.MUSEPACK8_HEADER.parse(packet) self.__sample_rate__ = (44100, 48000, 37800, 32000)[ header.sample_frequency] self.__total_frames__ = header.sample_count self.__channels__ = header.channel_count + 1 break elif (key == 'SE'): raise InvalidFile(_(u'No Musepack header found')) else: # a Musepack 7 stream f.seek(0, 0) try: header = MusepackAudio.MUSEPACK7_HEADER.parse_stream(f) except Con.ConstError: raise InvalidFile(_(u'Musepack signature incorrect')) header.last_frame_length = \ (header.last_frame_length_high << 4) | \ header.last_frame_length_low self.__sample_rate__ = (44100, 48000, 37800, 32000)[header.sample_frequency] self.__total_frames__ = (((header.frame_count - 1) * 1152) + header.last_frame_length) self.__channels__ = 2 finally: f.close() @classmethod 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 MusepackAudio object.""" import tempfile import bisect if (str(compression) not in cls.COMPRESSION_MODES): compression = cls.DEFAULT_COMPRESSION if ((pcmreader.channels > 2) or (pcmreader.sample_rate not in (44100, 48000, 37800, 32000)) or (pcmreader.bits_per_sample != 16)): pcmreader = PCMConverter( pcmreader, sample_rate=[32000, 32000, 37800, 44100, 48000][bisect.bisect( [32000, 37800, 44100, 48000], pcmreader.sample_rate)], channels=min(pcmreader.channels, 2), bits_per_sample=16) f = tempfile.NamedTemporaryFile(suffix=".wav") w = WaveAudio.from_pcm(f.name, pcmreader) try: return cls.__from_wave__(filename, f.name, compression) finally: del(w) f.close() #While Musepack needs to pipe things through WAVE, #not all WAVEs are acceptable. #Use the *_pcm() methods first. def __to_wave__(self, wave_filename): devnull = file(os.devnull, "wb") try: sub = subprocess.Popen([BIN['mpcdec'], self.filename, wave_filename], stdout=devnull, stderr=devnull) #FIXME - small files (~5 seconds) result in an error by mpcdec, #even if they decode correctly. #Not much we can do except try to workaround its bugs. if (sub.wait() not in [0, 250]): raise DecodingError() finally: devnull.close() @classmethod def __from_wave__(cls, filename, wave_filename, compression=None): if (str(compression) not in cls.COMPRESSION_MODES): compression = cls.DEFAULT_COMPRESSION #mppenc requires files to end with .mpc for some reason if (not filename.endswith(".mpc")): import tempfile actual_filename = filename tempfile = tempfile.NamedTemporaryFile(suffix=".mpc") filename = tempfile.name else: actual_filename = tempfile = None ###Musepack SV7### #sub = subprocess.Popen([BIN['mppenc'], # "--silent", # "--overwrite", # "--%s" % (compression), # wave_filename, # filename], # preexec_fn=ignore_sigint) ###Musepack SV8### sub = subprocess.Popen([BIN['mpcenc'], "--silent", "--overwrite", "--%s" % (compression), wave_filename, filename]) if (sub.wait() == 0): if (tempfile is not None): filename = actual_filename f = file(filename, 'wb') tempfile.seek(0, 0) transfer_data(tempfile.read, f.write) f.close() tempfile.close() return MusepackAudio(filename) else: if (tempfile is not None): tempfile.close() raise EncodingError(u"error encoding file with mpcenc") @classmethod def is_type(cls, file): """Returns True if the given file object describes this format. Takes a seekable file pointer rewound to the start of the file.""" header = file.read(4) ###Musepack SV7### #return header == 'MP+\x07' ###Musepack SV8### return (header == 'MP+\x07') or (header == 'MPCK') def sample_rate(self): """Returns the rate of the track's audio as an integer number of Hz.""" return self.__sample_rate__ def total_frames(self): """Returns the total PCM frames of the track as an integer.""" return self.__total_frames__ def channels(self): """Returns an integer number of channels this track contains.""" return self.__channels__ def bits_per_sample(self): """Returns an integer number of bits-per-sample this track contains.""" return 16 def lossless(self): """Returns False.""" return False
Con.StrictRepeater(3, Con.Byte("extended_descriptor_type")), Con.Byte("descriptor_type_length"), Con.UBInt16("OD_ID"), Con.Byte("OD_profile"), Con.Byte("scene_profile"), Con.Byte("audio_profile"), Con.Byte("video_profile"), Con.Byte("graphics_profile")), 0x0E: Con.Struct( None, Con.StrictRepeater(3, Con.Byte("extended_descriptor_type")), Con.Byte("descriptor_type_length"), Con.String("track_id", 4)) })) ATOM_TKHD = Con.Struct( "tkhd", Con.Byte("version"), Con.BitStruct("flags", Con.Padding(20), Con.Flag("TrackInPoster"), Con.Flag("TrackInPreview"), Con.Flag("TrackInMovie"), Con.Flag("TrackEnabled")), VersionLength("created_mac_UTC_date"), VersionLength("modified_mac_UTC_date"), Con.UBInt32("track_id"), Con.Padding(4), VersionLength("duration"), Con.Padding(8), Con.UBInt16("video_layer"), Con.UBInt16("quicktime_alternate"), Con.UBInt16("volume"), Con.Padding(2), Con.Struct("video", Con.UBInt32("geometry_matrix_a"), Con.UBInt32("geometry_matrix_b"), Con.UBInt32("geometry_matrix_u"), Con.UBInt32("geometry_matrix_c"), Con.UBInt32("geometry_matrix_d"), Con.UBInt32("geometry_matrix_v"), Con.UBInt32("geometry_matrix_x"), Con.UBInt32("geometry_matrix_y"), Con.UBInt32("geometry_matrix_w")), Con.UBInt32("video_width"),
class MP3Audio(AudioFile): """An MP3 audio file.""" SUFFIX = "mp3" NAME = SUFFIX DEFAULT_COMPRESSION = "2" #0 is better quality/lower compression #9 is worse quality/higher compression COMPRESSION_MODES = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9") BINARIES = ("lame",) REPLAYGAIN_BINARIES = ("mp3gain", ) #MPEG1, Layer 1 #MPEG1, Layer 2, #MPEG1, Layer 3, #MPEG2, Layer 1, #MPEG2, Layer 2, #MPEG2, Layer 3 MP3_BITRATE = ((None, None, None, None, None, None), (32, 32, 32, 32, 8, 8), (64, 48, 40, 48, 16, 16), (96, 56, 48, 56, 24, 24), (128, 64, 56, 64, 32, 32), (160, 80, 64, 80, 40, 40), (192, 96, 80, 96, 48, 48), (224, 112, 96, 112, 56, 56), (256, 128, 112, 128, 64, 64), (288, 160, 128, 144, 80, 80), (320, 192, 160, 160, 96, 96), (352, 224, 192, 176, 112, 112), (384, 256, 224, 192, 128, 128), (416, 320, 256, 224, 144, 144), (448, 384, 320, 256, 160, 160)) #MPEG1, MPEG2, MPEG2.5 MP3_SAMPLERATE = ((44100, 22050, 11025), (48000, 24000, 12000), (32000, 16000, 8000)) MP3_FRAME_HEADER = Con.BitStruct("mp3_header", Con.Const(Con.Bits("sync", 11), 0x7FF), Con.Bits("mpeg_version", 2), Con.Bits("layer", 2), Con.Flag("protection", 1), Con.Bits("bitrate", 4), Con.Bits("sampling_rate", 2), Con.Bits("padding", 1), Con.Bits("private", 1), Con.Bits("channel", 2), Con.Bits("mode_extension", 2), Con.Flag("copyright", 1), Con.Flag("original", 1), Con.Bits("emphasis", 2)) XING_HEADER = Con.Struct("xing_header", Con.Bytes("header_id", 4), Con.Bytes("flags", 4), Con.UBInt32("num_frames"), Con.UBInt32("bytes"), Con.StrictRepeater(100, Con.Byte("toc_entries")), Con.UBInt32("quality")) def __init__(self, filename): """filename is a plain string.""" AudioFile.__init__(self, filename) try: mp3file = file(filename, "rb") except IOError, msg: raise InvalidMP3(str(msg)) try: try: MP3Audio.__find_next_mp3_frame__(mp3file) except ValueError: raise InvalidMP3(_(u"MP3 frame not found")) fr = MP3Audio.MP3_FRAME_HEADER.parse(mp3file.read(4)) self.__samplerate__ = MP3Audio.__get_mp3_frame_sample_rate__(fr) self.__channels__ = MP3Audio.__get_mp3_frame_channels__(fr) self.__framelength__ = self.__length__() finally: mp3file.close()
class WavPackAudio(ApeTaggedAudio, AudioFile): """A WavPack audio file.""" SUFFIX = "wv" NAME = SUFFIX DEFAULT_COMPRESSION = "standard" COMPRESSION_MODES = ("veryfast", "fast", "standard", "high", "veryhigh") APE_TAG_CLASS = WavPackAPEv2 HEADER = Con.Struct( "wavpackheader", Con.Const(Con.String("id", 4), 'wvpk'), Con.ULInt32("block_size"), Con.ULInt16("version"), Con.ULInt8("track_number"), Con.ULInt8("index_number"), Con.ULInt32("total_samples"), Con.ULInt32("block_index"), Con.ULInt32("block_samples"), Con.Embed( Con.BitStruct("flags", Con.Flag("floating_point_data"), Con.Flag("hybrid_noise_shaping"), Con.Flag("cross_channel_decorrelation"), Con.Flag("joint_stereo"), Con.Flag("hybrid_mode"), Con.Flag("mono_output"), Con.Bits("bits_per_sample", 2), Con.Bits("left_shift_data_low", 3), Con.Flag("final_block_in_sequence"), Con.Flag("initial_block_in_sequence"), Con.Flag("hybrid_noise_balanced"), Con.Flag("hybrid_mode_control_bitrate"), Con.Flag("extended_size_integers"), Con.Bit("sampling_rate_low"), Con.Bits("maximum_magnitude", 5), Con.Bits("left_shift_data_high", 2), Con.Flag("reserved2"), Con.Flag("false_stereo"), Con.Flag("use_IIR"), Con.Bits("reserved1", 2), Con.Bits("sampling_rate_high", 3))), Con.ULInt32("crc")) SUB_HEADER = Con.Struct( "wavpacksubheader", Con.Embed( Con.BitStruct("flags", Con.Flag("large_block"), Con.Flag("actual_size_1_less"), Con.Flag("nondecoder_data"), Con.Bits("metadata_function", 5))), Con.IfThenElse('size', lambda ctx: ctx['large_block'], ULInt24('s'), Con.Byte('s'))) BITS_PER_SAMPLE = (8, 16, 24, 32) SAMPLING_RATE = (6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000, 0) __options__ = { "veryfast": { "block_size": 44100, "joint_stereo": True, "false_stereo": True, "wasted_bits": True, "decorrelation_passes": 1 }, "fast": { "block_size": 44100, "joint_stereo": True, "false_stereo": True, "wasted_bit": True, "decorrelation_passes": 2 }, "standard": { "block_size": 44100, "joint_stereo": True, "false_stereo": True, "wasted_bits": True, "decorrelation_passes": 5 }, "high": { "block_size": 44100, "joint_stereo": True, "false_stereo": True, "wasted_bits": True, "decorrelation_passes": 10 }, "veryhigh": { "block_size": 44100, "joint_stereo": True, "false_stereo": True, "wasted_bits": True, "decorrelation_passes": 16 } } def __init__(self, filename): """filename is a plain string.""" self.filename = filename self.__samplerate__ = 0 self.__channels__ = 0 self.__bitspersample__ = 0 self.__total_frames__ = 0 try: self.__read_info__() except IOError, msg: raise InvalidWavPack(str(msg))