class UnframedVorbisComment(VorbisComment):
    """An implementation of VorbisComment without the framing bit."""

    VORBIS_COMMENT = Con.Struct(
        "vorbis_comment",
        Con.PascalString("vendor_string", length_field=Con.ULInt32("length")),
        Con.PrefixedArray(length_field=Con.ULInt32("length"),
                          subcon=Con.PascalString(
                              "value", length_field=Con.ULInt32("length"))))
Beispiel #2
0
class __TIFF__(ImageMetrics):
    HEADER = Con.Struct('header',
                        Con.String('byte_order', 2),
                        Con.Switch('order',
                                   lambda ctx: ctx['byte_order'],
                                   {"II": Con.Embed(
        Con.Struct('little_endian',
                   Con.Const(Con.ULInt16('version'), 42),
                   Con.ULInt32('offset'))),
                                    "MM": Con.Embed(
        Con.Struct('big_endian',
                   Con.Const(Con.UBInt16('version'), 42),
                   Con.UBInt32('offset')))}))

    L_IFD = Con.Struct('ifd',
                       Con.PrefixedArray(
        length_field=Con.ULInt16('length'),
        subcon=Con.Struct('tags',
                          Con.ULInt16('id'),
                          Con.ULInt16('type'),
                          Con.ULInt32('count'),
                          Con.ULInt32('offset'))),
                       Con.ULInt32('next'))

    B_IFD = Con.Struct('ifd',
                       Con.PrefixedArray(
        length_field=Con.UBInt16('length'),
        subcon=Con.Struct('tags',
                          Con.UBInt16('id'),
                          Con.UBInt16('type'),
                          Con.UBInt32('count'),
                          Con.UBInt32('offset'))),
                       Con.UBInt32('next'))

    def __init__(self, width, height, bits_per_pixel, color_count):
        ImageMetrics.__init__(self, width, height,
                              bits_per_pixel, color_count,
                              u'image/tiff')

    @classmethod
    def b_tag_value(cls, file, tag):
        subtype = {1: Con.Byte("data"),
                   2: Con.CString("data"),
                   3: Con.UBInt16("data"),
                   4: Con.UBInt32("data"),
                   5: Con.Struct("data",
                                 Con.UBInt32("high"),
                                 Con.UBInt32("low"))}[tag.type]

        data = Con.StrictRepeater(tag.count,
                                  subtype)
        if ((tag.type != 2) and (data.sizeof() <= 4)):
            return tag.offset
        else:
            file.seek(tag.offset, 0)
            return data.parse_stream(file)

    @classmethod
    def l_tag_value(cls, file, tag):
        subtype = {1: Con.Byte("data"),
                   2: Con.CString("data"),
                   3: Con.ULInt16("data"),
                   4: Con.ULInt32("data"),
                   5: Con.Struct("data",
                                 Con.ULInt32("high"),
                                 Con.ULInt32("low"))}[tag.type]

        data = Con.StrictRepeater(tag.count,
                                  subtype)
        if ((tag.type != 2) and (data.sizeof() <= 4)):
            return tag.offset
        else:
            file.seek(tag.offset, 0)
            return data.parse_stream(file)

    @classmethod
    def parse(cls, file):
        width = 0
        height = 0
        bits_per_sample = 0
        color_count = 0

        try:
            header = cls.HEADER.parse_stream(file)
            if (header.byte_order == 'II'):
                IFD = cls.L_IFD
                tag_value = cls.l_tag_value
            elif (header.byte_order == 'MM'):
                IFD = cls.B_IFD
                tag_value = cls.b_tag_value
            else:
                raise InvalidTIFF(_(u'Invalid byte order'))

            file.seek(header.offset, 0)

            ifd = IFD.parse_stream(file)

            while (True):
                for tag in ifd.tags:
                    if (tag.id == 0x0100):
                        width = tag_value(file, tag)
                    elif (tag.id == 0x0101):
                        height = tag_value(file, tag)
                    elif (tag.id == 0x0102):
                        try:
                            bits_per_sample = sum(tag_value(file, tag))
                        except TypeError:
                            bits_per_sample = tag_value(file, tag)
                    elif (tag.id == 0x0140):
                        color_count = tag.count / 3
                    else:
                        pass

                if (ifd.next == 0x00):
                    break
                else:
                    file.seek(ifd.next, 0)
                    ifd = IFD.parse_stream(file)

            return __TIFF__(width, height, bits_per_sample, color_count)
        except Con.ConstError:
            raise InvalidTIFF(_(u'Invalid TIFF'))
    Con.UBInt16("quicktime_quality"))

ATOM_HDLR = Con.Struct("hdlr", Con.Byte("version"), Con.String("flags", 3),
                       Con.String("quicktime_type", 4),
                       Con.String("subtype", 4),
                       Con.String("quicktime_manufacturer", 4),
                       Con.UBInt32("quicktime_component_reserved_flags"),
                       Con.UBInt32("quicktime_component_reserved_flags_mask"),
                       Con.PascalString("component_name"), Con.Padding(1))

ATOM_SMHD = Con.Struct('smhd', Con.Byte("version"), Con.String("flags", 3),
                       Con.String("audio_balance", 2), Con.Padding(2))

ATOM_DREF = Con.Struct(
    'dref', Con.Byte("version"), Con.String("flags", 3),
    Con.PrefixedArray(length_field=Con.UBInt32("num_references"),
                      subcon=Atom("references")))

ATOM_STSD = Con.Struct(
    'stsd', Con.Byte("version"), Con.String("flags", 3),
    Con.PrefixedArray(length_field=Con.UBInt32("num_descriptions"),
                      subcon=Atom("descriptions")))

ATOM_MP4A = Con.Struct("mp4a", Con.Padding(6), Con.UBInt16("reference_index"),
                       Con.UBInt16("quicktime_audio_encoding_version"),
                       Con.UBInt16("quicktime_audio_encoding_revision"),
                       Con.String("quicktime_audio_encoding_vendor", 4),
                       Con.UBInt16("channels"), Con.UBInt16("sample_size"),
                       Con.UBInt16("audio_compression_id"),
                       Con.UBInt16("quicktime_audio_packet_size"),
                       Con.String("sample_rate", 4))
Beispiel #4
0
class VorbisComment(MetaData, dict):
    """A complete Vorbis Comment tag."""

    VORBIS_COMMENT = Con.Struct(
        "vorbis_comment",
        Con.PascalString("vendor_string", length_field=Con.ULInt32("length")),
        Con.PrefixedArray(length_field=Con.ULInt32("length"),
                          subcon=Con.PascalString(
                              "value", length_field=Con.ULInt32("length"))),
        Con.Const(Con.Byte("framing"), 1))

    ATTRIBUTE_MAP = {
        'track_name': 'TITLE',
        'track_number': 'TRACKNUMBER',
        'track_total': 'TRACKTOTAL',
        'album_name': 'ALBUM',
        'artist_name': 'ARTIST',
        'performer_name': 'PERFORMER',
        'composer_name': 'COMPOSER',
        'conductor_name': 'CONDUCTOR',
        'media': 'SOURCE MEDIUM',
        'ISRC': 'ISRC',
        'catalog': 'CATALOG',
        'copyright': 'COPYRIGHT',
        'publisher': 'PUBLISHER',
        'year': 'DATE',
        'album_number': 'DISCNUMBER',
        'album_total': 'DISCTOTAL',
        'comment': 'COMMENT'
    }

    ITEM_MAP = dict(map(reversed, ATTRIBUTE_MAP.items()))

    def __init__(self, vorbis_data, vendor_string=u""):
        """Initialized with a key->[value1,value2] dict.

        keys are generally upper case.
        values are unicode string.
        vendor_string is an optional unicode string."""

        dict.__init__(self, [(key.upper(), values)
                             for (key, values) in vorbis_data.items()])
        self.vendor_string = vendor_string

    def __setitem__(self, key, value):
        dict.__setitem__(self, key.upper(), value)

    def __getattr__(self, key):
        if (key == 'track_number'):
            match = re.match(r'^\d+$', self.get('TRACKNUMBER', [u''])[0])
            if (match):
                return int(match.group(0))
            else:
                match = re.match('^(\d+)/\d+$',
                                 self.get('TRACKNUMBER', [u''])[0])
                if (match):
                    return int(match.group(1))
                else:
                    return 0
        elif (key == 'track_total'):
            match = re.match(r'^\d+$', self.get('TRACKTOTAL', [u''])[0])
            if (match):
                return int(match.group(0))
            else:
                match = re.match('^\d+/(\d+)$',
                                 self.get('TRACKNUMBER', [u''])[0])
                if (match):
                    return int(match.group(1))
                else:
                    return 0
        elif (key == 'album_number'):
            match = re.match(r'^\d+$', self.get('DISCNUMBER', [u''])[0])
            if (match):
                return int(match.group(0))
            else:
                match = re.match('^(\d+)/\d+$',
                                 self.get('DISCNUMBER', [u''])[0])
                if (match):
                    return int(match.group(1))
                else:
                    return 0
        elif (key == 'album_total'):
            match = re.match(r'^\d+$', self.get('DISCTOTAL', [u''])[0])
            if (match):
                return int(match.group(0))
            else:
                match = re.match('^\d+/(\d+)$',
                                 self.get('DISCNUMBER', [u''])[0])
                if (match):
                    return int(match.group(1))
                else:
                    return 0
        elif (key in self.ATTRIBUTE_MAP):
            return self.get(self.ATTRIBUTE_MAP[key], [u''])[0]
        elif (key in MetaData.__FIELDS__):
            return u''
        else:
            try:
                return self.__dict__[key]
            except KeyError:
                raise AttributeError(key)

    def __delattr__(self, key):
        if (key == 'track_number'):
            track_number = self.get('TRACKNUMBER', [u''])[0]
            if (re.match(r'^\d+$', track_number)):
                del (self['TRACKNUMBER'])
            elif (re.match('^\d+/(\d+)$', track_number)):
                self['TRACKNUMBER'] = u"0/%s" % (re.match(
                    '^\d+/(\d+)$', track_number).group(1))
        elif (key == 'track_total'):
            track_number = self.get('TRACKNUMBER', [u''])[0]
            if (re.match('^(\d+)/\d+$', track_number)):
                self['TRACKNUMBER'] = u"%s" % (re.match(
                    '^(\d+)/\d+$', track_number).group(1))
            if ('TRACKTOTAL' in self):
                del (self['TRACKTOTAL'])
        elif (key == 'album_number'):
            album_number = self.get('DISCNUMBER', [u''])[0]
            if (re.match(r'^\d+$', album_number)):
                del (self['DISCNUMBER'])
            elif (re.match('^\d+/(\d+)$', album_number)):
                self['DISCNUMBER'] = u"0/%s" % (re.match(
                    '^\d+/(\d+)$', album_number).group(1))
        elif (key == 'album_total'):
            album_number = self.get('DISCNUMBER', [u''])[0]
            if (re.match('^(\d+)/\d+$', album_number)):
                self['DISCNUMBER'] = u"%s" % (re.match('^(\d+)/\d+$',
                                                       album_number).group(1))
            if ('DISCTOTAL' in self):
                del (self['DISCTOTAL'])
        elif (key in self.ATTRIBUTE_MAP):
            if (self.ATTRIBUTE_MAP[key] in self):
                del (self[self.ATTRIBUTE_MAP[key]])
        elif (key in MetaData.__FIELDS__):
            pass
        else:
            try:
                del (self.__dict__[key])
            except KeyError:
                raise AttributeError(key)

    @classmethod
    def supports_images(cls):
        """Returns False."""

        #There's actually a (proposed?) standard to add embedded covers
        #to Vorbis Comments by base64 encoding them.
        #This strikes me as messy and convoluted.
        #In addition, I'd have to perform a special case of
        #image extraction and re-insertion whenever converting
        #to FlacMetaData.  The whole thought gives me a headache.

        return False

    def images(self):
        """Returns an empty list of Image objects."""

        return list()

    #if an attribute is updated (e.g. self.track_name)
    #make sure to update the corresponding dict pair
    def __setattr__(self, key, value):
        if (key in self.ATTRIBUTE_MAP):
            if (key not in MetaData.__INTEGER_FIELDS__):
                self[self.ATTRIBUTE_MAP[key]] = [value]
            else:
                self[self.ATTRIBUTE_MAP[key]] = [unicode(value)]
        else:
            self.__dict__[key] = value

    @classmethod
    def converted(cls, metadata):
        """Converts a MetaData object to a VorbisComment object."""

        if ((metadata is None) or (isinstance(metadata, VorbisComment))):
            return metadata
        elif (metadata.__class__.__name__ == 'FlacMetaData'):
            return cls(vorbis_data=dict(metadata.vorbis_comment.items()),
                       vendor_string=metadata.vorbis_comment.vendor_string)
        else:
            values = {}
            for key in cls.ATTRIBUTE_MAP.keys():
                if (key in cls.__INTEGER_FIELDS__):
                    if (getattr(metadata, key) != 0):
                        values[cls.ATTRIBUTE_MAP[key]] = \
                            [unicode(getattr(metadata, key))]
                elif (getattr(metadata, key) != u""):
                    values[cls.ATTRIBUTE_MAP[key]] = \
                        [unicode(getattr(metadata, key))]

            return VorbisComment(values)

    def merge(self, metadata):
        """Updates any currently empty entries from metadata's values."""

        metadata = self.__class__.converted(metadata)
        if (metadata is None):
            return

        for (key, values) in metadata.items():
            if ((len(values) > 0) and (len(self.get(key, [])) == 0)):
                self[key] = values

    def __comment_name__(self):
        return u'Vorbis'

    #takes two (key,value) vorbiscomment pairs
    #returns cmp on the weighted set of them
    #(title first, then artist, album, tracknumber, ... , replaygain)
    @classmethod
    def __by_pair__(cls, pair1, pair2):
        KEY_MAP = {
            "TITLE": 1,
            "ALBUM": 2,
            "TRACKNUMBER": 3,
            "TRACKTOTAL": 4,
            "DISCNUMBER": 5,
            "DISCTOTAL": 6,
            "ARTIST": 7,
            "PERFORMER": 8,
            "COMPOSER": 9,
            "CONDUCTOR": 10,
            "CATALOG": 11,
            "PUBLISHER": 12,
            "ISRC": 13,
            "SOURCE MEDIUM": 14,
            #"YEAR": 15,
            "DATE": 16,
            "COPYRIGHT": 17,
            "REPLAYGAIN_ALBUM_GAIN": 19,
            "REPLAYGAIN_ALBUM_PEAK": 19,
            "REPLAYGAIN_TRACK_GAIN": 19,
            "REPLAYGAIN_TRACK_PEAK": 19,
            "REPLAYGAIN_REFERENCE_LOUDNESS": 20
        }
        return cmp(
            (KEY_MAP.get(pair1[0].upper(), 18), pair1[0].upper(), pair1[1]),
            (KEY_MAP.get(pair2[0].upper(), 18), pair2[0].upper(), pair2[1]))

    def __comment_pairs__(self):
        pairs = []
        for (key, values) in self.items():
            for value in values:
                pairs.append((key, value))

        pairs.sort(VorbisComment.__by_pair__)
        return pairs

    def build(self):
        """Returns this VorbisComment as a binary string."""

        comment = Con.Container(vendor_string=self.vendor_string,
                                framing=1,
                                value=[])

        for (key, values) in self.items():
            for value in values:
                if ((value != u"")
                        and not ((key in ("TRACKNUMBER", "TRACKTOTAL",
                                          "DISCNUMBER", "DISCTOTAL")) and
                                 (value == u"0"))):
                    comment.value.append("%s=%s" %
                                         (key, value.encode('utf-8')))
        return self.VORBIS_COMMENT.build(comment)