Beispiel #1
0
    def save(self):
        metadata = Metadata()
        for i in range(self.ui.tags.topLevelItemCount()):
            item = self.ui.tags.topLevelItem(i)
            name = unicode(item.data(0, QtCore.Qt.UserRole).toString())
            if name in self.changed:
                value = unicode(item.text(1))
                metadata.add(name, value)

        # Rate the different tracks
        if self.config.setting['enable_ratings']:
            rating = self.ui.rating.getRating()
            metadata['~rating'] = unicode(rating)
            tracks = set([
                file.parent for file in self.files
                if isinstance(file.parent, Track)
            ])
            ratings = {}
            for track in tracks:
                ratings[('recording', track.id)] = rating
                track.metadata['~rating'] = rating
            if self.config.setting['submit_ratings']:
                self.tagger.xmlws.submit_ratings(ratings, None)

        for file in self.files:
            for name in self.changed:
                try:
                    del file.metadata[name]
                except KeyError:
                    pass
            file.metadata.update(metadata)
            file.update()
Beispiel #2
0
    def save(self):
        metadata = Metadata()
        for i in range(self.ui.tags.topLevelItemCount()):
            item = self.ui.tags.topLevelItem(i)
            name = unicode(item.data(0, QtCore.Qt.UserRole).toString())
            if name in self.changed:
                value = unicode(item.text(1))
                metadata.add(name, value)

        # Rate the different tracks
        if self.config.setting['enable_ratings']:
            rating = self.ui.rating.getRating()
            metadata['~rating'] = unicode(rating)
            tracks = set([file.parent for file in self.files
                          if isinstance(file.parent, Track)])
            ratings = {}
            for track in tracks:
                ratings[('recording', track.id)] = rating
                track.metadata['~rating'] = rating
            if self.config.setting['submit_ratings']:
                self.tagger.xmlws.submit_ratings(ratings, None)

        for file in self.files:
            for name in self.changed:
                try:
                    del file.metadata[name]
                except KeyError:
                    pass
            file.metadata.update(metadata)
            file.update()
Beispiel #3
0
 def _load(self, filename):
     self.log.debug("Loading file %r", filename)
     file = self._File(encode_filename(filename))
     file.tags = file.tags or {}
     metadata = Metadata()
     for origname, values in file.tags.items():
         for value in values:
             name = origname
             if name == "date" or name == "originaldate":
                 # YYYY-00-00 => YYYY
                 value = sanitize_date(value)
             elif name == 'performer' or name == 'comment':
                 # transform "performer=Joe Barr (Piano)" to "performer:Piano=Joe Barr"
                 name += ':'
                 if value.endswith(')'):
                     start = value.rfind(' (')
                     if start > 0:
                         name += value[start + 2:-1]
                         value = value[:start]
             elif name.startswith('rating'):
                 try:
                     name, email = name.split(':', 1)
                 except ValueError:
                     email = ''
                 if email != self.config.setting['rating_user_email']:
                     continue
                 name = '~rating'
                 value = unicode(
                     int(
                         round(
                             (float(value) *
                              (self.config.setting['rating_steps'] - 1)))))
             elif name == "fingerprint" and value.startswith(
                     "MusicMagic Fingerprint"):
                 name = "musicip_fingerprint"
                 value = value[22:]
             elif name == "tracktotal" and "totaltracks" not in file.tags:
                 name = "totaltracks"
             elif name == "metadata_block_picture":
                 image = mutagen.flac.Picture(
                     base64.standard_b64decode(value))
                 metadata.add_image(image.mime, image.data)
                 continue
             metadata.add(name, value)
     if self._File == mutagen.flac.FLAC:
         for image in file.pictures:
             metadata.add_image(image.mime, image.data)
     # Read the unofficial COVERART tags, for backward compatibillity only
     if not "metadata_block_picture" in file.tags:
         try:
             for index, data in enumerate(file["COVERART"]):
                 metadata.add_image(file["COVERARTMIME"][index],
                                    base64.standard_b64decode(data))
         except KeyError:
             pass
     self._info(metadata, file)
     return metadata
Beispiel #4
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        self.__casemap = {}
        file = self._File(encode_filename(filename))
        metadata = Metadata()
        if file.tags:
            for origname, values in file.tags.items():
                name_lower = origname.lower()
                if (values.kind == mutagen.apev2.BINARY
                        and name_lower.startswith("cover art")):
                    if b'\0' in values.value:
                        descr, data = values.value.split(b'\0', 1)
                        try:
                            coverartimage = TagCoverArtImage(
                                file=filename,
                                tag=name_lower,
                                data=data,
                            )
                        except CoverArtImageError as e:
                            log.error('Cannot load image from %r: %s' %
                                      (filename, e))
                        else:
                            metadata.images.append(coverartimage)

                # skip EXTERNAL and BINARY values
                if values.kind != mutagen.apev2.TEXT:
                    continue
                for value in values:
                    name = name_lower
                    if name == "year":
                        name = "date"
                        value = sanitize_date(value)
                    elif name == "track":
                        name = "tracknumber"
                        track = value.split("/")
                        if len(track) > 1:
                            metadata["totaltracks"] = track[1]
                            value = track[0]
                    elif name == "disc":
                        name = "discnumber"
                        disc = value.split("/")
                        if len(disc) > 1:
                            metadata["totaldiscs"] = disc[1]
                            value = disc[0]
                    elif name == 'performer' or name == 'comment':
                        name = name + ':'
                        if value.endswith(')'):
                            start = value.rfind(' (')
                            if start > 0:
                                name += value[start + 2:-1]
                                value = value[:start]
                    elif name in self.__rtranslate:
                        name = self.__rtranslate[name]
                    self.__casemap[name] = origname
                    metadata.add(name, value)
        self._info(metadata, file)
        return metadata
Beispiel #5
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename))
        metadata = Metadata()
        if file.tags:
            for origname, values in file.tags.items():
                if origname.lower().startswith("cover art") and values.kind == mutagen.apev2.BINARY:
                    if '\0' in values.value:
                        descr, data = values.value.split('\0', 1)
                        try:
                            coverartimage = TagCoverArtImage(
                                file=filename,
                                tag=origname,
                                data=data,
                            )
                        except CoverArtImageError as e:
                            log.error('Cannot load image from %r: %s' %
                                      (filename, e))
                        else:
                            metadata.append_image(coverartimage)

                # skip EXTERNAL and BINARY values
                if values.kind != mutagen.apev2.TEXT:
                    continue
                for value in values:
                    name = origname
                    if name == "Year":
                        name = "date"
                        value = sanitize_date(value)
                    elif name == "Track":
                        name = "tracknumber"
                        track = value.split("/")
                        if len(track) > 1:
                            metadata["totaltracks"] = track[1]
                            value = track[0]
                    elif name == "Disc":
                        name = "discnumber"
                        disc = value.split("/")
                        if len(disc) > 1:
                            metadata["totaldiscs"] = disc[1]
                            value = disc[0]
                    elif name == 'Performer' or name == 'Comment':
                        name = name.lower() + ':'
                        if value.endswith(')'):
                            start = value.rfind(' (')
                            if start > 0:
                                name += value[start + 2:-1]
                                value = value[:start]
                    elif name in self.__translate:
                        name = self.__translate[name]
                    else:
                        name = name.lower()
                    metadata.add(name, value)
        self._info(metadata, file)
        return metadata
Beispiel #6
0
 def _load(self, filename):
     self.log.debug("Loading file %r", filename)
     file = self._File(encode_filename(filename))
     file.tags = file.tags or {}
     metadata = Metadata()
     for origname, values in file.tags.items():
         for value in values:
             name = origname
             if name == "date" or name == "originaldate":
                 # YYYY-00-00 => YYYY
                 value = sanitize_date(value)
             elif name == 'performer' or name == 'comment':
                 # transform "performer=Joe Barr (Piano)" to "performer:Piano=Joe Barr"
                 name += ':'
                 if value.endswith(')'):
                     start = value.rfind(' (')
                     if start > 0:
                         name += value[start + 2:-1]
                         value = value[:start]
             elif name.startswith('rating'):
                 try: name, email = name.split(':', 1)
                 except ValueError: email = ''
                 if email != self.config.setting['rating_user_email']:
                     continue
                 name = '~rating'
                 value = unicode(int(round((float(value) * (self.config.setting['rating_steps'] - 1)))))
             elif name == "fingerprint" and value.startswith("MusicMagic Fingerprint"):
                 name = "musicip_fingerprint"
                 value = value[22:]
             elif name == "tracktotal":
                 if "totaltracks" in file.tags:
                     continue
                 name = "totaltracks"
             elif name == "disctotal":
                 if "totaldiscs" in file.tags:
                     continue
                 name = "totaldiscs"
             elif name == "metadata_block_picture":
                 image = mutagen.flac.Picture(base64.standard_b64decode(value))
                 metadata.add_image(image.mime, image.data)
                 continue
             metadata.add(name, value)
     if self._File == mutagen.flac.FLAC:
         for image in file.pictures:
             metadata.add_image(image.mime, image.data)
     # Read the unofficial COVERART tags, for backward compatibillity only
     if not "metadata_block_picture" in file.tags:
         try:
             for index, data in enumerate(file["COVERART"]):
                 metadata.add_image(file["COVERARTMIME"][index], base64.standard_b64decode(data))
         except KeyError:
             pass
     self._info(metadata, file)
     return metadata
Beispiel #7
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = MP4(encode_filename(filename))
        tags = file.tags
        if tags is None:
            file.add_tags()

        metadata = Metadata()
        for name, values in tags.items():
            if name in self.__text_tags:
                for value in values:
                    metadata.add(self.__text_tags[name], value)
            elif name in self.__bool_tags:
                metadata.add(self.__bool_tags[name], values and '1' or '0')
            elif name in self.__int_tags:
                for value in values:
                    metadata.add(self.__int_tags[name], unicode(value))
            elif name in self.__freeform_tags:
                for value in values:
                    value = value.strip("\x00").decode("utf-8", "replace")
                    metadata.add(self.__freeform_tags[name], value)
            elif name == "----:com.apple.iTunes:fingerprint":
                for value in values:
                    value = value.strip("\x00").decode("utf-8", "replace")
                    if value.startswith("MusicMagic Fingerprint"):
                        metadata.add("musicip_fingerprint", value[22:])
            elif name == "trkn":
                metadata["tracknumber"] = str(values[0][0])
                metadata["totaltracks"] = str(values[0][1])
            elif name == "disk":
                metadata["discnumber"] = str(values[0][0])
                metadata["totaldiscs"] = str(values[0][1])
            elif name == "covr":
                for value in values:
                    if value.imageformat not in (value.FORMAT_JPEG,
                                                 value.FORMAT_PNG):
                        continue
                    try:
                        coverartimage = TagCoverArtImage(
                            file=filename,
                            tag=name,
                            data=value,
                        )
                    except CoverArtImageError as e:
                        log.error('Cannot load image from %r: %s' %
                                  (filename, e))
                    else:
                        metadata.append_image(coverartimage)

        self._info(metadata, file)
        return metadata
Beispiel #8
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = MP4(encode_filename(filename))
        tags = file.tags
        if tags is None:
            file.add_tags()

        metadata = Metadata()
        for name, values in tags.items():
            if name in self.__text_tags:
                for value in values:
                    metadata.add(self.__text_tags[name], value)
            elif name in self.__bool_tags:
                metadata.add(self.__bool_tags[name], values and '1' or '0')
            elif name in self.__int_tags:
                for value in values:
                    metadata.add(self.__int_tags[name], string_(value))
            elif name in self.__freeform_tags:
                for value in values:
                    value = value.decode("utf-8", "replace").strip("\x00")
                    metadata.add(self.__freeform_tags[name], value)
            elif name == "----:com.apple.iTunes:fingerprint":
                for value in values:
                    value = value.decode("utf-8", "replace").strip("\x00")
                    if value.startswith("MusicMagic Fingerprint"):
                        metadata.add("musicip_fingerprint", value[22:])
            elif name == "trkn":
                metadata["tracknumber"] = string_(values[0][0])
                metadata["totaltracks"] = string_(values[0][1])
            elif name == "disk":
                metadata["discnumber"] = string_(values[0][0])
                metadata["totaldiscs"] = string_(values[0][1])
            elif name == "covr":
                for value in values:
                    if value.imageformat not in (value.FORMAT_JPEG,
                                                 value.FORMAT_PNG):
                        continue
                    try:
                        coverartimage = TagCoverArtImage(
                            file=filename,
                            tag=name,
                            data=value,
                        )
                    except CoverArtImageError as e:
                        log.error('Cannot load image from %r: %s' %
                                  (filename, e))
                    else:
                        metadata.append_image(coverartimage)

        self._info(metadata, file)
        return metadata
Beispiel #9
0
 def _load(self, lines):
     metadata = Metadata()
     metadata.copy(self.metadata)
     metadata.add("album", self.metadata.get("title"))
     del metadata["title"]
     for line in lines:
         splitline = line.split()
         # linetokv?
         key = splitline[0]
         value = " ".join(splitline[1:])
         if key == "INDEX":
             index, value = value.split()
             self.indexes[index] = value
         elif not key == "TRACK":
             self.kv_to_metadata(key, value, metadata)
     return metadata
Beispiel #10
0
 def _load(self, filename):
     self.log.debug("Loading file %r", filename)
     file = self._File(encode_filename(filename))
     metadata = Metadata()
     if file.tags:
         for origname, values in file.tags.items():
             if origname.lower().startswith(
                     "cover art") and values.kind == mutagen.apev2.BINARY:
                 if '\0' in values.value:
                     descr, data = values.value.split('\0', 1)
                     mime = mimetype.get_from_data(data, descr,
                                                   'image/jpeg')
                     metadata.add_image(mime, data)
             # skip EXTERNAL and BINARY values
             if values.kind != mutagen.apev2.TEXT:
                 continue
             for value in values:
                 name = origname
                 if name == "Year":
                     name = "date"
                     value = sanitize_date(value)
                 elif name == "Track":
                     name = "tracknumber"
                     track = value.split("/")
                     if len(track) > 1:
                         metadata["totaltracks"] = track[1]
                         value = track[0]
                 elif name == "Disc":
                     name = "discnumber"
                     disc = value.split("/")
                     if len(disc) > 1:
                         metadata["totaldiscs"] = disc[1]
                         value = disc[0]
                 elif name == 'Performer' or name == 'Comment':
                     name = name.lower() + ':'
                     if value.endswith(')'):
                         start = value.rfind(' (')
                         if start > 0:
                             name += value[start + 2:-1]
                             value = value[:start]
                 elif name in self.__translate:
                     name = self.__translate[name]
                 else:
                     name = name.lower()
                 metadata.add(name, value)
     self._info(metadata, file)
     return metadata
Beispiel #11
0
    def _load(self, filename):
        self.log.debug("Loading file %r", filename)
        file = MP4(encode_filename(filename))
        if file.tags is None:
            file.add_tags()

        metadata = Metadata()
        for name, values in file.tags.items():
            if name in self.__text_tags:
                for value in values:
                    metadata.add(self.__text_tags[name], value)
            elif name in self.__bool_tags:
                metadata.add(self.__bool_tags[name], values and "1" or "0")
            elif name in self.__int_tags:
                for value in values:
                    metadata.add(self.__int_tags[name], unicode(value))
            elif name in self.__freeform_tags:
                for value in values:
                    value = value.strip("\x00").decode("utf-8", "replace")
                    metadata.add(self.__freeform_tags[name], value)
            elif name == "----:com.apple.iTunes:fingerprint":
                for value in values:
                    value = value.strip("\x00").decode("utf-8", "replace")
                    if value.startswith("MusicMagic Fingerprint"):
                        metadata.add("musicip_fingerprint", value[22:])
            elif name == "trkn":
                metadata["tracknumber"] = str(values[0][0])
                metadata["totaltracks"] = str(values[0][1])
            elif name == "disk":
                metadata["discnumber"] = str(values[0][0])
                metadata["totaldiscs"] = str(values[0][1])
            elif name == "covr":
                for value in values:
                    value = MP4Cover(value)
                    if value.imageformat == value.FORMAT_JPEG:
                        metadata.add_image("image/jpeg", value)
                    elif value.imageformat == value.FORMAT_PNG:
                        metadata.add_image("image/png", value)

        self._info(metadata, file)
        return metadata
Beispiel #12
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = MP4(encode_filename(filename))
        if file.tags is None:
            file.add_tags()

        metadata = Metadata()
        for name, values in file.tags.items():
            if name in self.__text_tags:
                for value in values:
                    metadata.add(self.__text_tags[name], value)
            elif name in self.__bool_tags:
                metadata.add(self.__bool_tags[name], values and '1' or '0')
            elif name in self.__int_tags:
                for value in values:
                    metadata.add(self.__int_tags[name], unicode(value))
            elif name in self.__freeform_tags:
                for value in values:
                    value = value.strip("\x00").decode("utf-8", "replace")
                    metadata.add(self.__freeform_tags[name], value)
            elif name == "----:com.apple.iTunes:fingerprint":
                for value in values:
                    value = value.strip("\x00").decode("utf-8", "replace")
                    if value.startswith("MusicMagic Fingerprint"):
                        metadata.add("musicip_fingerprint", value[22:])
            elif name == "trkn":
                metadata["tracknumber"] = str(values[0][0])
                metadata["totaltracks"] = str(values[0][1])
            elif name == "disk":
                metadata["discnumber"] = str(values[0][0])
                metadata["totaldiscs"] = str(values[0][1])
            elif name == "covr":
                for value in values:
                    if value.imageformat == value.FORMAT_JPEG:
                        metadata.add_image("image/jpeg", value)
                    elif value.imageformat == value.FORMAT_PNG:
                        metadata.add_image("image/png", value)

        self._info(metadata, file)
        return metadata
Beispiel #13
0
    def _finalize_loading(self, error):
        if error:
            self.metadata.clear()
            self.status = _("[could not load album %s]") % self.id
            del self._new_metadata
            del self._new_tracks
            self.update()
            return

        if self._requests > 0:
            return

        if not self._tracks_loaded:
            artists = set()
            all_media = []
            absolutetracknumber = 0

            va = self._new_metadata['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID

            djmix_ars = {}
            if hasattr(self._new_metadata, "_djmix_ars"):
                djmix_ars = self._new_metadata._djmix_ars

            for medium_node in self._release_node['media']:
                mm = Metadata()
                mm.copy(self._new_metadata)
                medium_to_metadata(medium_node, mm)
                discpregap = False
                format = medium_node.get('format')
                if format:
                    all_media.append(format)

                for dj in djmix_ars.get(mm["discnumber"], []):
                    mm.add("djmixer", dj)

                if 'discs' in medium_node:
                    discids = [disc.get('id') for disc in medium_node['discs']]
                    mm['~musicbrainz_discids'] = discids
                    mm['musicbrainz_discid'] = list(self._discids.intersection(discids))

                if "pregap" in medium_node:
                    discpregap = True
                    absolutetracknumber += 1
                    track = self._finalize_loading_track(medium_node['pregap'], mm, artists, va, absolutetracknumber, discpregap)
                    track.metadata['~pregap'] = "1"

                track_count = medium_node['track-count']
                if track_count:
                    tracklist_node = medium_node['tracks']
                    for track_node in tracklist_node:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)

                if "data-tracks" in medium_node:
                    for track_node in medium_node['data-tracks']:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)
                        track.metadata['~datatrack'] = "1"

            totalalbumtracks = absolutetracknumber
            self._new_metadata['~totalalbumtracks'] = totalalbumtracks
            # Generate a list of unique media, but keep order of first appearance
            self._new_metadata['media'] = " / ".join(list(OrderedDict.fromkeys(all_media)))

            for track in self._new_tracks:
                track.metadata["~totalalbumtracks"] = totalalbumtracks
                if len(artists) > 1:
                    track.metadata["~multiartist"] = "1"
            del self._release_node
            del self._release_artist_nodes
            self._tracks_loaded = True

        if not self._requests:
            self.enable_update_metadata_images(False)
            # Prepare parser for user's script
            for s_name, s_text in enabled_tagger_scripts_texts():
                parser = ScriptParser()
                for track in self._new_tracks:
                    # Run tagger script for each track
                    try:
                        parser.eval(s_text, track.metadata)
                    except ScriptError:
                        log.exception("Failed to run tagger script %s on track", s_name)
                    track.metadata.strip_whitespace()
                # Run tagger script for the album itself
                try:
                    parser.eval(s_text, self._new_metadata)
                except ScriptError:
                    log.exception("Failed to run tagger script %s on album", s_name)
                self._new_metadata.strip_whitespace()

            for track in self.tracks:
                track.metadata_images_changed.connect(self.update_metadata_images)
                for file in list(track.linked_files):
                    file.move(self.unmatched_files)
            self.metadata = self._new_metadata
            self.tracks = self._new_tracks
            del self._new_metadata
            del self._new_tracks
            self.loaded = True
            self.status = None
            self.match_files(self.unmatched_files.files)
            self.enable_update_metadata_images(True)
            self.update()
            self.tagger.window.set_statusbar_message(
                N_('Album %(id)s loaded: %(artist)s - %(album)s'),
                {
                    'id': self.id,
                    'artist': self.metadata['albumartist'],
                    'album': self.metadata['album']
                },
                timeout=3000
            )
            for func in self._after_load_callbacks:
                func()
            self._after_load_callbacks = []
Beispiel #14
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = self._get_file(encode_filename(filename))
        tags = file.tags or {}
        # upgrade custom 2.3 frames to 2.4
        for old, new in self.__upgrade.items():
            if old in tags and new not in tags:
                f = tags.pop(old)
                tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text))
        metadata = Metadata()
        for frame in tags.values():
            frameid = frame.FrameID
            if frameid in self.__translate:
                name = self.__translate[frameid]
                if frameid.startswith('T') or frameid == 'GRP1':
                    for text in frame.text:
                        if text:
                            metadata.add(name, string_(text))
                elif frameid == 'COMM':
                    for text in frame.text:
                        if text:
                            metadata.add('%s:%s' % (name, frame.desc),
                                         string_(text))
                else:
                    metadata.add(name, string_(frame))
            elif frameid == 'TIT1':
                itunes_compatible = config.setting[
                    'itunes_compatible_grouping']
                name = 'work' if itunes_compatible else 'grouping'
                for text in frame.text:
                    if text:
                        metadata.add(name, string_(text))
            elif frameid == "TMCL":
                for role, name in frame.people:
                    if role or name:
                        metadata.add('performer:%s' % role, name)
            elif frameid == "TIPL":
                # If file is ID3v2.3, TIPL tag could contain TMCL
                # so we will test for TMCL values and add to TIPL if not TMCL
                for role, name in frame.people:
                    if role in self._tipl_roles and name:
                        metadata.add(self._tipl_roles[role], name)
                    else:
                        metadata.add('performer:%s' % role, name)
            elif frameid == 'TXXX':
                name = frame.desc
                if name in self.__translate_freetext:
                    name = self.__translate_freetext[name]
                elif ((name in self.__rtranslate) !=
                      (name in self.__rtranslate_freetext)):
                    # If the desc of a TXXX frame conflicts with the name of a
                    # Picard tag, load it into ~id3:TXXX:desc rather than desc.
                    #
                    # This basically performs an XOR, making sure that 'name'
                    # is in __rtranslate or __rtranslate_freetext, but not
                    # both. (Being in both implies we support reading it both
                    # ways.) Currently, the only tag in both is license.
                    name = '~id3:TXXX:' + name
                for text in frame.text:
                    metadata.add(name, string_(text))
            elif frameid == 'USLT':
                name = 'lyrics'
                if frame.desc:
                    name += ':%s' % frame.desc
                metadata.add(name, string_(frame.text))
            elif frameid == 'UFID' and frame.owner == 'http://musicbrainz.org':
                metadata['musicbrainz_recordingid'] = frame.data.decode(
                    'ascii', 'ignore')
            elif frameid in self.__tag_re_parse.keys():
                m = self.__tag_re_parse[frameid].search(frame.text[0])
                if m:
                    for name, value in m.groupdict().items():
                        if value is not None:
                            metadata[name] = value
                else:
                    log.error("Invalid %s value '%s' dropped in %r", frameid,
                              frame.text[0], filename)
            elif frameid == 'APIC':
                try:
                    coverartimage = TagCoverArtImage(
                        file=filename,
                        tag=frameid,
                        types=types_from_id3(frame.type),
                        comment=frame.desc,
                        support_types=True,
                        data=frame.data,
                    )
                except CoverArtImageError as e:
                    log.error('Cannot load image from %r: %s' % (filename, e))
                else:
                    metadata.append_image(coverartimage)
            elif frameid == 'POPM':
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == config.setting['rating_user_email']:
                    rating = string_(
                        int(
                            round(frame.rating / 255.0 *
                                  (config.setting['rating_steps'] - 1))))
                    metadata.add('~rating', rating)

        if 'date' in metadata:
            sanitized = sanitize_date(metadata.getall('date')[0])
            if sanitized:
                metadata['date'] = sanitized

        self._info(metadata, file)
        return metadata
Beispiel #15
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        self.__casemap = {}
        file = MP4(encode_filename(filename))
        tags = file.tags or {}
        metadata = Metadata()
        for name, values in tags.items():
            name_lower = name.lower()
            if name in self.__text_tags:
                for value in values:
                    metadata.add(self.__text_tags[name], value)
            elif name in self.__bool_tags:
                metadata.add(self.__bool_tags[name], values and '1' or '0')
            elif name in self.__int_tags:
                for value in values:
                    metadata.add(self.__int_tags[name], value)
            elif name in self.__freeform_tags:
                tag_name = self.__freeform_tags[name]
                _add_text_values_to_metadata(metadata, tag_name, values)
            elif name_lower in self.__freeform_tags_ci:
                tag_name = self.__freeform_tags_ci[name_lower]
                self.__casemap[tag_name] = name
                _add_text_values_to_metadata(metadata, tag_name, values)
            elif name == "----:com.apple.iTunes:fingerprint":
                for value in values:
                    value = value.decode("utf-8", "replace").strip("\x00")
                    if value.startswith("MusicMagic Fingerprint"):
                        metadata.add("musicip_fingerprint", value[22:])
            elif name == "trkn":
                try:
                    metadata["tracknumber"] = values[0][0]
                    metadata["totaltracks"] = values[0][1]
                except IndexError:
                    log.debug('trkn is invalid, ignoring')
            elif name == "disk":
                try:
                    metadata["discnumber"] = values[0][0]
                    metadata["totaldiscs"] = values[0][1]
                except IndexError:
                    log.debug('disk is invalid, ignoring')
            elif name == "covr":
                for value in values:
                    if value.imageformat not in (value.FORMAT_JPEG,
                                                 value.FORMAT_PNG):
                        continue
                    try:
                        coverartimage = TagCoverArtImage(
                            file=filename,
                            tag=name,
                            data=value,
                        )
                    except CoverArtImageError as e:
                        log.error('Cannot load image from %r: %s' %
                                  (filename, e))
                    else:
                        metadata.images.append(coverartimage)
            # Read other freeform tags always case insensitive
            elif name.startswith('----:com.apple.iTunes:'):
                tag_name = name_lower[22:]
                self.__casemap[tag_name] = name[22:]
                if (name not in self.__r_text_tags
                        and name not in self.__r_bool_tags
                        and name not in self.__r_int_tags
                        and name not in self.__r_freeform_tags
                        and name_lower not in self.__r_freeform_tags_ci
                        and name not in self.__other_supported_tags):
                    _add_text_values_to_metadata(metadata, tag_name, values)

        self._info(metadata, file)
        return metadata
Beispiel #16
0
 def _load(self, filename):
     log.debug("Loading file %r", filename)
     file = self._File(encode_filename(filename))
     file.tags = file.tags or {}
     metadata = Metadata()
     for origname, values in file.tags.items():
         for value in values:
             name = origname
             if name == "date" or name == "originaldate":
                 # YYYY-00-00 => YYYY
                 value = sanitize_date(value)
             elif name == "performer" or name == "comment":
                 # transform "performer=Joe Barr (Piano)" to "performer:Piano=Joe Barr"
                 name += ":"
                 if value.endswith(")"):
                     start = len(value) - 2
                     count = 1
                     while count > 0 and start > 0:
                         if value[start] == ")":
                             count += 1
                         elif value[start] == "(":
                             count -= 1
                         start -= 1
                     if start > 0:
                         name += value[start + 2 : -1]
                         value = value[:start]
             elif name.startswith("rating"):
                 try:
                     name, email = name.split(":", 1)
                 except ValueError:
                     email = ""
                 if email != config.setting["rating_user_email"]:
                     continue
                 name = "~rating"
                 value = unicode(int(round((float(value) * (config.setting["rating_steps"] - 1)))))
             elif name == "fingerprint" and value.startswith("MusicMagic Fingerprint"):
                 name = "musicip_fingerprint"
                 value = value[22:]
             elif name == "tracktotal":
                 if "totaltracks" in file.tags:
                     continue
                 name = "totaltracks"
             elif name == "disctotal":
                 if "totaldiscs" in file.tags:
                     continue
                 name = "totaldiscs"
             elif name == "metadata_block_picture":
                 image = mutagen.flac.Picture(base64.standard_b64decode(value))
                 imagetype = ID3_REVERSE_IMAGE_TYPE_MAP.get(image.type, "other")
                 metadata.add_image(image.mime, image.data, description=image.desc, type_=imagetype)
                 continue
             metadata.add(name, value)
     if self._File == mutagen.flac.FLAC:
         for image in file.pictures:
             imagetype = ID3_REVERSE_IMAGE_TYPE_MAP.get(image.type, "other")
             metadata.add_image(image.mime, image.data, description=image.desc, type_=imagetype)
     # Read the unofficial COVERART tags, for backward compatibillity only
     if not "metadata_block_picture" in file.tags:
         try:
             for index, data in enumerate(file["COVERART"]):
                 metadata.add_image(file["COVERARTMIME"][index], base64.standard_b64decode(data))
         except KeyError:
             pass
     self._info(metadata, file)
     return metadata
Beispiel #17
0
    def _finalize_loading(self, error):
        if error:
            self.metadata.clear()
            self.status = _("[could not load album %s]") % self.id
            del self._new_metadata
            del self._new_tracks
            self.update()
            return

        if self._requests > 0:
            return

        if not self._tracks_loaded:
            artists = set()
            totalalbumtracks = 0
            absolutetracknumber = 0
            va = self._new_metadata[
                'musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID

            djmix_ars = {}
            if hasattr(self._new_metadata, "_djmix_ars"):
                djmix_ars = self._new_metadata._djmix_ars

            for medium_node in self._release_node.medium_list[0].medium:
                mm = Metadata()
                mm.copy(self._new_metadata)
                medium_to_metadata(medium_node, mm)
                discpregap = False

                for dj in djmix_ars.get(mm["discnumber"], []):
                    mm.add("djmixer", dj)

                if "pregap" in medium_node.children:
                    discpregap = True
                    absolutetracknumber += 1
                    track = self._finalize_loading_track(
                        medium_node.pregap[0], mm, artists, va,
                        absolutetracknumber, discpregap)
                    track.metadata['~pregap'] = "1"

                for track_node in medium_node.track_list[0].track:
                    absolutetracknumber += 1
                    track = self._finalize_loading_track(
                        track_node, mm, artists, va, absolutetracknumber,
                        discpregap)

                if "data_track_list" in medium_node.children:
                    for track_node in medium_node.data_track_list[0].track:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(
                            track_node, mm, artists, va, absolutetracknumber,
                            discpregap)
                        track.metadata['~datatrack'] = "1"

            totalalbumtracks = str(absolutetracknumber)

            for track in self._new_tracks:
                track.metadata["~totalalbumtracks"] = totalalbumtracks
                if len(artists) > 1:
                    track.metadata["~multiartist"] = "1"
            del self._release_node
            self._tracks_loaded = True

        if not self._requests:
            # Prepare parser for user's script
            if config.setting["enable_tagger_script"]:
                script = config.setting["tagger_script"]
                if script:
                    parser = ScriptParser()
                    for track in self._new_tracks:
                        # Run tagger script for each track
                        try:
                            parser.eval(script, track.metadata)
                        except:
                            self.error_append(traceback.format_exc())
                        # Strip leading/trailing whitespace
                        track.metadata.strip_whitespace()
                    # Run tagger script for the album itself
                    try:
                        parser.eval(script, self._new_metadata)
                    except:
                        self.error_append(traceback.format_exc())
                    self._new_metadata.strip_whitespace()

            for track in self.tracks:
                for file in list(track.linked_files):
                    file.move(self.unmatched_files)
            self.metadata = self._new_metadata
            self.tracks = self._new_tracks
            del self._new_metadata
            del self._new_tracks
            self.loaded = True
            self.status = None
            self.match_files(self.unmatched_files.files)
            self.update()
            self.tagger.window.set_statusbar_message(
                N_('Album %(id)s loaded: %(artist)s - %(album)s'), {
                    'id': self.id,
                    'artist': self.metadata['albumartist'],
                    'album': self.metadata['album']
                },
                timeout=3000)
            for func in self._after_load_callbacks:
                func()
            self._after_load_callbacks = []
Beispiel #18
0
class MetadataTest(unittest.TestCase):

    original = None
    tags = []

    def setUp(self):
        config.setting = settings.copy()
        self.metadata = Metadata()
        self.metadata["single1"] = "single1-value"
        self.metadata.add_unique("single2", "single2-value")
        self.metadata.add_unique("single2", "single2-value")
        self.multi1 = ["multi1-value", "multi1-value"]
        self.metadata.add("multi1", self.multi1[0])
        self.metadata.add("multi1", self.multi1[1])
        self.multi2 = ["multi2-value1", "multi2-value2"]
        self.metadata["multi2"] = self.multi2
        self.multi3 = ["multi3-value1", "multi3-value2"]
        self.metadata.set("multi3", self.multi3)
        self.metadata["~hidden"] = "hidden-value"

    def tearDown(self):
        pass

    def test_metadata_set(self):
        self.assertEqual(["single1-value"], dict.get(self.metadata, "single1"))
        self.assertEqual(["single2-value"], dict.get(self.metadata, "single2"))
        self.assertEqual(self.multi1, dict.get(self.metadata, "multi1"))
        self.assertEqual(self.multi2, dict.get(self.metadata, "multi2"))
        self.assertEqual(self.multi3, dict.get(self.metadata, "multi3"))
        self.assertEqual(["hidden-value"], dict.get(self.metadata, "~hidden"))

    def test_metadata_get(self):
        self.assertEqual("single1-value", self.metadata["single1"])
        self.assertEqual("single1-value", self.metadata.get("single1"))
        self.assertEqual(["single1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1),
                         self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1),
                         self.metadata.get("multi1"))
        self.assertEqual(self.multi1, self.metadata.getall("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))

        self.assertEqual(dict.items(self.metadata), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in dict.items(self.metadata)
                          for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_metadata_delete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

        self.metadata["single2"] = ""
        self.assertNotIn("single2", self.metadata)
        self.assertIn("single2", self.metadata.deleted_tags)

    def test_metadata_update(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.update(self.metadata)
        self.assertIn("old", m)
        self.assertNotIn("single1", m)
        self.assertIn("single1", m.deleted_tags)
        self.assertEqual("single2-value", m["single2"])
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)

        self.metadata["old"] = "old-value"
        for (key, value) in dict.items(self.metadata):
            self.assertIn(key, m)
            self.assertEqual(value, dict.get(m, key))
        for (key, value) in dict.items(m):
            self.assertIn(key, self.metadata)
            self.assertEqual(value, dict.get(self.metadata, key))

    def test_metadata_clear(self):
        self.metadata.clear()
        self.assertEqual(0, len(self.metadata))

    def test_metadata_applyfunc(self):
        func = lambda x: x[1:]
        self.metadata.apply_func(func)

        self.assertEqual("ingle1-value", self.metadata["single1"])
        self.assertEqual("ingle1-value", self.metadata.get("single1"))
        self.assertEqual(["ingle1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)),
                         self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)),
                         self.metadata.get("multi1"))
        self.assertEqual(list(map(func, self.multi1)),
                         self.metadata.getall("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))

        self.assertEqual(dict.items(self.metadata), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in dict.items(self.metadata)
                          for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_length_score(self):
        results = [(20000, 0, 0.333333333333), (20000, 10000, 0.666666666667),
                   (20000, 20000, 1.0), (20000, 30000, 0.666666666667),
                   (20000, 40000, 0.333333333333), (20000, 50000, 0.0)]
        for (a, b, expected) in results:
            actual = Metadata.length_score(a, b)
            self.assertAlmostEqual(expected,
                                   actual,
                                   msg="a={a}, b={b}".format(a=a, b=b))
Beispiel #19
0
class MetadataTest(PicardTestCase):

    original = None
    tags = []

    def setUp(self):
        super().setUp()
        config.setting = settings.copy()
        self.metadata = Metadata()
        self.metadata["single1"] = "single1-value"
        self.metadata.add_unique("single2", "single2-value")
        self.metadata.add_unique("single2", "single2-value")
        self.multi1 = ["multi1-value", "multi1-value"]
        self.metadata.add("multi1", self.multi1[0])
        self.metadata.add("multi1", self.multi1[1])
        self.multi2 = ["multi2-value1", "multi2-value2"]
        self.metadata["multi2"] = self.multi2
        self.multi3 = ["multi3-value1", "multi3-value2"]
        self.metadata.set("multi3", self.multi3)
        self.metadata["~hidden"] = "hidden-value"

        self.metadata_d1 = Metadata({
            'a': 'b',
            'c': 2,
            'd': ['x', 'y'],
            'x': ''
        })
        self.metadata_d2 = Metadata({
            'a': 'b',
            'c': 2,
            'd': ['x', 'y'],
            'x': 'z'
        })
        self.metadata_d3 = Metadata({'c': 3, 'd': ['u', 'w'], 'x': 'p'})

    def tearDown(self):
        pass

    def test_metadata_setitem(self):
        self.assertEqual(["single1-value"], self.metadata.getraw("single1"))
        self.assertEqual(["single2-value"], self.metadata.getraw("single2"))
        self.assertEqual(self.multi1, self.metadata.getraw("multi1"))
        self.assertEqual(self.multi2, self.metadata.getraw("multi2"))
        self.assertEqual(self.multi3, self.metadata.getraw("multi3"))
        self.assertEqual(["hidden-value"], self.metadata.getraw("~hidden"))

    def test_metadata_get(self):
        self.assertEqual("single1-value", self.metadata["single1"])
        self.assertEqual("single1-value", self.metadata.get("single1"))
        self.assertEqual(["single1-value"], self.metadata.getall("single1"))
        self.assertEqual(["single1-value"], self.metadata.getraw("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1),
                         self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1),
                         self.metadata.get("multi1"))
        self.assertEqual(self.multi1, self.metadata.getall("multi1"))
        self.assertEqual(self.multi1, self.metadata.getraw("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))
        self.assertRaises(KeyError, self.metadata.getraw, "nonexistent")

        self.assertEqual(self.metadata._store.items(),
                         self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in self.metadata.rawitems()
                          for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_metadata_delete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

    def test_metadata_implicit_delete(self):
        self.metadata["single2"] = ""
        self.assertNotIn("single2", self.metadata)
        self.assertIn("single2", self.metadata.deleted_tags)

        self.metadata["unknown"] = ""
        self.assertNotIn("unknown", self.metadata)
        self.assertNotIn("unknown", self.metadata.deleted_tags)

    def test_metadata_set_explicit_empty(self):
        self.metadata.delete("single1")
        self.metadata.set("single1", [])
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)
        self.assertEqual([], self.metadata.getall("single1"))

    def test_metadata_undelete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

        self.metadata["single1"] = "value1"
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_copy(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.copy(self.metadata)
        self.assertEqual(self.metadata._store, m._store)
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
        self.assertEqual(self.metadata.length, m.length)
        self.assertEqual(self.metadata.images, m.images)

    def test_metadata_copy_without_images(self):
        m = Metadata()
        m.copy(self.metadata, copy_images=False)
        self.assertEqual(self.metadata._store, m._store)
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
        self.assertEqual(self.metadata.length, m.length)
        self.assertEqual(ImageList(), m.images)

    def test_metadata_update(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.update(self.metadata)
        self.assertIn("old", m)
        self.assertNotIn("single1", m)
        self.assertIn("single1", m.deleted_tags)
        self.assertEqual("single2-value", m["single2"])
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
        self.assertEqual(self.metadata.images, m.images)

        self.metadata["old"] = "old-value"
        self.assertEqual(self.metadata._store, m._store)

    def test_metadata_clear(self):
        self.metadata.clear()
        self.assertEqual(0, len(self.metadata))

    def test_metadata_clear_deleted(self):
        self.metadata.delete("single1")
        self.assertIn("single1", self.metadata.deleted_tags)
        self.metadata.clear_deleted()
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_applyfunc(self):
        def func(x):
            return x[1:]

        self.metadata.apply_func(func)

        self.assertEqual("ingle1-value", self.metadata["single1"])
        self.assertEqual("ingle1-value", self.metadata.get("single1"))
        self.assertEqual(["ingle1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)),
                         self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)),
                         self.metadata.get("multi1"))
        self.assertEqual(list(map(func, self.multi1)),
                         self.metadata.getall("multi1"))

    def test_metadata_applyfunc_preserve_tags(self):
        self.assertTrue(len(PRESERVED_TAGS) > 0)
        m = Metadata()
        m[PRESERVED_TAGS[0]] = 'value1'
        m['not_preserved'] = 'value2'

        def func(x):
            return x[1:]

        m.apply_func(func)

        self.assertEqual("value1", m[PRESERVED_TAGS[0]])
        self.assertEqual("alue2", m['not_preserved'])

    def test_metadata_applyfunc_delete_tags(self):
        def func(x):
            return None

        metadata = Metadata(self.metadata)
        metadata.apply_func(func)
        self.assertEqual(0, len(metadata.rawitems()))
        self.assertEqual(self.metadata.keys(), metadata.deleted_tags)

    def test_length_score(self):
        results = [(20000, 0, 0.333333333333), (20000, 10000, 0.666666666667),
                   (20000, 20000, 1.0), (20000, 30000, 0.666666666667),
                   (20000, 40000, 0.333333333333), (20000, 50000, 0.0)]
        for (a, b, expected) in results:
            actual = Metadata.length_score(a, b)
            self.assertAlmostEqual(expected,
                                   actual,
                                   msg="a={a}, b={b}".format(a=a, b=b))

    def test_compare_is_equal(self):
        m1 = Metadata()
        m1["title"] = "title1"
        m1["tracknumber"] = "2"
        m1.length = 360
        m2 = Metadata()
        m2["title"] = "title1"
        m2["tracknumber"] = "2"
        m2.length = 360
        self.assertEqual(m1.compare(m2), m2.compare(m1))
        self.assertEqual(m1.compare(m2), 1)

    def test_compare_lengths(self):
        m1 = Metadata()
        m1.length = 360
        m2 = Metadata()
        m2.length = 300
        self.assertAlmostEqual(m1.compare(m2), 0.998)

    def test_compare_tracknumber_difference(self):
        m1 = Metadata()
        m1["tracknumber"] = "1"
        m2 = Metadata()
        m2["tracknumber"] = "2"
        self.assertEqual(m1.compare(m2), 0)

    def test_compare_deleted(self):
        m1 = Metadata()
        m1["artist"] = "TheArtist"
        m1["title"] = "title1"
        m2 = Metadata()
        m2["artist"] = "TheArtist"
        m2.delete("title")
        self.assertTrue(m1.compare(m2) < 1)

    def test_strip_whitespace(self):
        m1 = Metadata()
        m1["artist"] = "  TheArtist  "
        m1["title"] = "\t\u00A0  tit le1 \r\n"
        m1["genre"] = " \t"
        m1.strip_whitespace()
        self.assertEqual(m1["artist"], "TheArtist")
        self.assertEqual(m1["title"], "tit le1")

    def test_metadata_mapping_init(self):
        d = {'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': '', 'z': {'u', 'w'}}
        deleted_tags = set('c')
        m = Metadata(d, deleted_tags=deleted_tags, length=1234)
        self.assertTrue('a' in m)
        self.assertEqual(m.getraw('a'), ['b'])
        self.assertEqual(m['d'], MULTI_VALUED_JOINER.join(d['d']))
        self.assertNotIn('c', m)
        self.assertNotIn('length', m)
        self.assertIn('c', m.deleted_tags)
        self.assertEqual(m.length, 1234)

    def test_metadata_mapping_init_zero(self):
        m = Metadata(tag1='a', tag2=0, tag3='', tag4=None)
        m['tag5'] = 0
        m['tag1'] = ''
        self.assertIn('tag1', m.deleted_tags)
        self.assertEqual(m['tag2'], '0')
        self.assertNotIn('tag3', m)
        self.assertNotIn('tag4', m)
        self.assertEqual(m['tag5'], '0')

    def test_metadata_mapping_del(self):
        m = self.metadata_d1
        self.assertEqual(m.getraw('a'), ['b'])
        self.assertNotIn('a', m.deleted_tags)

        self.assertNotIn('x', m.deleted_tags)
        self.assertRaises(KeyError, m.getraw, 'x')

        del m['a']
        self.assertRaises(KeyError, m.getraw, 'a')
        self.assertIn('a', m.deleted_tags)

        # NOTE: historic behavior of Metadata.delete()
        # an attempt to delete an non-existing tag, will add it to the list
        # of deleted tags
        # so this will not raise a KeyError
        # as is it differs from dict or even defaultdict behavior
        del m['unknown']
        self.assertIn('unknown', m.deleted_tags)

    def test_metadata_mapping_iter(self):
        l = set(self.metadata_d1)
        self.assertEqual(l, {'a', 'c', 'd'})

    def test_metadata_mapping_keys(self):
        l = set(self.metadata_d1.keys())
        self.assertEqual(l, {'a', 'c', 'd'})

    def test_metadata_mapping_values(self):
        l = set(self.metadata_d1.values())
        self.assertEqual(l, {'b', '2', 'x; y'})

    def test_metadata_mapping_len(self):
        m = self.metadata_d1
        self.assertEqual(len(m), 3)
        del m['x']
        self.assertEqual(len(m), 3)
        del m['c']
        self.assertEqual(len(m), 2)

    def _check_mapping_update(self, m):
        self.assertEqual(m['a'], 'b')
        self.assertEqual(m['c'], '3')
        self.assertEqual(m.getraw('d'), ['u', 'w'])
        self.assertEqual(m['x'], '')
        self.assertIn('x', m.deleted_tags)

    def test_metadata_mapping_update(self):
        # update from Metadata
        m = self.metadata_d2
        m2 = self.metadata_d3

        del m2['x']
        m.update(m2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_dict(self):
        # update from dict
        m = self.metadata_d2

        d2 = {'c': 3, 'd': ['u', 'w'], 'x': ''}

        m.update(d2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_tuple(self):
        # update from tuple
        m = self.metadata_d2

        d2 = (('c', 3), ('d', ['u', 'w']), ('x', ''))

        m.update(d2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_dictlike(self):
        # update from kwargs
        m = self.metadata_d2

        m.update(c=3, d=['u', 'w'], x='')
        self._check_mapping_update(m)

    def test_metadata_mapping_update_noparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(TypeError, m.update)
        self.assertEqual(m['a'], 'b')

    def test_metadata_mapping_update_intparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(TypeError, m.update, 123)

    def test_metadata_mapping_update_strparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(ValueError, m.update, 'abc')

    def test_metadata_mapping_update_kw(self):
        m = Metadata(tag1='a', tag2='b')
        m.update(tag1='c')
        self.assertEqual(m['tag1'], 'c')
        self.assertEqual(m['tag2'], 'b')
        m.update(tag2='')
        self.assertIn('tag2', m.deleted_tags)

    def test_metadata_mapping_update_kw_del(self):
        m = Metadata(tag1='a', tag2='b')
        del m['tag1']

        m2 = Metadata(tag1='c', tag2='d')
        del m2['tag2']

        m.update(m2)
        self.assertEqual(m['tag1'], 'c')
        self.assertNotIn('tag2', m)
        self.assertNotIn('tag1', m.deleted_tags)
        self.assertIn('tag2', m.deleted_tags)

    def test_metadata_mapping_images(self):
        image1 = create_image(b'A', comment='A')
        image2 = create_image(b'B', comment='B')

        m1 = Metadata(a='b', length=1234, images=[image1])
        self.assertEqual(m1.images[0], image1)
        self.assertEqual(len(m1), 2)  # one tag, one image

        m1.images.append(image2)
        self.assertEqual(m1.images[1], image2)

        m1.images.pop(0)
        self.assertEqual(m1.images[0], image2)

        m2 = Metadata(a='c', length=4567, images=[image1])
        m1.update(m2)
        self.assertEqual(m1.images[0], image1)

        m1.images.pop(0)
        self.assertEqual(len(m1), 1)  # one tag, zero image
        self.assertFalse(m1.images)

    def test_metadata_mapping_iterable(self):
        m = Metadata(tag_tuple=('a', 0))
        m['tag_set'] = {'c', 'd'}
        m['tag_dict'] = {'e': 1, 'f': 2}
        m['tag_str'] = 'gh'
        self.assertIn('0', m.getraw('tag_tuple'))
        self.assertIn('c', m.getraw('tag_set'))
        self.assertIn('e', m.getraw('tag_dict'))
        self.assertIn('gh', m.getraw('tag_str'))
Beispiel #20
0
    def _finalize_loading(self, error):
        if error:
            self.metadata.clear()
            self.status = _("[could not load album %s]") % self.id
            del self._new_metadata
            del self._new_tracks
            self.update()
            return

        if self._requests > 0:
            return

        if not self._tracks_loaded:
            artists = set()
            totalalbumtracks = 0
            absolutetracknumber = 0
            va = self._new_metadata['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID

            djmix_ars = {}
            if hasattr(self._new_metadata, "_djmix_ars"):
                djmix_ars = self._new_metadata._djmix_ars

            for medium_node in self._release_node.medium_list[0].medium:
                mm = Metadata()
                mm.copy(self._new_metadata)
                medium_to_metadata(medium_node, mm)
                discpregap = False

                for dj in djmix_ars.get(mm["discnumber"], []):
                    mm.add("djmixer", dj)

                if "pregap" in medium_node.children:
                    discpregap = True
                    absolutetracknumber += 1
                    track = self._finalize_loading_track(medium_node.pregap[0], mm, artists, va, absolutetracknumber, discpregap)
                    track.metadata['~pregap'] = "1"

                for track_node in medium_node.track_list[0].track:
                    absolutetracknumber += 1
                    track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)

                if "data_track_list" in medium_node.children:
                    for track_node in medium_node.data_track_list[0].track:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)
                        track.metadata['~datatrack'] = "1"

            totalalbumtracks = str(absolutetracknumber)

            for track in self._new_tracks:
                track.metadata["~totalalbumtracks"] = totalalbumtracks
                if len(artists) > 1:
                    track.metadata["~multiartist"] = "1"
            del self._release_node
            self._tracks_loaded = True

        if not self._requests:
            # Prepare parser for user's script
            if config.setting["enable_tagger_script"]:
                script = config.setting["tagger_script"]
                if script:
                    parser = ScriptParser()
                    for track in self._new_tracks:
                        # Run tagger script for each track
                        try:
                            parser.eval(script, track.metadata)
                        except:
                            self.error_append(traceback.format_exc())
                        # Strip leading/trailing whitespace
                        track.metadata.strip_whitespace()
                    # Run tagger script for the album itself
                    try:
                        parser.eval(script, self._new_metadata)
                    except:
                        self.error_append(traceback.format_exc())
                    self._new_metadata.strip_whitespace()

            for track in self.tracks:
                for file in list(track.linked_files):
                    file.move(self.unmatched_files)
            self.metadata = self._new_metadata
            self.tracks = self._new_tracks
            del self._new_metadata
            del self._new_tracks
            self.loaded = True
            self.status = None
            self.match_files(self.unmatched_files.files)
            self.update()
            self.tagger.window.set_statusbar_message(
                N_('Album %(id)s loaded: %(artist)s - %(album)s'),
                {
                    'id': self.id,
                    'artist': self.metadata['albumartist'],
                    'album': self.metadata['album']
                },
                timeout=3000
            )
            for func in self._after_load_callbacks:
                func()
            self._after_load_callbacks = []
Beispiel #21
0
    def _load(self, filename):
        self.log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename), ID3=compatid3.CompatID3)
        tags = file.tags or {}
        # upgrade custom 2.3 frames to 2.4
        for old, new in self.__upgrade.items():
            if old in tags and new not in tags:
                f = tags.pop(old)
                tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text))
        metadata = Metadata()
        for frame in tags.values():
            frameid = frame.FrameID
            if frameid in self.__translate:
                name = self.__translate[frameid]
                if frameid.startswith('T'):
                    for text in frame.text:
                        if text:
                            metadata.add(name, unicode(text))
                elif frameid == 'COMM':
                    for text in frame.text:
                        if text:
                            metadata.add('%s:%s' % (name, frame.desc), unicode(text))
                else:
                    metadata.add(name, unicode(frame))
            elif frameid == "TMCL":
                for role, name in frame.people:
                    if role or name:
                        metadata.add('performer:%s' % role, name)
            elif frameid == "TIPL":
                for role, name in frame.people:
                    if role in self.__tipl_roles and name:
                        metadata.add(self.__tipl_roles[role], name)
            elif frameid == 'TXXX' and frame.desc in self.__translate_freetext:
                name = self.__translate_freetext[frame.desc]
                for text in frame.text:
                    metadata.add(name, unicode(text))
            elif frameid == 'USLT':
                name = 'lyrics'
                if frame.desc:
                    name += frame.desc
                metadata.add(name, unicode(frame.text))
            elif frameid == 'UFID' and frame.owner == 'http://musicbrainz.org':
                metadata['musicbrainz_trackid'] = unicode(frame.data)
            elif frameid == 'TRCK':
                value = frame.text[0].split('/')
                if len(value) > 1:
                    metadata['tracknumber'], metadata['totaltracks'] = value[:2]
                else:
                    metadata['tracknumber'] = value[0]
            elif frameid == 'TPOS':
                value = frame.text[0].split('/')
                if len(value) > 1:
                    metadata['discnumber'], metadata['totaldiscs'] = value[:2]
                else:
                    metadata['discnumber'] = value[0]
            elif frameid == 'APIC':
                metadata.add_image(frame.mime, frame.data)
            elif frameid == 'POPM':
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == self.config.setting['rating_user_email']:
                    rating = unicode(int(round(frame.rating / 255.0 * (self.config.setting['rating_steps'] - 1))))
                    metadata.add('~rating', rating)

        if 'date' in metadata:
            metadata['date'] = sanitize_date(metadata.getall('date')[0])

        self._info(metadata, file)
        return metadata
class MetadataTest(unittest.TestCase):

    original = None
    tags = []

    def setUp(self):
        config.setting = settings.copy()
        self.metadata = Metadata()
        self.metadata["single1"] = "single1-value"
        self.metadata.add_unique("single2", "single2-value")
        self.metadata.add_unique("single2", "single2-value")
        self.multi1 = ["multi1-value", "multi1-value"]
        self.metadata.add("multi1", self.multi1[0])
        self.metadata.add("multi1", self.multi1[1])
        self.multi2 = ["multi2-value1", "multi2-value2"]
        self.metadata["multi2"] = self.multi2
        self.multi3 = ["multi3-value1", "multi3-value2"]
        self.metadata.set("multi3", self.multi3)
        self.metadata["~hidden"] = "hidden-value"

    def tearDown(self):
        pass

    def test_metadata_set(self):
        self.assertEqual(["single1-value"], dict.get(self.metadata,"single1"))
        self.assertEqual(["single2-value"], dict.get(self.metadata,"single2"))
        self.assertEqual(self.multi1, dict.get(self.metadata,"multi1"))
        self.assertEqual(self.multi2, dict.get(self.metadata,"multi2"))
        self.assertEqual(self.multi3, dict.get(self.metadata,"multi3"))
        self.assertEqual(["hidden-value"], dict.get(self.metadata,"~hidden"))

    def test_metadata_get(self):
        self.assertEqual("single1-value", self.metadata["single1"])
        self.assertEqual("single1-value", self.metadata.get("single1"))
        self.assertEqual(["single1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata.get("multi1"))
        self.assertEqual(self.multi1, self.metadata.getall("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))

        self.assertEqual(dict.items(self.metadata), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in dict.items(self.metadata) for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_metadata_delete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

        self.metadata["single2"] = ""
        self.assertNotIn("single2", self.metadata)
        self.assertIn("single2", self.metadata.deleted_tags)

    def test_metadata_update(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.update(self.metadata)
        self.assertIn("old", m)
        self.assertNotIn("single1", m)
        self.assertIn("single1", m.deleted_tags)
        self.assertEqual("single2-value", m["single2"])
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)

        self.metadata["old"] = "old-value"
        for (key, value) in dict.items(self.metadata):
            self.assertIn(key, m)
            self.assertEqual(value, dict.get(m, key))
        for (key, value) in dict.items(m):
            self.assertIn(key, self.metadata)
            self.assertEqual(value, dict.get(self.metadata, key))

    def test_metadata_clear(self):
        self.metadata.clear()
        self.assertEqual(0, len(self.metadata))

    def test_metadata_applyfunc(self):
        func = lambda x: x[1:]
        self.metadata.apply_func(func)

        self.assertEqual("ingle1-value", self.metadata["single1"])
        self.assertEqual("ingle1-value", self.metadata.get("single1"))
        self.assertEqual(["ingle1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata.get("multi1"))
        self.assertEqual(list(map(func, self.multi1)), self.metadata.getall("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))

        self.assertEqual(dict.items(self.metadata), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in dict.items(self.metadata) for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))
Beispiel #23
0
class MetadataTest(PicardTestCase):

    original = None
    tags = []

    def setUp(self):
        super().setUp()
        config.setting = settings.copy()
        self.metadata = Metadata()
        self.metadata["single1"] = "single1-value"
        self.metadata.add_unique("single2", "single2-value")
        self.metadata.add_unique("single2", "single2-value")
        self.multi1 = ["multi1-value", "multi1-value"]
        self.metadata.add("multi1", self.multi1[0])
        self.metadata.add("multi1", self.multi1[1])
        self.multi2 = ["multi2-value1", "multi2-value2"]
        self.metadata["multi2"] = self.multi2
        self.multi3 = ["multi3-value1", "multi3-value2"]
        self.metadata.set("multi3", self.multi3)
        self.metadata["~hidden"] = "hidden-value"

    def tearDown(self):
        pass

    def test_metadata_setitem(self):
        self.assertEqual(["single1-value"], dict.get(self.metadata, "single1"))
        self.assertEqual(["single2-value"], dict.get(self.metadata, "single2"))
        self.assertEqual(self.multi1, dict.get(self.metadata, "multi1"))
        self.assertEqual(self.multi2, dict.get(self.metadata, "multi2"))
        self.assertEqual(self.multi3, dict.get(self.metadata, "multi3"))
        self.assertEqual(["hidden-value"], dict.get(self.metadata, "~hidden"))

    def test_metadata_get(self):
        self.assertEqual("single1-value", self.metadata["single1"])
        self.assertEqual("single1-value", self.metadata.get("single1"))
        self.assertEqual(["single1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1),
                         self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1),
                         self.metadata.get("multi1"))
        self.assertEqual(self.multi1, self.metadata.getall("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))

        self.assertEqual(dict.items(self.metadata), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in dict.items(self.metadata)
                          for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_metadata_delete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

    def test_metadata_implicit_delete(self):
        self.metadata["single2"] = ""
        self.assertNotIn("single2", self.metadata)
        self.assertIn("single2", self.metadata.deleted_tags)

        self.metadata["unknown"] = ""
        self.assertNotIn("unknown", self.metadata)
        self.assertNotIn("unknown", self.metadata.deleted_tags)

    def test_metadata_set_explicit_empty(self):
        self.metadata.delete("single1")
        self.metadata.set("single1", [])
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)
        self.assertEqual([], self.metadata.getall("single1"))

    def test_metadata_undelete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

        self.metadata["single1"] = "value1"
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_update(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.update(self.metadata)
        self.assertIn("old", m)
        self.assertNotIn("single1", m)
        self.assertIn("single1", m.deleted_tags)
        self.assertEqual("single2-value", m["single2"])
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)

        self.metadata["old"] = "old-value"
        for (key, value) in dict.items(self.metadata):
            self.assertIn(key, m)
            self.assertEqual(value, dict.get(m, key))
        for (key, value) in dict.items(m):
            self.assertIn(key, self.metadata)
            self.assertEqual(value, dict.get(self.metadata, key))

    def test_metadata_clear(self):
        self.metadata.clear()
        self.assertEqual(0, len(self.metadata))

    def test_metadata_clear_deleted(self):
        self.metadata.delete("single1")
        self.assertIn("single1", self.metadata.deleted_tags)
        self.metadata.clear_deleted()
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_applyfunc(self):
        def func(x):
            return x[1:]

        self.metadata.apply_func(func)

        self.assertEqual("ingle1-value", self.metadata["single1"])
        self.assertEqual("ingle1-value", self.metadata.get("single1"))
        self.assertEqual(["ingle1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)),
                         self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)),
                         self.metadata.get("multi1"))
        self.assertEqual(list(map(func, self.multi1)),
                         self.metadata.getall("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))

        self.assertEqual(dict.items(self.metadata), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in dict.items(self.metadata)
                          for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_length_score(self):
        results = [(20000, 0, 0.333333333333), (20000, 10000, 0.666666666667),
                   (20000, 20000, 1.0), (20000, 30000, 0.666666666667),
                   (20000, 40000, 0.333333333333), (20000, 50000, 0.0)]
        for (a, b, expected) in results:
            actual = Metadata.length_score(a, b)
            self.assertAlmostEqual(expected,
                                   actual,
                                   msg="a={a}, b={b}".format(a=a, b=b))

    def test_compare_is_equal(self):
        m1 = Metadata()
        m1["title"] = "title1"
        m1["tracknumber"] = "2"
        m1.length = 360
        m2 = Metadata()
        m2["title"] = "title1"
        m2["tracknumber"] = "2"
        m2.length = 360
        self.assertEqual(m1.compare(m2), m2.compare(m1))
        self.assertEqual(m1.compare(m2), 1)

    def test_compare_lengths(self):
        m1 = Metadata()
        m1.length = 360
        m2 = Metadata()
        m2.length = 300
        self.assertAlmostEqual(m1.compare(m2), 0.998)

    def test_compare_tracknumber_difference(self):
        m1 = Metadata()
        m1["tracknumber"] = "1"
        m2 = Metadata()
        m2["tracknumber"] = "2"
        self.assertEqual(m1.compare(m2), 0)

    def test_compare_deleted(self):
        m1 = Metadata()
        m1["artist"] = "TheArtist"
        m1["title"] = "title1"
        m2 = Metadata()
        m2["artist"] = "TheArtist"
        m2.delete("title")
        self.assertTrue(m1.compare(m2) < 1)
Beispiel #24
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename))
        file.tags = file.tags or {}
        metadata = Metadata()
        for origname, values in file.tags.items():
            for value in values:
                name = origname
                if name == "date" or name == "originaldate":
                    # YYYY-00-00 => YYYY
                    value = sanitize_date(value)
                elif name == 'performer' or name == 'comment':
                    # transform "performer=Joe Barr (Piano)" to "performer:Piano=Joe Barr"
                    name += ':'
                    if value.endswith(')'):
                        start = len(value) - 2
                        count = 1
                        while count > 0 and start > 0:
                            if value[start] == ')':
                                count += 1
                            elif value[start] == '(':
                                count -= 1
                            start -= 1
                        if start > 0:
                            name += value[start + 2:-1]
                            value = value[:start]
                elif name.startswith('rating'):
                    try:
                        name, email = name.split(':', 1)
                    except ValueError:
                        email = ''
                    if email != config.setting['rating_user_email']:
                        continue
                    name = '~rating'
                    value = unicode(int(round((float(value) * (config.setting['rating_steps'] - 1)))))
                elif name == "fingerprint" and value.startswith("MusicMagic Fingerprint"):
                    name = "musicip_fingerprint"
                    value = value[22:]
                elif name == "tracktotal":
                    if "totaltracks" in file.tags:
                        continue
                    name = "totaltracks"
                elif name == "disctotal":
                    if "totaldiscs" in file.tags:
                        continue
                    name = "totaldiscs"
                elif name == "metadata_block_picture":
                    image = mutagen.flac.Picture(base64.standard_b64decode(value))
                    try:
                        coverartimage = TagCoverArtImage(
                            file=filename,
                            tag=name,
                            types=types_from_id3(image.type),
                            comment=image.desc,
                            support_types=True,
                            data=image.data,
                        )
                    except CoverArtImageError as e:
                        log.error('Cannot load image from %r: %s' % (filename, e))
                    else:
                        metadata.append_image(coverartimage)

                    continue
                elif name in self.__translate:
                    name = self.__translate[name]
                metadata.add(name, value)
        if self._File == mutagen.flac.FLAC:
            for image in file.pictures:
                try:
                    coverartimage = TagCoverArtImage(
                        file=filename,
                        tag='FLAC/PICTURE',
                        types=types_from_id3(image.type),
                        comment=image.desc,
                        support_types=True,
                        data=image.data,
                    )
                except CoverArtImageError as e:
                    log.error('Cannot load image from %r: %s' % (filename, e))
                else:
                     metadata.append_image(coverartimage)

        # Read the unofficial COVERART tags, for backward compatibillity only
        if not "metadata_block_picture" in file.tags:
            try:
                for data in file["COVERART"]:
                    try:
                        coverartimage = TagCoverArtImage(
                            file=filename,
                            tag='COVERART',
                            data=base64.standard_b64decode(data)
                        )
                    except CoverArtImageError as e:
                        log.error('Cannot load image from %r: %s' % (filename, e))
                    else:
                        metadata.append_image(coverartimage)
            except KeyError:
                pass
        self._info(metadata, file)
        return metadata
Beispiel #25
0
    def _finalize_loading(self, error):
        if error:
            self.metadata.clear()
            self.status = _("[could not load album %s]") % self.id
            del self._new_metadata
            del self._new_tracks
            self.update()
            return

        if self._requests > 0:
            return

        if not self._tracks_loaded:
            artists = set()
            totalalbumtracks = 0
            absolutetracknumber = 0
            va = self._new_metadata['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID

            djmix_ars = {}
            if hasattr(self._new_metadata, "_djmix_ars"):
                djmix_ars = self._new_metadata._djmix_ars

            for medium_node in self._release_node['media']:
                mm = Metadata()
                mm.copy(self._new_metadata)
                medium_to_metadata(medium_node, mm)
                discpregap = False

                for dj in djmix_ars.get(mm["discnumber"], []):
                    mm.add("djmixer", dj)

                if 'discs' in medium_node:
                    discids = [disc.get('id') for disc in medium_node['discs']]
                    mm['~musicbrainz_discids'] = discids
                    mm['musicbrainz_discid'] = list(self._discids.intersection(discids))

                if "pregap" in medium_node:
                    discpregap = True
                    absolutetracknumber += 1
                    track = self._finalize_loading_track(medium_node['pregap'], mm, artists, va, absolutetracknumber, discpregap)
                    track.metadata['~pregap'] = "1"

                track_count = medium_node['track-count']
                if track_count:
                    tracklist_node = medium_node['tracks']
                    for track_node in tracklist_node:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)

                if "data-tracks" in medium_node:
                    for track_node in medium_node['data-tracks']:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)
                        track.metadata['~datatrack'] = "1"

            totalalbumtracks = str(absolutetracknumber)

            for track in self._new_tracks:
                track.metadata["~totalalbumtracks"] = totalalbumtracks
                if len(artists) > 1:
                    track.metadata["~multiartist"] = "1"
            del self._release_node
            del self._release_artist_nodes
            self._tracks_loaded = True

        if not self._requests:
            self.enable_update_metadata_images(False)
            # Prepare parser for user's script
            for s_name, s_text in enabled_tagger_scripts_texts():
                parser = ScriptParser()
                for track in self._new_tracks:
                    # Run tagger script for each track
                    try:
                        parser.eval(s_text, track.metadata)
                    except ScriptError:
                        log.exception("Failed to run tagger script %s on track", s_name)
                    track.metadata.strip_whitespace()
                # Run tagger script for the album itself
                try:
                    parser.eval(s_text, self._new_metadata)
                except ScriptError:
                    log.exception("Failed to run tagger script %s on album", s_name)
                self._new_metadata.strip_whitespace()

            for track in self.tracks:
                track.metadata_images_changed.connect(self.update_metadata_images)
                for file in list(track.linked_files):
                    file.move(self.unmatched_files)
            self.metadata = self._new_metadata
            self.tracks = self._new_tracks
            del self._new_metadata
            del self._new_tracks
            self.loaded = True
            self.status = None
            self.match_files(self.unmatched_files.files)
            self.enable_update_metadata_images(True)
            self.update()
            self.tagger.window.set_statusbar_message(
                N_('Album %(id)s loaded: %(artist)s - %(album)s'),
                {
                    'id': self.id,
                    'artist': self.metadata['albumartist'],
                    'album': self.metadata['album']
                },
                timeout=3000
            )
            for func in self._after_load_callbacks:
                func()
            self._after_load_callbacks = []
Beispiel #26
0
class MetadataTest(PicardTestCase):

    original = None
    tags = []

    def setUp(self):
        super().setUp()
        config.setting = settings.copy()
        self.metadata = Metadata()
        self.metadata["single1"] = "single1-value"
        self.metadata.add_unique("single2", "single2-value")
        self.metadata.add_unique("single2", "single2-value")
        self.multi1 = ["multi1-value", "multi1-value"]
        self.metadata.add("multi1", self.multi1[0])
        self.metadata.add("multi1", self.multi1[1])
        self.multi2 = ["multi2-value1", "multi2-value2"]
        self.metadata["multi2"] = self.multi2
        self.multi3 = ["multi3-value1", "multi3-value2"]
        self.metadata.set("multi3", self.multi3)
        self.metadata["~hidden"] = "hidden-value"

        self.metadata_d1 = Metadata({'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': ''})
        self.metadata_d2 = Metadata({'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': 'z'})
        self.metadata_d3 = Metadata({'c': 3, 'd': ['u', 'w'], 'x': 'p'})

    def tearDown(self):
        pass

    def test_metadata_setitem(self):
        self.assertEqual(["single1-value"], self.metadata.getraw("single1"))
        self.assertEqual(["single2-value"], self.metadata.getraw("single2"))
        self.assertEqual(self.multi1, self.metadata.getraw("multi1"))
        self.assertEqual(self.multi2, self.metadata.getraw("multi2"))
        self.assertEqual(self.multi3, self.metadata.getraw("multi3"))
        self.assertEqual(["hidden-value"], self.metadata.getraw("~hidden"))

    def test_metadata_get(self):
        self.assertEqual("single1-value", self.metadata["single1"])
        self.assertEqual("single1-value", self.metadata.get("single1"))
        self.assertEqual(["single1-value"], self.metadata.getall("single1"))
        self.assertEqual(["single1-value"], self.metadata.getraw("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata.get("multi1"))
        self.assertEqual(self.multi1, self.metadata.getall("multi1"))
        self.assertEqual(self.multi1, self.metadata.getraw("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))
        self.assertRaises(KeyError, self.metadata.getraw, "nonexistent")

        self.assertEqual(self.metadata._store.items(), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in self.metadata.rawitems() for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_metadata_delete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

    def test_metadata_implicit_delete(self):
        self.metadata["single2"] = ""
        self.assertNotIn("single2", self.metadata)
        self.assertIn("single2", self.metadata.deleted_tags)

        self.metadata["unknown"] = ""
        self.assertNotIn("unknown", self.metadata)
        self.assertNotIn("unknown", self.metadata.deleted_tags)

    def test_metadata_set_explicit_empty(self):
        self.metadata.delete("single1")
        self.metadata.set("single1", [])
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)
        self.assertEqual([], self.metadata.getall("single1"))

    def test_metadata_undelete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

        self.metadata["single1"] = "value1"
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_update(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.update(self.metadata)
        self.assertIn("old", m)
        self.assertNotIn("single1", m)
        self.assertIn("single1", m.deleted_tags)
        self.assertEqual("single2-value", m["single2"])
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)

        self.metadata["old"] = "old-value"
        for (key, value) in self.metadata.rawitems():
            self.assertIn(key, m)
            self.assertEqual(value, m.getraw(key))
        for (key, value) in m.rawitems():
            self.assertIn(key, self.metadata)
            self.assertEqual(value, self.metadata.getraw(key))

    def test_metadata_clear(self):
        self.metadata.clear()
        self.assertEqual(0, len(self.metadata))

    def test_metadata_clear_deleted(self):
        self.metadata.delete("single1")
        self.assertIn("single1", self.metadata.deleted_tags)
        self.metadata.clear_deleted()
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_applyfunc(self):
        def func(x): return x[1:]
        self.metadata.apply_func(func)

        self.assertEqual("ingle1-value", self.metadata["single1"])
        self.assertEqual("ingle1-value", self.metadata.get("single1"))
        self.assertEqual(["ingle1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata.get("multi1"))
        self.assertEqual(list(map(func, self.multi1)), self.metadata.getall("multi1"))

    def test_metadata_applyfunc_preserve_tags(self):
        self.assertTrue(len(PRESERVED_TAGS) > 0)
        m = Metadata()
        m[PRESERVED_TAGS[0]] = 'value1'
        m['not_preserved'] = 'value2'

        def func(x): return x[1:]
        m.apply_func(func)

        self.assertEqual("value1", m[PRESERVED_TAGS[0]])
        self.assertEqual("alue2", m['not_preserved'])

    def test_length_score(self):
        results = [(20000, 0, 0.333333333333),
                   (20000, 10000, 0.666666666667),
                   (20000, 20000, 1.0),
                   (20000, 30000, 0.666666666667),
                   (20000, 40000, 0.333333333333),
                   (20000, 50000, 0.0)]
        for (a, b, expected) in results:
            actual = Metadata.length_score(a, b)
            self.assertAlmostEqual(expected, actual,
                                   msg="a={a}, b={b}".format(a=a, b=b))

    def test_compare_is_equal(self):
        m1 = Metadata()
        m1["title"] = "title1"
        m1["tracknumber"] = "2"
        m1.length = 360
        m2 = Metadata()
        m2["title"] = "title1"
        m2["tracknumber"] = "2"
        m2.length = 360
        self.assertEqual(m1.compare(m2), m2.compare(m1))
        self.assertEqual(m1.compare(m2), 1)

    def test_compare_lengths(self):
        m1 = Metadata()
        m1.length = 360
        m2 = Metadata()
        m2.length = 300
        self.assertAlmostEqual(m1.compare(m2), 0.998)

    def test_compare_tracknumber_difference(self):
        m1 = Metadata()
        m1["tracknumber"] = "1"
        m2 = Metadata()
        m2["tracknumber"] = "2"
        self.assertEqual(m1.compare(m2), 0)

    def test_compare_deleted(self):
        m1 = Metadata()
        m1["artist"] = "TheArtist"
        m1["title"] = "title1"
        m2 = Metadata()
        m2["artist"] = "TheArtist"
        m2.delete("title")
        self.assertTrue(m1.compare(m2) < 1)

    def test_strip_whitespace(self):
        m1 = Metadata()
        m1["artist"] = "  TheArtist  "
        m1["title"] = "\t\u00A0  tit le1 \r\n"
        m1.strip_whitespace()
        self.assertEqual(m1["artist"], "TheArtist")
        self.assertEqual(m1["title"], "tit le1")

    def test_metadata_mapping_init(self):
        d = {'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': '', 'z': {'u', 'w'}}
        deleted_tags = set('c')
        m = Metadata(d, deleted_tags=deleted_tags, length=1234)
        self.assertTrue('a' in m)
        self.assertEqual(m.getraw('a'), ['b'])
        self.assertEqual(m['d'], MULTI_VALUED_JOINER.join(d['d']))
        self.assertNotIn('c', m)
        self.assertNotIn('length', m)
        self.assertIn('c', m.deleted_tags)
        self.assertEqual(m.length, 1234)

    def test_metadata_mapping_init_zero(self):
        m = Metadata(tag1='a', tag2=0, tag3='', tag4=None)
        m['tag5'] = 0
        m['tag1'] = ''
        self.assertIn('tag1', m.deleted_tags)
        self.assertEqual(m['tag2'], '0')
        self.assertNotIn('tag3', m)
        self.assertNotIn('tag4', m)
        self.assertEqual(m['tag5'], '0')

    def test_metadata_mapping_del(self):
        m = self.metadata_d1
        self.assertEqual(m.getraw('a'), ['b'])
        self.assertNotIn('a', m.deleted_tags)

        self.assertNotIn('x', m.deleted_tags)
        self.assertRaises(KeyError, m.getraw, 'x')

        del m['a']
        self.assertRaises(KeyError, m.getraw, 'a')
        self.assertIn('a', m.deleted_tags)

        # NOTE: historic behavior of Metadata.delete()
        # an attempt to delete an non-existing tag, will add it to the list
        # of deleted tags
        # so this will not raise a KeyError
        # as is it differs from dict or even defaultdict behavior
        del m['unknown']
        self.assertIn('unknown', m.deleted_tags)

    def test_metadata_mapping_iter(self):
        l = set(self.metadata_d1)
        self.assertEqual(l, {'a', 'c', 'd'})

    def test_metadata_mapping_keys(self):
        l = set(self.metadata_d1.keys())
        self.assertEqual(l, {'a', 'c', 'd'})

    def test_metadata_mapping_values(self):
        l = set(self.metadata_d1.values())
        self.assertEqual(l, {'b', '2', 'x; y'})

    def test_metadata_mapping_len(self):
        m = self.metadata_d1
        self.assertEqual(len(m), 3)
        del m['x']
        self.assertEqual(len(m), 3)
        del m['c']
        self.assertEqual(len(m), 2)

    def _check_mapping_update(self, m):
        self.assertEqual(m['a'], 'b')
        self.assertEqual(m['c'], '3')
        self.assertEqual(m.getraw('d'), ['u', 'w'])
        self.assertEqual(m['x'], '')
        self.assertIn('x', m.deleted_tags)

    def test_metadata_mapping_update(self):
        # update from Metadata
        m = self.metadata_d2
        m2 = self.metadata_d3

        del m2['x']
        m.update(m2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_dict(self):
        # update from dict
        m = self.metadata_d2

        d2 = {'c': 3, 'd': ['u', 'w'], 'x': ''}

        m.update(d2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_tuple(self):
        # update from tuple
        m = self.metadata_d2

        d2 = (('c', 3), ('d', ['u', 'w']), ('x', ''))

        m.update(d2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_dictlike(self):
        # update from kwargs
        m = self.metadata_d2

        m.update(c=3, d=['u', 'w'], x='')
        self._check_mapping_update(m)

    def test_metadata_mapping_update_noparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(TypeError, m.update)
        self.assertEqual(m['a'], 'b')

    def test_metadata_mapping_update_intparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(TypeError, m.update, 123)

    def test_metadata_mapping_update_strparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(ValueError, m.update, 'abc')

    def test_metadata_mapping_update_kw(self):
        m = Metadata(tag1='a', tag2='b')
        m.update(tag1='c')
        self.assertEqual(m['tag1'], 'c')
        self.assertEqual(m['tag2'], 'b')
        m.update(tag2='')
        self.assertIn('tag2', m.deleted_tags)

    def test_metadata_mapping_update_kw_del(self):
        m = Metadata(tag1='a', tag2='b')
        del m['tag1']

        m2 = Metadata(tag1='c', tag2='d')
        del m2['tag2']

        m.update(m2)
        self.assertEqual(m['tag1'], 'c')
        self.assertNotIn('tag2', m)
        self.assertNotIn('tag1', m.deleted_tags)
        self.assertIn('tag2', m.deleted_tags)

    def test_metadata_mapping_images(self):
        image1 = create_image(b'A', comment='A')
        image2 = create_image(b'B', comment='B')

        m1 = Metadata(a='b', length=1234, images=[image1])
        self.assertEqual(m1.images[0], image1)
        self.assertEqual(len(m1), 2) # one tag, one image

        m1.images.append(image2)
        self.assertEqual(m1.images[1], image2)

        m1.images.pop(0)
        self.assertEqual(m1.images[0], image2)

        m2 = Metadata(a='c', length=4567, images=[image1])
        m1.update(m2)
        self.assertEqual(m1.images[0], image1)

        m1.images.pop(0)
        self.assertEqual(len(m1), 1) # one tag, zero image
        self.assertFalse(m1.images)

    def test_metadata_mapping_iterable(self):
        m = Metadata(tag_tuple=('a', 0))
        m['tag_set'] = {'c', 'd'}
        m['tag_dict'] = {'e': 1, 'f': 2}
        m['tag_str'] = 'gh'
        self.assertIn('0', m.getraw('tag_tuple'))
        self.assertIn('c', m.getraw('tag_set'))
        self.assertIn('e', m.getraw('tag_dict'))
        self.assertIn('gh', m.getraw('tag_str'))
Beispiel #27
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename), ID3=compatid3.CompatID3)
        tags = file.tags or {}
        # upgrade custom 2.3 frames to 2.4
        for old, new in self.__upgrade.items():
            if old in tags and new not in tags:
                f = tags.pop(old)
                tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text))
        metadata = Metadata()
        for frame in tags.values():
            frameid = frame.FrameID
            if frameid in self.__translate:
                name = self.__translate[frameid]
                if frameid.startswith("T"):
                    for text in frame.text:
                        if text:
                            metadata.add(name, unicode(text))
                elif frameid == "COMM":
                    for text in frame.text:
                        if text:
                            metadata.add("%s:%s" % (name, frame.desc), unicode(text))
                else:
                    metadata.add(name, unicode(frame))
            elif frameid == "TMCL":
                for role, name in frame.people:
                    if role or name:
                        metadata.add("performer:%s" % role, name)
            elif frameid == "TIPL":
                # If file is ID3v2.3, TIPL tag could contain TMCL
                # so we will test for TMCL values and add to TIPL if not TMCL
                for role, name in frame.people:
                    if role in self._tipl_roles and name:
                        metadata.add(self._tipl_roles[role], name)
                    else:
                        metadata.add("performer:%s" % role, name)
            elif frameid == "TXXX":
                name = frame.desc
                if name in self.__translate_freetext:
                    name = self.__translate_freetext[name]
                elif (name in self.__rtranslate) != (name in self.__rtranslate_freetext):
                    # If the desc of a TXXX frame conflicts with the name of a
                    # Picard tag, load it into ~id3:TXXX:desc rather than desc.
                    #
                    # This basically performs an XOR, making sure that 'name'
                    # is in __rtranslate or __rtranslate_freetext, but not
                    # both. (Being in both implies we support reading it both
                    # ways.) Currently, the only tag in both is license.
                    name = "~id3:TXXX:" + name
                for text in frame.text:
                    metadata.add(name, unicode(text))
            elif frameid == "USLT":
                name = "lyrics"
                if frame.desc:
                    name += ":%s" % frame.desc
                metadata.add(name, unicode(frame.text))
            elif frameid == "UFID" and frame.owner == "http://musicbrainz.org":
                metadata["musicbrainz_recordingid"] = frame.data.decode("ascii", "ignore")
            elif frameid == "TRCK":
                value = frame.text[0].split("/")
                if len(value) > 1:
                    metadata["tracknumber"], metadata["totaltracks"] = value[:2]
                else:
                    metadata["tracknumber"] = value[0]
            elif frameid == "TPOS":
                value = frame.text[0].split("/")
                if len(value) > 1:
                    metadata["discnumber"], metadata["totaldiscs"] = value[:2]
                else:
                    metadata["discnumber"] = value[0]
            elif frameid == "APIC":
                extras = {"desc": frame.desc, "type": image_type_from_id3_num(frame.type)}
                metadata.add_image(frame.mime, frame.data, extras=extras)
            elif frameid == "POPM":
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == config.setting["rating_user_email"]:
                    rating = unicode(int(round(frame.rating / 255.0 * (config.setting["rating_steps"] - 1))))
                    metadata.add("~rating", rating)

        if "date" in metadata:
            sanitized = sanitize_date(metadata.getall("date")[0])
            if sanitized:
                metadata["date"] = sanitized

        self._info(metadata, file)
        return metadata
Beispiel #28
0
    def _load(self, filename):
        self.log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename), ID3=compatid3.CompatID3)
        tags = file.tags or {}
        # upgrade custom 2.3 frames to 2.4
        for old, new in self.__upgrade.items():
            if old in tags and new not in tags:
                f = tags.pop(old)
                tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text))
        metadata = Metadata()
        for frame in tags.values():
            frameid = frame.FrameID
            if frameid in self.__translate:
                name = self.__translate[frameid]
                if frameid.startswith('T'):
                    for text in frame.text:
                        if text:
                            metadata.add(name, unicode(text))
                elif frameid == 'COMM':
                    for text in frame.text:
                        if text:
                            metadata.add('%s:%s' % (name, frame.desc),
                                         unicode(text))
                else:
                    metadata.add(name, unicode(frame))
            elif frameid == "TMCL":
                for role, name in frame.people:
                    if role or name:
                        metadata.add('performer:%s' % role, name)
            elif frameid == "TIPL":
                for role, name in frame.people:
                    if role in self.__tipl_roles and name:
                        metadata.add(self.__tipl_roles[role], name)
            elif frameid == 'TXXX' and frame.desc in self.__translate_freetext:
                name = self.__translate_freetext[frame.desc]
                for text in frame.text:
                    metadata.add(name, unicode(text))
            elif frameid == 'USLT':
                name = 'lyrics'
                if frame.desc:
                    name += frame.desc
                metadata.add(name, unicode(frame.text))
            elif frameid == 'UFID' and frame.owner == 'http://musicbrainz.org':
                metadata['musicbrainz_trackid'] = unicode(frame.data)
            elif frameid == 'TRCK':
                value = frame.text[0].split('/')
                if len(value) > 1:
                    metadata['tracknumber'], metadata[
                        'totaltracks'] = value[:2]
                else:
                    metadata['tracknumber'] = value[0]
            elif frameid == 'TPOS':
                value = frame.text[0].split('/')
                if len(value) > 1:
                    metadata['discnumber'], metadata['totaldiscs'] = value[:2]
                else:
                    metadata['discnumber'] = value[0]
            elif frameid == 'APIC':
                metadata.add_image(frame.mime, frame.data)
            elif frameid == 'POPM':
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == self.config.setting['rating_user_email']:
                    rating = unicode(
                        int(
                            round(frame.rating / 255.0 *
                                  (self.config.setting['rating_steps'] - 1))))
                    metadata.add('~rating', rating)

        if 'date' in metadata:
            metadata['date'] = sanitize_date(metadata.getall('date')[0])

        self._info(metadata, file)
        return metadata
Beispiel #29
0
    def _finalize_loading(self, error):
        if error:
            self.metadata.clear()
            self.metadata['album'] = _("[could not load album %s]") % self.id
            del self._new_metadata
            del self._new_tracks
            self.update()
            return

        if self._requests > 0:
            return

        if not self._tracks_loaded:
            artists = set()
            totalalbumtracks = 0

            djmix_ars = {}
            if hasattr(self._new_metadata, "_djmix_ars"):
                djmix_ars = self._new_metadata._djmix_ars

            for medium_node in self._release_node.medium_list[0].medium:
                mm = Metadata()
                mm.copy(self._new_metadata)
                medium_to_metadata(medium_node, mm)
                totalalbumtracks += int(mm["totaltracks"])

                for dj in djmix_ars.get(mm["discnumber"], []):
                    mm.add("djmixer", dj)

                for track_node in medium_node.track_list[0].track:
                    track = Track(track_node.recording[0].id, self)
                    self._new_tracks.append(track)

                    # Get track metadata
                    tm = track.metadata
                    tm.copy(mm)
                    track_to_metadata(track_node, track, self.config)
                    track._customize_metadata()

                    self._new_metadata.length += tm.length
                    artists.add(tm["musicbrainz_artistid"])

                    # Run track metadata plugins
                    try:
                        run_track_metadata_processors(self, tm,
                                                      self._release_node,
                                                      track_node)
                    except:
                        self.log.error(traceback.format_exc())

            totalalbumtracks = str(totalalbumtracks)

            for track in self._new_tracks:
                track.metadata["~totalalbumtracks"] = totalalbumtracks
                if len(artists) > 1:
                    track.metadata["compilation"] = "1"

            del self._release_node
            self._tracks_loaded = True

        if not self._requests:
            # Prepare parser for user's script
            if self.config.setting["enable_tagger_script"]:
                script = self.config.setting["tagger_script"]
                if script:
                    parser = ScriptParser()
                    for track in self._new_tracks:
                        # Run tagger script for each track
                        try:
                            parser.eval(script, track.metadata)
                        except:
                            self.log.error(traceback.format_exc())
                        # Strip leading/trailing whitespace
                        track.metadata.strip_whitespace()
                    # Run tagger script for the album itself
                    try:
                        parser.eval(script, self._new_metadata)
                    except:
                        self.log.error(traceback.format_exc())
                    self._new_metadata.strip_whitespace()

            for track in self.tracks:
                for file in list(track.linked_files):
                    file.move(self.unmatched_files)
            self.metadata = self._new_metadata
            self.tracks = self._new_tracks
            del self._new_metadata
            del self._new_tracks
            self.loaded = True
            self.match_files(self.unmatched_files.files)
            self.update()
            self.tagger.window.set_statusbar_message('Album %s loaded',
                                                     self.id,
                                                     timeout=3000)
            while self._after_load_callbacks.qsize() > 0:
                func = self._after_load_callbacks.get()
                func()
Beispiel #30
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = self._get_file(encode_filename(filename))
        tags = file.tags or {}
        # upgrade custom 2.3 frames to 2.4
        for old, new in self.__upgrade.items():
            if old in tags and new not in tags:
                f = tags.pop(old)
                tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text))
        metadata = Metadata()
        for frame in tags.values():
            frameid = frame.FrameID
            if frameid in self.__translate:
                name = self.__translate[frameid]
                if frameid.startswith('T'):
                    for text in frame.text:
                        if text:
                            metadata.add(name, unicode(text))
                elif frameid == 'COMM':
                    for text in frame.text:
                        if text:
                            metadata.add('%s:%s' % (name, frame.desc), unicode(text))
                else:
                    metadata.add(name, unicode(frame))
            elif frameid == "TMCL":
                for role, name in frame.people:
                    if role or name:
                        metadata.add('performer:%s' % role, name)
            elif frameid == "TIPL":
                # If file is ID3v2.3, TIPL tag could contain TMCL
                # so we will test for TMCL values and add to TIPL if not TMCL
                for role, name in frame.people:
                    if role in self._tipl_roles and name:
                        metadata.add(self._tipl_roles[role], name)
                    else:
                        metadata.add('performer:%s' % role, name)
            elif frameid == 'TXXX':
                name = frame.desc
                if name in self.__translate_freetext:
                    name = self.__translate_freetext[name]
                elif ((name in self.__rtranslate) !=
                        (name in self.__rtranslate_freetext)):
                    # If the desc of a TXXX frame conflicts with the name of a
                    # Picard tag, load it into ~id3:TXXX:desc rather than desc.
                    #
                    # This basically performs an XOR, making sure that 'name'
                    # is in __rtranslate or __rtranslate_freetext, but not
                    # both. (Being in both implies we support reading it both
                    # ways.) Currently, the only tag in both is license.
                    name = '~id3:TXXX:' + name
                for text in frame.text:
                    metadata.add(name, unicode(text))
            elif frameid == 'USLT':
                name = 'lyrics'
                if frame.desc:
                    name += ':%s' % frame.desc
                metadata.add(name, unicode(frame.text))
            elif frameid == 'UFID' and frame.owner == 'http://musicbrainz.org':
                metadata['musicbrainz_recordingid'] = frame.data.decode('ascii', 'ignore')
            elif frameid in self.__tag_re_parse.keys():
                m = self.__tag_re_parse[frameid].search(frame.text[0])
                if m:
                    for name, value in m.groupdict().iteritems():
                        if value is not None:
                            metadata[name] = value
                else:
                    log.error("Invalid %s value '%s' dropped in %r", frameid, frame.text[0], filename)
            elif frameid == 'APIC':
                try:
                    coverartimage = TagCoverArtImage(
                        file=filename,
                        tag=frameid,
                        types=types_from_id3(frame.type),
                        comment=frame.desc,
                        support_types=True,
                        data=frame.data,
                    )
                except CoverArtImageError as e:
                    log.error('Cannot load image from %r: %s' % (filename, e))
                else:
                    metadata.append_image(coverartimage)
            elif frameid == 'POPM':
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == config.setting['rating_user_email']:
                    rating = unicode(int(round(frame.rating / 255.0 * (config.setting['rating_steps'] - 1))))
                    metadata.add('~rating', rating)

        if 'date' in metadata:
            sanitized = sanitize_date(metadata.getall('date')[0])
            if sanitized:
                metadata['date'] = sanitized

        self._info(metadata, file)
        return metadata
Beispiel #31
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename))
        file.tags = file.tags or {}
        metadata = Metadata()
        for origname, values in file.tags.items():
            for value in values:
                name = origname
                if name == "date" or name == "originaldate":
                    # YYYY-00-00 => YYYY
                    value = sanitize_date(value)
                elif name == 'performer' or name == 'comment':
                    # transform "performer=Joe Barr (Piano)" to "performer:Piano=Joe Barr"
                    name += ':'
                    if value.endswith(')'):
                        start = len(value) - 2
                        count = 1
                        while count > 0 and start > 0:
                            if value[start] == ')':
                                count += 1
                            elif value[start] == '(':
                                count -= 1
                            start -= 1
                        if start > 0:
                            name += value[start + 2:-1]
                            value = value[:start]
                elif name.startswith('rating'):
                    try:
                        name, email = name.split(':', 1)
                    except ValueError:
                        email = ''
                    if email != config.setting['rating_user_email']:
                        continue
                    name = '~rating'
                    value = string_(
                        int(
                            round((float(value) *
                                   (config.setting['rating_steps'] - 1)))))
                elif name == "fingerprint" and value.startswith(
                        "MusicMagic Fingerprint"):
                    name = "musicip_fingerprint"
                    value = value[22:]
                elif name == "tracktotal":
                    if "totaltracks" in file.tags:
                        continue
                    name = "totaltracks"
                elif name == "disctotal":
                    if "totaldiscs" in file.tags:
                        continue
                    name = "totaldiscs"
                elif name == "metadata_block_picture":
                    image = mutagen.flac.Picture(
                        base64.standard_b64decode(value))
                    try:
                        coverartimage = TagCoverArtImage(
                            file=filename,
                            tag=name,
                            types=types_from_id3(image.type),
                            comment=image.desc,
                            support_types=True,
                            data=image.data,
                        )
                    except CoverArtImageError as e:
                        log.error('Cannot load image from %r: %s' %
                                  (filename, e))
                    else:
                        metadata.append_image(coverartimage)

                    continue
                elif name in self.__translate:
                    name = self.__translate[name]
                metadata.add(name, value)
        if self._File == mutagen.flac.FLAC:
            for image in file.pictures:
                try:
                    coverartimage = TagCoverArtImage(
                        file=filename,
                        tag='FLAC/PICTURE',
                        types=types_from_id3(image.type),
                        comment=image.desc,
                        support_types=True,
                        data=image.data,
                    )
                except CoverArtImageError as e:
                    log.error('Cannot load image from %r: %s' % (filename, e))
                else:
                    metadata.append_image(coverartimage)

        # Read the unofficial COVERART tags, for backward compatibillity only
        if "metadata_block_picture" not in file.tags:
            try:
                for data in file["COVERART"]:
                    try:
                        coverartimage = TagCoverArtImage(
                            file=filename,
                            tag='COVERART',
                            data=base64.standard_b64decode(data))
                    except CoverArtImageError as e:
                        log.error('Cannot load image from %r: %s' %
                                  (filename, e))
                    else:
                        metadata.append_image(coverartimage)
            except KeyError:
                pass
        self._info(metadata, file)
        return metadata
Beispiel #32
0
    def _load(self, filename):
        self.log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename), ID3=compatid3.CompatID3)
        tags = file.tags or {}
        # upgrade custom 2.3 frames to 2.4
        for old, new in self.__upgrade.items():
            if old in tags and new not in tags:
                f = tags.pop(old)
                tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text))
        metadata = Metadata()
        for frame in tags.values():
            frameid = frame.FrameID
            if frameid in self.__translate:
                name = self.__translate[frameid]
                if frameid.startswith("T"):
                    for text in frame.text:
                        if text:
                            metadata.add(name, unicode(text))
                elif frameid == "COMM":
                    for text in frame.text:
                        if text:
                            metadata.add("%s:%s" % (name, frame.desc), unicode(text))
                else:
                    metadata.add(name, unicode(frame))
            elif frameid == "TMCL":
                for role, name in frame.people:
                    if role or name:
                        metadata.add("performer:%s" % role, name)
            elif frameid == "TIPL":
                for role, name in frame.people:
                    if role in self.__tipl_roles and name:
                        metadata.add(self.__tipl_roles[role], name)
            elif frameid == "TXXX" and frame.desc in self.__translate_freetext:
                name = self.__translate_freetext[frame.desc]
                for text in frame.text:
                    metadata.add(name, unicode(text))
            elif frameid == "USLT":
                name = "lyrics"
                if frame.desc:
                    name += frame.desc
                metadata.add(name, unicode(frame.text))
            elif frameid == "UFID" and frame.owner == "http://musicbrainz.org":
                metadata["musicbrainz_trackid"] = unicode(frame.data)
            elif frameid == "TRCK":
                value = frame.text[0].split("/")
                if len(value) > 1:
                    metadata["tracknumber"], metadata["totaltracks"] = value[:2]
                else:
                    metadata["tracknumber"] = value[0]
            elif frameid == "TPOS":
                value = frame.text[0].split("/")
                if len(value) > 1:
                    metadata["discnumber"], metadata["totaldiscs"] = value[:2]
                else:
                    metadata["discnumber"] = value[0]
            elif frameid == "APIC":
                metadata.add_image(frame.mime, frame.data)
            elif frameid == "POPM":
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == self.config.setting["rating_user_email"]:
                    rating = unicode(int(round(frame.rating / 255.0 * (self.config.setting["rating_steps"] - 1))))
                    metadata.add("~rating", rating)

        if "date" in metadata:
            metadata["date"] = sanitize_date(metadata.getall("date")[0])

        self._info(metadata, file)
        return metadata
Beispiel #33
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename), ID3=compatid3.CompatID3)
        tags = file.tags or {}
        # upgrade custom 2.3 frames to 2.4
        for old, new in self.__upgrade.items():
            if old in tags and new not in tags:
                f = tags.pop(old)
                tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text))
        metadata = Metadata()
        for frame in tags.values():
            frameid = frame.FrameID
            if frameid in self.__translate:
                name = self.__translate[frameid]
                if frameid.startswith('T'):
                    for text in frame.text:
                        if text:
                            metadata.add(name, unicode(text))
                elif frameid == 'COMM':
                    for text in frame.text:
                        if text:
                            metadata.add('%s:%s' % (name, frame.desc), unicode(text))
                else:
                    metadata.add(name, unicode(frame))
            elif frameid == "TMCL":
                for role, name in frame.people:
                    if role or name:
                        metadata.add('performer:%s' % role, name)
            elif frameid == "TIPL":
                # If file is ID3v2.3, TIPL tag could contain TMCL
                # so we will test for TMCL values and add to TIPL if not TMCL
                for role, name in frame.people:
                    if role in self._tipl_roles and name:
                        metadata.add(self._tipl_roles[role], name)
                    else:
                        metadata.add('performer:%s' % role, name)
            elif frameid == 'TXXX':
                name = frame.desc
                if name in self.__translate_freetext:
                    name = self.__translate_freetext[name]
                elif ((name in self.__rtranslate) !=
                        (name in self.__rtranslate_freetext)):
                    # If the desc of a TXXX frame conflicts with the name of a
                    # Picard tag, load it into ~id3:TXXX:desc rather than desc.
                    #
                    # This basically performs an XOR, making sure that 'name'
                    # is in __rtranslate or __rtranslate_freetext, but not
                    # both. (Being in both implies we support reading it both
                    # ways.) Currently, the only tag in both is license.
                    name = '~id3:TXXX:' + name
                for text in frame.text:
                    metadata.add(name, unicode(text))
            elif frameid == 'USLT':
                name = 'lyrics'
                if frame.desc:
                    name += ':%s' % frame.desc
                metadata.add(name, unicode(frame.text))
            elif frameid == 'UFID' and frame.owner == 'http://musicbrainz.org':
                metadata['musicbrainz_recordingid'] = frame.data.decode('ascii', 'ignore')
            elif frameid == 'TRCK':
                value = frame.text[0].split('/')
                if len(value) > 1:
                    metadata['tracknumber'], metadata['totaltracks'] = value[:2]
                else:
                    metadata['tracknumber'] = value[0]
            elif frameid == 'TPOS':
                value = frame.text[0].split('/')
                if len(value) > 1:
                    metadata['discnumber'], metadata['totaldiscs'] = value[:2]
                else:
                    metadata['discnumber'] = value[0]
            elif frameid == 'APIC':
                extras = {
                    'desc': frame.desc,
                    'type': image_type_from_id3_num(frame.type)
                }
                metadata.add_image(frame.mime, frame.data, extras=extras)
            elif frameid == 'POPM':
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == config.setting['rating_user_email']:
                    rating = unicode(int(round(frame.rating / 255.0 * (config.setting['rating_steps'] - 1))))
                    metadata.add('~rating', rating)

        if 'date' in metadata:
            sanitized = sanitize_date(metadata.getall('date')[0])
            if sanitized:
                metadata['date'] = sanitized

        self._info(metadata, file)
        return metadata
Beispiel #34
0
    def _load(self, filename):
        log.debug("Loading file %r", filename)
        file = self._File(encode_filename(filename), ID3=compatid3.CompatID3)
        tags = file.tags or {}
        # upgrade custom 2.3 frames to 2.4
        for old, new in self.__upgrade.items():
            if old in tags and new not in tags:
                f = tags.pop(old)
                tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text))
        metadata = Metadata()
        for frame in tags.values():
            frameid = frame.FrameID
            if frameid in self.__translate:
                name = self.__translate[frameid]
                if frameid.startswith('T'):
                    for text in frame.text:
                        if text:
                            metadata.add(name, unicode(text))
                elif frameid == 'COMM':
                    for text in frame.text:
                        if text:
                            metadata.add('%s:%s' % (name, frame.desc),
                                         unicode(text))
                else:
                    metadata.add(name, unicode(frame))
            elif frameid == "TMCL":
                for role, name in frame.people:
                    if role or name:
                        metadata.add('performer:%s' % role, name)
            elif frameid == "TIPL":
                # If file is ID3v2.3, TIPL tag could contain TMCL
                # so we will test for TMCL values and add to TIPL if not TMCL
                for role, name in frame.people:
                    if role in self._tipl_roles and name:
                        metadata.add(self._tipl_roles[role], name)
                    else:
                        metadata.add('performer:%s' % role, name)
            elif frameid == 'TXXX':
                name = frame.desc
                if name in self.__translate_freetext:
                    name = self.__translate_freetext[name]
                elif ((name in self.__rtranslate) !=
                      (name in self.__rtranslate_freetext)):
                    # If the desc of a TXXX frame conflicts with the name of a
                    # Picard tag, load it into ~id3:TXXX:desc rather than desc.
                    #
                    # This basically performs an XOR, making sure that 'name'
                    # is in __rtranslate or __rtranslate_freetext, but not
                    # both. (Being in both implies we support reading it both
                    # ways.) Currently, the only tag in both is license.
                    name = '~id3:TXXX:' + name
                for text in frame.text:
                    metadata.add(name, unicode(text))
            elif frameid == 'USLT':
                name = 'lyrics'
                if frame.desc:
                    name += ':%s' % frame.desc
                metadata.add(name, unicode(frame.text))
            elif frameid == 'UFID' and frame.owner == 'http://musicbrainz.org':
                metadata['musicbrainz_recordingid'] = frame.data.decode(
                    'ascii', 'ignore')
            elif frameid == 'TRCK':
                value = frame.text[0].split('/')
                if len(value) > 1:
                    metadata['tracknumber'], metadata[
                        'totaltracks'] = value[:2]
                else:
                    metadata['tracknumber'] = value[0]
            elif frameid == 'TPOS':
                value = frame.text[0].split('/')
                if len(value) > 1:
                    metadata['discnumber'], metadata['totaldiscs'] = value[:2]
                else:
                    metadata['discnumber'] = value[0]
            elif frameid == 'APIC':
                extras = {
                    'desc': frame.desc,
                    'type': image_type_from_id3_num(frame.type)
                }
                metadata.add_image(frame.mime, frame.data, extras=extras)
            elif frameid == 'POPM':
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == config.setting['rating_user_email']:
                    rating = unicode(
                        int(
                            round(frame.rating / 255.0 *
                                  (config.setting['rating_steps'] - 1))))
                    metadata.add('~rating', rating)

        if 'date' in metadata:
            sanitized = sanitize_date(metadata.getall('date')[0])
            if sanitized:
                metadata['date'] = sanitized

        self._info(metadata, file)
        return metadata
Beispiel #35
0
    def _finalize_loading(self, error):
        if error:
            self.metadata.clear()
            self.metadata['album'] = _("[could not load album %s]") % self.id
            del self._new_metadata
            del self._new_tracks
            self.update()
            return

        if self._requests > 0:
            return

        if not self._tracks_loaded:
            artists = set()
            totalalbumtracks = 0

            djmix_ars = {}
            if hasattr(self._new_metadata, "_djmix_ars"):
                djmix_ars = self._new_metadata._djmix_ars

            for medium_node in self._release_node.medium_list[0].medium:
                mm = Metadata()
                mm.copy(self._new_metadata)
                medium_to_metadata(medium_node, mm)
                totalalbumtracks += int(mm["totaltracks"])

                for dj in djmix_ars.get(mm["discnumber"], []):
                    mm.add("djmixer", dj)

                for track_node in medium_node.track_list[0].track:
                    track = Track(track_node.recording[0].id, self)
                    self._new_tracks.append(track)

                    # Get track metadata
                    tm = track.metadata
                    tm.copy(mm)
                    track_to_metadata(track_node, track, self.config)
                    track._customize_metadata()

                    self._new_metadata.length += tm.length
                    artists.add(tm["musicbrainz_artistid"])

                    # Run track metadata plugins
                    try:
                        run_track_metadata_processors(self, tm, self._release_node, track_node)
                    except:
                        self.log.error(traceback.format_exc())

            totalalbumtracks = str(totalalbumtracks)

            for track in self._new_tracks:
                track.metadata["~totalalbumtracks"] = totalalbumtracks
                if len(artists) > 1:
                    track.metadata["compilation"] = "1"

            del self._release_node
            self._tracks_loaded = True

        if not self._requests:
            # Prepare parser for user's script
            if self.config.setting["enable_tagger_script"]:
                script = self.config.setting["tagger_script"]
                if script:
                    parser = ScriptParser()
                    for track in self._new_tracks:
                        # Run tagger script for each track
                        try:
                            parser.eval(script, track.metadata)
                        except:
                            self.log.error(traceback.format_exc())
                        # Strip leading/trailing whitespace
                        track.metadata.strip_whitespace()
                    # Run tagger script for the album itself
                    try:
                        parser.eval(script, self._new_metadata)
                    except:
                        self.log.error(traceback.format_exc())
                    self._new_metadata.strip_whitespace()

            for track in self.tracks:
                for file in list(track.linked_files):
                    file.move(self.unmatched_files)
            self.metadata = self._new_metadata
            self.tracks = self._new_tracks
            del self._new_metadata
            del self._new_tracks
            self.loaded = True
            self.match_files(self.unmatched_files.files)
            self.update()
            self.tagger.window.set_statusbar_message(_('Album %s loaded'), self.id, timeout=3000)
            while self._after_load_callbacks.qsize() > 0:
                func = self._after_load_callbacks.get()
                func()
Beispiel #36
0
class MetadataTest(PicardTestCase):

    original = None
    tags = []

    def setUp(self):
        super().setUp()
        config.setting = settings.copy()
        self.metadata = Metadata()
        self.metadata["single1"] = "single1-value"
        self.metadata.add_unique("single2", "single2-value")
        self.metadata.add_unique("single2", "single2-value")
        self.multi1 = ["multi1-value", "multi1-value"]
        self.metadata.add("multi1", self.multi1[0])
        self.metadata.add("multi1", self.multi1[1])
        self.multi2 = ["multi2-value1", "multi2-value2"]
        self.metadata["multi2"] = self.multi2
        self.multi3 = ["multi3-value1", "multi3-value2"]
        self.metadata.set("multi3", self.multi3)
        self.metadata["~hidden"] = "hidden-value"

    def tearDown(self):
        pass

    def test_metadata_setitem(self):
        self.assertEqual(["single1-value"], dict.get(self.metadata, "single1"))
        self.assertEqual(["single2-value"], dict.get(self.metadata, "single2"))
        self.assertEqual(self.multi1, dict.get(self.metadata, "multi1"))
        self.assertEqual(self.multi2, dict.get(self.metadata, "multi2"))
        self.assertEqual(self.multi3, dict.get(self.metadata, "multi3"))
        self.assertEqual(["hidden-value"], dict.get(self.metadata, "~hidden"))

    def test_metadata_get(self):
        self.assertEqual("single1-value", self.metadata["single1"])
        self.assertEqual("single1-value", self.metadata.get("single1"))
        self.assertEqual(["single1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata.get("multi1"))
        self.assertEqual(self.multi1, self.metadata.getall("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))

        self.assertEqual(dict.items(self.metadata), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in dict.items(self.metadata) for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_metadata_delete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

    def test_metadata_implicit_delete(self):
        self.metadata["single2"] = ""
        self.assertNotIn("single2", self.metadata)
        self.assertIn("single2", self.metadata.deleted_tags)

        self.metadata["unknown"] = ""
        self.assertNotIn("unknown", self.metadata)
        self.assertNotIn("unknown", self.metadata.deleted_tags)

    def test_metadata_set_explicit_empty(self):
        self.metadata.delete("single1")
        self.metadata.set("single1", [])
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)
        self.assertEqual([], self.metadata.getall("single1"))

    def test_metadata_undelete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

        self.metadata["single1"] = "value1"
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_update(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.update(self.metadata)
        self.assertIn("old", m)
        self.assertNotIn("single1", m)
        self.assertIn("single1", m.deleted_tags)
        self.assertEqual("single2-value", m["single2"])
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)

        self.metadata["old"] = "old-value"
        for (key, value) in dict.items(self.metadata):
            self.assertIn(key, m)
            self.assertEqual(value, dict.get(m, key))
        for (key, value) in dict.items(m):
            self.assertIn(key, self.metadata)
            self.assertEqual(value, dict.get(self.metadata, key))

    def test_metadata_clear(self):
        self.metadata.clear()
        self.assertEqual(0, len(self.metadata))

    def test_metadata_clear_deleted(self):
        self.metadata.delete("single1")
        self.assertIn("single1", self.metadata.deleted_tags)
        self.metadata.clear_deleted()
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_applyfunc(self):
        def func(x): return x[1:]
        self.metadata.apply_func(func)

        self.assertEqual("ingle1-value", self.metadata["single1"])
        self.assertEqual("ingle1-value", self.metadata.get("single1"))
        self.assertEqual(["ingle1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata.get("multi1"))
        self.assertEqual(list(map(func, self.multi1)), self.metadata.getall("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))

        self.assertEqual(dict.items(self.metadata), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in dict.items(self.metadata) for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_length_score(self):
        results = [(20000, 0, 0.333333333333),
                   (20000, 10000, 0.666666666667),
                   (20000, 20000, 1.0),
                   (20000, 30000, 0.666666666667),
                   (20000, 40000, 0.333333333333),
                   (20000, 50000, 0.0)]
        for (a, b, expected) in results:
            actual = Metadata.length_score(a, b)
            self.assertAlmostEqual(expected, actual,
                                   msg="a={a}, b={b}".format(a=a, b=b))

    def test_compare_is_equal(self):
        m1 = Metadata()
        m1["title"] = "title1"
        m1["tracknumber"] = "2"
        m1.length = 360
        m2 = Metadata()
        m2["title"] = "title1"
        m2["tracknumber"] = "2"
        m2.length = 360
        self.assertEqual(m1.compare(m2), m2.compare(m1))
        self.assertEqual(m1.compare(m2), 1)

    def test_compare_lengths(self):
        m1 = Metadata()
        m1.length = 360
        m2 = Metadata()
        m2.length = 300
        self.assertAlmostEqual(m1.compare(m2), 0.998)

    def test_compare_tracknumber_difference(self):
        m1 = Metadata()
        m1["tracknumber"] = "1"
        m2 = Metadata()
        m2["tracknumber"] = "2"
        self.assertEqual(m1.compare(m2), 0)

    def test_compare_deleted(self):
        m1 = Metadata()
        m1["artist"] = "TheArtist"
        m1["title"] = "title1"
        m2 = Metadata()
        m2["artist"] = "TheArtist"
        m2.delete("title")
        self.assertTrue(m1.compare(m2) < 1)
Beispiel #37
0
class MetadataTest(PicardTestCase):

    original = None
    tags = []

    def setUp(self):
        super().setUp()
        config.setting = settings.copy()
        self.metadata = Metadata()
        self.metadata["single1"] = "single1-value"
        self.metadata.add_unique("single2", "single2-value")
        self.metadata.add_unique("single2", "single2-value")
        self.multi1 = ["multi1-value", "multi1-value"]
        self.metadata.add("multi1", self.multi1[0])
        self.metadata.add("multi1", self.multi1[1])
        self.multi2 = ["multi2-value1", "multi2-value2"]
        self.metadata["multi2"] = self.multi2
        self.multi3 = ["multi3-value1", "multi3-value2"]
        self.metadata.set("multi3", self.multi3)
        self.metadata["~hidden"] = "hidden-value"

        self.metadata_d1 = Metadata({'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': ''})
        self.metadata_d2 = Metadata({'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': 'z'})
        self.metadata_d3 = Metadata({'c': 3, 'd': ['u', 'w'], 'x': 'p'})

    def tearDown(self):
        pass

    def test_metadata_setitem(self):
        self.assertEqual(["single1-value"], self.metadata.getraw("single1"))
        self.assertEqual(["single2-value"], self.metadata.getraw("single2"))
        self.assertEqual(self.multi1, self.metadata.getraw("multi1"))
        self.assertEqual(self.multi2, self.metadata.getraw("multi2"))
        self.assertEqual(self.multi3, self.metadata.getraw("multi3"))
        self.assertEqual(["hidden-value"], self.metadata.getraw("~hidden"))

    def test_metadata_set_all_values_as_string(self):
        for val in (0, 2, True):
            str_val = str(val)
            self.metadata.set('val1', val)
            self.assertEqual([str_val], self.metadata.getraw("val1"))
            self.metadata['val2'] = val
            self.assertEqual([str_val], self.metadata.getraw("val2"))
            del self.metadata['val3']
            self.metadata.add('val3', val)
            self.assertEqual([str_val], self.metadata.getraw("val3"))
            del self.metadata['val4']
            self.metadata.add_unique('val4', val)
            self.assertEqual([str_val], self.metadata.getraw("val4"))

    def test_metadata_get(self):
        self.assertEqual("single1-value", self.metadata["single1"])
        self.assertEqual("single1-value", self.metadata.get("single1"))
        self.assertEqual(["single1-value"], self.metadata.getall("single1"))
        self.assertEqual(["single1-value"], self.metadata.getraw("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata.get("multi1"))
        self.assertEqual(self.multi1, self.metadata.getall("multi1"))
        self.assertEqual(self.multi1, self.metadata.getraw("multi1"))

        self.assertEqual("", self.metadata["nonexistent"])
        self.assertEqual(None, self.metadata.get("nonexistent"))
        self.assertEqual([], self.metadata.getall("nonexistent"))
        self.assertRaises(KeyError, self.metadata.getraw, "nonexistent")

        self.assertEqual(self.metadata._store.items(), self.metadata.rawitems())
        metadata_items = [(x, z) for (x, y) in self.metadata.rawitems() for z in y]
        self.assertEqual(metadata_items, list(self.metadata.items()))

    def test_metadata_unset(self):
        self.metadata.unset("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)
        self.assertRaises(KeyError, self.metadata.unset, 'unknown_tag')

    def test_metadata_delete(self):
        del self.metadata["single1"]
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

    def test_metadata_legacy_delete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

    def test_metadata_implicit_delete(self):
        self.metadata["single2"] = ""
        self.assertNotIn("single2", self.metadata)
        self.assertIn("single2", self.metadata.deleted_tags)

        self.metadata["unknown"] = ""
        self.assertNotIn("unknown", self.metadata)
        self.assertNotIn("unknown", self.metadata.deleted_tags)

    def test_metadata_undelete(self):
        self.metadata.delete("single1")
        self.assertNotIn("single1", self.metadata)
        self.assertIn("single1", self.metadata.deleted_tags)

        self.metadata["single1"] = "value1"
        self.assertIn("single1", self.metadata)
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_normalize_tag(self):
        self.assertEqual('sometag', Metadata.normalize_tag('sometag'))
        self.assertEqual('sometag', Metadata.normalize_tag('sometag:'))
        self.assertEqual('sometag', Metadata.normalize_tag('sometag::'))
        self.assertEqual('sometag:desc', Metadata.normalize_tag('sometag:desc'))

    def test_metadata_tag_trailing_colon(self):
        self.metadata['tag:'] = 'Foo'
        self.assertIn('tag', self.metadata)
        self.assertIn('tag:', self.metadata)
        self.assertEqual('Foo', self.metadata['tag'])
        self.assertEqual('Foo', self.metadata['tag:'])
        del self.metadata['tag']
        self.assertNotIn('tag', self.metadata)
        self.assertNotIn('tag:', self.metadata)

    def test_metadata_copy(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.copy(self.metadata)
        self.assertEqual(self.metadata._store, m._store)
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
        self.assertEqual(self.metadata.length, m.length)
        self.assertEqual(self.metadata.images, m.images)

    def test_metadata_copy_without_images(self):
        m = Metadata()
        m.copy(self.metadata, copy_images=False)
        self.assertEqual(self.metadata._store, m._store)
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
        self.assertEqual(self.metadata.length, m.length)
        self.assertEqual(ImageList(), m.images)

    def test_metadata_update(self):
        m = Metadata()
        m["old"] = "old-value"
        self.metadata.delete("single1")
        m.update(self.metadata)
        self.assertIn("old", m)
        self.assertNotIn("single1", m)
        self.assertIn("single1", m.deleted_tags)
        self.assertEqual("single2-value", m["single2"])
        self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
        self.assertEqual(self.metadata.images, m.images)

        self.metadata["old"] = "old-value"
        self.assertEqual(self.metadata._store, m._store)

    def test_metadata_clear(self):
        self.metadata.clear()
        self.assertEqual(0, len(self.metadata))

    def test_metadata_clear_deleted(self):
        self.metadata.delete("single1")
        self.assertIn("single1", self.metadata.deleted_tags)
        self.metadata.clear_deleted()
        self.assertNotIn("single1", self.metadata.deleted_tags)

    def test_metadata_applyfunc(self):
        def func(x):
            return x[1:]
        self.metadata.apply_func(func)

        self.assertEqual("ingle1-value", self.metadata["single1"])
        self.assertEqual("ingle1-value", self.metadata.get("single1"))
        self.assertEqual(["ingle1-value"], self.metadata.getall("single1"))

        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata["multi1"])
        self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata.get("multi1"))
        self.assertEqual(list(map(func, self.multi1)), self.metadata.getall("multi1"))

    def test_metadata_applyfunc_preserve_tags(self):
        self.assertTrue(len(PRESERVED_TAGS) > 0)
        m = Metadata()
        m[PRESERVED_TAGS[0]] = 'value1'
        m['not_preserved'] = 'value2'

        def func(x):
            return x[1:]
        m.apply_func(func)

        self.assertEqual("value1", m[PRESERVED_TAGS[0]])
        self.assertEqual("alue2", m['not_preserved'])

    def test_metadata_applyfunc_delete_tags(self):
        def func(x):
            return None
        metadata = Metadata(self.metadata)
        metadata.apply_func(func)
        self.assertEqual(0, len(metadata.rawitems()))
        self.assertEqual(self.metadata.keys(), metadata.deleted_tags)

    def test_length_score(self):
        results = [(20000, 0, 0.333333333333),
                   (20000, 10000, 0.666666666667),
                   (20000, 20000, 1.0),
                   (20000, 30000, 0.666666666667),
                   (20000, 40000, 0.333333333333),
                   (20000, 50000, 0.0)]
        for (a, b, expected) in results:
            actual = Metadata.length_score(a, b)
            self.assertAlmostEqual(expected, actual,
                                   msg="a={a}, b={b}".format(a=a, b=b))

    def test_compare_is_equal(self):
        m1 = Metadata()
        m1["title"] = "title1"
        m1["tracknumber"] = "2"
        m1.length = 360
        m2 = Metadata()
        m2["title"] = "title1"
        m2["tracknumber"] = "2"
        m2.length = 360
        self.assertEqual(m1.compare(m2), m2.compare(m1))
        self.assertEqual(m1.compare(m2), 1)

    def test_compare_with_ignored(self):
        m1 = Metadata()
        m1["title"] = "title1"
        m1["tracknumber"] = "2"
        m1.length = 360
        m2 = Metadata()
        m2["title"] = "title1"
        m2["tracknumber"] = "3"
        m2.length = 300
        self.assertNotEqual(m1.compare(m2), 1)
        self.assertEqual(m1.compare(m2, ignored=['tracknumber', '~length']), 1)

    def test_compare_lengths(self):
        m1 = Metadata()
        m1.length = 360
        m2 = Metadata()
        m2.length = 300
        self.assertAlmostEqual(m1.compare(m2), 0.998)

    def test_compare_tracknumber_difference(self):
        m1 = Metadata()
        m1["tracknumber"] = "1"
        m2 = Metadata()
        m2["tracknumber"] = "2"
        m3 = Metadata()
        m3["tracknumber"] = "2"
        self.assertEqual(m1.compare(m2), 0)
        self.assertEqual(m2.compare(m3), 1)

    def test_compare_discnumber_difference(self):
        m1 = Metadata()
        m1["discnumber"] = "1"
        m2 = Metadata()
        m2["discnumber"] = "2"
        m3 = Metadata()
        m3["discnumber"] = "2"
        self.assertEqual(m1.compare(m2), 0)
        self.assertEqual(m2.compare(m3), 1)

    def test_compare_deleted(self):
        m1 = Metadata()
        m1["artist"] = "TheArtist"
        m1["title"] = "title1"
        m2 = Metadata()
        m2["artist"] = "TheArtist"
        m2.delete("title")
        self.assertTrue(m1.compare(m2) < 1)

    def test_strip_whitespace(self):
        m1 = Metadata()
        m1["artist"] = "  TheArtist  "
        m1["title"] = "\t\u00A0  tit le1 \r\n"
        m1["genre"] = " \t"
        m1.strip_whitespace()
        self.assertEqual(m1["artist"], "TheArtist")
        self.assertEqual(m1["title"], "tit le1")

    def test_metadata_mapping_init(self):
        d = {'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': '', 'z': {'u', 'w'}}
        deleted_tags = set('c')
        m = Metadata(d, deleted_tags=deleted_tags, length=1234)
        self.assertIn('a', m)
        self.assertEqual(m.getraw('a'), ['b'])
        self.assertEqual(m['d'], MULTI_VALUED_JOINER.join(d['d']))
        self.assertNotIn('c', m)
        self.assertNotIn('length', m)
        self.assertIn('c', m.deleted_tags)
        self.assertEqual(m.length, 1234)

    def test_metadata_mapping_init_zero(self):
        m = Metadata(tag1='a', tag2=0, tag3='', tag4=None)
        m['tag5'] = 0
        m['tag1'] = ''
        self.assertIn('tag1', m.deleted_tags)
        self.assertEqual(m['tag2'], '0')
        self.assertNotIn('tag3', m)
        self.assertNotIn('tag4', m)
        self.assertEqual(m['tag5'], '0')

    def test_metadata_mapping_del(self):
        m = self.metadata_d1
        self.assertEqual(m.getraw('a'), ['b'])
        self.assertNotIn('a', m.deleted_tags)

        self.assertNotIn('x', m.deleted_tags)
        self.assertRaises(KeyError, m.getraw, 'x')

        del m['a']
        self.assertRaises(KeyError, m.getraw, 'a')
        self.assertIn('a', m.deleted_tags)

        # NOTE: historic behavior of Metadata.delete()
        # an attempt to delete an non-existing tag, will add it to the list
        # of deleted tags
        # so this will not raise a KeyError
        # as is it differs from dict or even defaultdict behavior
        del m['unknown']
        self.assertIn('unknown', m.deleted_tags)

    def test_metadata_mapping_iter(self):
        self.assertEqual(set(self.metadata_d1), {'a', 'c', 'd'})

    def test_metadata_mapping_keys(self):
        self.assertEqual(set(self.metadata_d1.keys()), {'a', 'c', 'd'})

    def test_metadata_mapping_values(self):
        self.assertEqual(set(self.metadata_d1.values()), {'b', '2', 'x; y'})

    def test_metadata_mapping_len(self):
        m = self.metadata_d1
        self.assertEqual(len(m), 3)
        del m['x']
        self.assertEqual(len(m), 3)
        del m['c']
        self.assertEqual(len(m), 2)

    def _check_mapping_update(self, m):
        self.assertEqual(m['a'], 'b')
        self.assertEqual(m['c'], '3')
        self.assertEqual(m.getraw('d'), ['u', 'w'])
        self.assertEqual(m['x'], '')
        self.assertIn('x', m.deleted_tags)

    def test_metadata_mapping_update(self):
        # update from Metadata
        m = self.metadata_d2
        m2 = self.metadata_d3

        del m2['x']
        m.update(m2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_dict(self):
        # update from dict
        m = self.metadata_d2

        d2 = {'c': 3, 'd': ['u', 'w'], 'x': ''}

        m.update(d2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_tuple(self):
        # update from tuple
        m = self.metadata_d2

        d2 = (('c', 3), ('d', ['u', 'w']), ('x', ''))

        m.update(d2)
        self._check_mapping_update(m)

    def test_metadata_mapping_update_dictlike(self):
        # update from kwargs
        m = self.metadata_d2

        m.update(c=3, d=['u', 'w'], x='')
        self._check_mapping_update(m)

    def test_metadata_mapping_update_noparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(TypeError, m.update)
        self.assertEqual(m['a'], 'b')

    def test_metadata_mapping_update_intparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(TypeError, m.update, 123)

    def test_metadata_mapping_update_strparam(self):
        # update without parameter
        m = self.metadata_d2

        self.assertRaises(ValueError, m.update, 'abc')

    def test_metadata_mapping_update_kw(self):
        m = Metadata(tag1='a', tag2='b')
        m.update(tag1='c')
        self.assertEqual(m['tag1'], 'c')
        self.assertEqual(m['tag2'], 'b')
        m.update(tag2='')
        self.assertIn('tag2', m.deleted_tags)

    def test_metadata_mapping_update_kw_del(self):
        m = Metadata(tag1='a', tag2='b')
        del m['tag1']

        m2 = Metadata(tag1='c', tag2='d')
        del m2['tag2']

        m.update(m2)
        self.assertEqual(m['tag1'], 'c')
        self.assertNotIn('tag2', m)
        self.assertNotIn('tag1', m.deleted_tags)
        self.assertIn('tag2', m.deleted_tags)

    def test_metadata_mapping_images(self):
        image1 = create_image(b'A', comment='A')
        image2 = create_image(b'B', comment='B')

        m1 = Metadata(a='b', length=1234, images=[image1])
        self.assertEqual(m1.images[0], image1)
        self.assertEqual(len(m1), 2)  # one tag, one image

        m1.images.append(image2)
        self.assertEqual(m1.images[1], image2)

        m1.images.pop(0)
        self.assertEqual(m1.images[0], image2)

        m2 = Metadata(a='c', length=4567, images=[image1])
        m1.update(m2)
        self.assertEqual(m1.images[0], image1)

        m1.images.pop(0)
        self.assertEqual(len(m1), 1)  # one tag, zero image
        self.assertFalse(m1.images)

    def test_metadata_mapping_iterable(self):
        m = Metadata(tag_tuple=('a', 0))
        m['tag_set'] = {'c', 'd'}
        m['tag_dict'] = {'e': 1, 'f': 2}
        m['tag_str'] = 'gh'
        self.assertIn('0', m.getraw('tag_tuple'))
        self.assertIn('c', m.getraw('tag_set'))
        self.assertIn('e', m.getraw('tag_dict'))
        self.assertIn('gh', m.getraw('tag_str'))

    def test_compare_to_release(self):
        release = load_test_json('release.json')
        metadata = Metadata()
        release_to_metadata(release, metadata)
        match = metadata.compare_to_release(release, Cluster.comparison_weights)
        self.assertEqual(1.0, match.similarity)
        self.assertEqual(release, match.release)

    def test_compare_to_release_with_score(self):
        release = load_test_json('release.json')
        metadata = Metadata()
        release_to_metadata(release, metadata)
        for score, sim in ((42, 0.42), ('42', 0.42), ('foo', 1.0), (None, 1.0)):
            release['score'] = score
            match = metadata.compare_to_release(release, Cluster.comparison_weights)
            self.assertEqual(sim, match.similarity)

    def test_weights_from_release_type_scores(self):
        release = load_test_json('release.json')
        parts = []
        weights_from_release_type_scores(parts, release, {'Album': 0.75}, 666)
        self.assertEqual(
            parts[0],
            (0.75, 666)
        )
        weights_from_release_type_scores(parts, release, {}, 666)
        self.assertEqual(
            parts[1],
            (0.5, 666)
        )
        del release['release-group']
        weights_from_release_type_scores(parts, release, {}, 777)
        self.assertEqual(
            parts[2],
            (0.0, 777)
        )

    def test_preferred_countries(self):
        release = load_test_json('release.json')
        parts = []
        weights_from_preferred_countries(parts, release, [], 666)
        self.assertFalse(parts)
        weights_from_preferred_countries(parts, release, ['FR'], 666)
        self.assertEqual(parts[0], (0.0, 666))
        weights_from_preferred_countries(parts, release, ['GB'], 666)
        self.assertEqual(parts[1], (1.0, 666))

    def test_preferred_formats(self):
        release = load_test_json('release.json')
        parts = []
        weights_from_preferred_formats(parts, release, [], 777)
        self.assertFalse(parts)
        weights_from_preferred_formats(parts, release, ['Digital Media'], 777)
        self.assertEqual(parts[0], (0.0, 777))
        weights_from_preferred_formats(parts, release, ['12" Vinyl'], 777)
        self.assertEqual(parts[1], (1.0, 777))

    def test_compare_to_track(self):
        track_json = load_test_json('track.json')
        track = Track(track_json['id'])
        track_to_metadata(track_json, track)
        match = track.metadata.compare_to_track(track_json, File.comparison_weights)
        self.assertEqual(1.0, match.similarity)
        self.assertEqual(track_json, match.track)

    def test_compare_to_track_with_score(self):
        track_json = load_test_json('track.json')
        track = Track(track_json['id'])
        track_to_metadata(track_json, track)
        for score, sim in ((42, 0.42), ('42', 0.42), ('foo', 1.0), (None, 1.0)):
            track_json['score'] = score
            match = track.metadata.compare_to_track(track_json, File.comparison_weights)
            self.assertEqual(sim, match.similarity)
Beispiel #38
0
    def _load_tracks(self):
        artists = set()
        all_media = []
        absolutetracknumber = 0

        va = self._new_metadata[
            'musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID

        djmix_ars = {}
        if hasattr(self._new_metadata, "_djmix_ars"):
            djmix_ars = self._new_metadata._djmix_ars

        for medium_node in self._release_node['media']:
            mm = Metadata()
            mm.copy(self._new_metadata)
            medium_to_metadata(medium_node, mm)
            format = medium_node.get('format')
            if format:
                all_media.append(format)

            for dj in djmix_ars.get(mm["discnumber"], []):
                mm.add("djmixer", dj)

            if va:
                mm["compilation"] = "1"
            else:
                del mm["compilation"]

            if 'discs' in medium_node:
                discids = [disc.get('id') for disc in medium_node['discs']]
                mm['~musicbrainz_discids'] = discids
                mm['musicbrainz_discid'] = list(
                    self._discids.intersection(discids))

            if "pregap" in medium_node:
                absolutetracknumber += 1
                mm['~discpregap'] = '1'
                extra_metadata = {
                    '~pregap': '1',
                    '~absolutetracknumber': absolutetracknumber,
                }
                self._finalize_loading_track(medium_node['pregap'], mm,
                                             artists, extra_metadata)

            track_count = medium_node['track-count']
            if track_count:
                tracklist_node = medium_node['tracks']
                for track_node in tracklist_node:
                    absolutetracknumber += 1
                    extra_metadata = {
                        '~absolutetracknumber': absolutetracknumber,
                    }
                    self._finalize_loading_track(track_node, mm, artists,
                                                 extra_metadata)

            if "data-tracks" in medium_node:
                for track_node in medium_node['data-tracks']:
                    absolutetracknumber += 1
                    extra_metadata = {
                        '~datatrack': '1',
                        '~absolutetracknumber': absolutetracknumber,
                    }
                    self._finalize_loading_track(track_node, mm, artists,
                                                 extra_metadata)

        totalalbumtracks = absolutetracknumber
        self._new_metadata['~totalalbumtracks'] = totalalbumtracks
        # Generate a list of unique media, but keep order of first appearance
        self._new_metadata['media'] = " / ".join(
            list(OrderedDict.fromkeys(all_media)))

        for track in self._new_tracks:
            track.metadata["~totalalbumtracks"] = totalalbumtracks
            if len(artists) > 1:
                track.metadata["~multiartist"] = "1"
        del self._release_node
        del self._release_artist_nodes
        self._tracks_loaded = True