Пример #1
0
    def compare_to_track(self, track, weights):
        parts = []

        if 'title' in self:
            a = self['title']
            b = track.get('title', '')
            parts.append((similarity2(a, b), weights["title"]))

        if 'artist' in self:
            a = self['artist']
            artist_credits = track.get('artist-credit', [])
            b = artist_credit_from_node(artist_credits)[0]
            parts.append((similarity2(a, b), weights["artist"]))

        a = self.length
        if a > 0 and 'length' in track:
            b = track['length']
            score = self.length_score(a, b)
            parts.append((score, weights["length"]))

        releases = []
        if "releases" in track:
            releases = track['releases']

        search_score = get_score(track)
        if not releases:
            sim = linear_combination_of_weights(parts) * search_score
            return SimMatchTrack(similarity=sim,
                                 releasegroup=None,
                                 release=None,
                                 track=track)

        if 'isvideo' in weights:
            metadata_is_video = self['~video'] == '1'
            track_is_video = track.get('video', False)
            score = 1 if metadata_is_video == track_is_video else 0
            parts.append((score, weights['isvideo']))

        result = SimMatchTrack(similarity=-1,
                               releasegroup=None,
                               release=None,
                               track=None)
        for release in releases:
            release_parts = self.compare_to_release_parts(release, weights)
            sim = linear_combination_of_weights(parts +
                                                release_parts) * search_score
            if sim > result.similarity:
                rg = release[
                    'release-group'] if "release-group" in release else None
                result = SimMatchTrack(similarity=sim,
                                       releasegroup=rg,
                                       release=release,
                                       track=track)
        return result
Пример #2
0
    def compare_to_track(self, track, weights):
        parts = []

        if 'title' in self:
            a = self['title']
            b = track.get('title', '')
            parts.append((similarity2(a, b), weights["title"]))

        if 'artist' in self:
            a = self['artist']
            artist_credits = track.get('artist-credit', [])
            b = artist_credit_from_node(artist_credits)[0]
            parts.append((similarity2(a, b), weights["artist"]))

        a = self.length
        if a > 0 and 'length' in track:
            b = track['length']
            score = self.length_score(a, b)
            parts.append((score, weights["length"]))

        releases = []
        if "releases" in track:
            releases = track['releases']

        if not releases:
            sim = linear_combination_of_weights(parts)
            if 'score' in track:
                sim *= track['score'] / 100
            return SimMatchTrack(similarity=sim,
                                 releasegroup=None,
                                 release=None,
                                 track=track)

        result = SimMatchTrack(similarity=-1,
                               releasegroup=None,
                               release=None,
                               track=None)
        for release in releases:
            release_parts = self.compare_to_release_parts(release, weights)
            sim = linear_combination_of_weights(parts + release_parts)
            if 'score' in track:
                sim *= track['score'] / 100
            if sim > result.similarity:
                rg = release[
                    'release-group'] if "release-group" in release else None
                result = SimMatchTrack(similarity=sim,
                                       releasegroup=rg,
                                       release=release,
                                       track=track)
        return result
Пример #3
0
    def compare(self, other):
        parts = []

        if self.length and other.length:
            score = self.length_score(self.length, other.length)
            parts.append((score, 8))

        for name, weight in self.__weights:
            a = self[name]
            b = other[name]
            if a and b:
                if name in ('tracknumber', 'totaltracks'):
                    try:
                        ia = int(a)
                        ib = int(b)
                    except ValueError:
                        ia = a
                        ib = b
                    score = 1.0 - (int(ia != ib))
                else:
                    score = similarity2(a, b)
                parts.append((score, weight))
            elif (a and name in other.deleted_tags
                  or b and name in self.deleted_tags):
                parts.append((0, weight))
        return linear_combination_of_weights(parts)
Пример #4
0
 def compare_to_release(self, release, weights):
     """
     Compare metadata to a MusicBrainz release. Produces a probability as a
     linear combination of weights that the metadata matches a certain album.
     """
     parts = self.compare_to_release_parts(release, weights)
     return (linear_combination_of_weights(parts), release)
Пример #5
0
 def compare_to_release(self, release, weights):
     """
     Compare metadata to a MusicBrainz release. Produces a probability as a
     linear combination of weights that the metadata matches a certain album.
     """
     parts = self.compare_to_release_parts(release, weights)
     return (linear_combination_of_weights(parts), release)
Пример #6
0
    def compare(self, other):
        parts = []

        if self.length and other.length:
            score = self.length_score(self.length, other.length)
            parts.append((score, 8))

        for name, weight in self.__weights:
            a = self[name]
            b = other[name]
            if a and b:
                if name in ('tracknumber', 'totaltracks'):
                    try:
                        ia = int(a)
                        ib = int(b)
                    except ValueError:
                        ia = a
                        ib = b
                    score = 1.0 - (int(ia != ib))
                else:
                    score = similarity2(a, b)
                parts.append((score, weight))
            elif (a and name in other.deleted_tags
                  or b and name in self.deleted_tags):
                parts.append((0, weight))
        return linear_combination_of_weights(parts)
Пример #7
0
    def compare(self, other, ignored=None):
        parts = []
        if ignored is None:
            ignored = []

        if self.length and other.length and '~length' not in ignored:
            score = self.length_score(self.length, other.length)
            parts.append((score, 8))

        for name, weight in self.__weights:
            if name in ignored:
                continue
            a = self[name]
            b = other[name]
            if a and b:
                if name in {'tracknumber', 'totaltracks', 'discnumber', 'totaldiscs'}:
                    try:
                        ia = int(a)
                        ib = int(b)
                    except ValueError:
                        ia = a
                        ib = b
                    score = 1.0 - (int(ia != ib))
                else:
                    score = similarity2(a, b)
                parts.append((score, weight))
            elif (a and name in other.deleted_tags
                  or b and name in self.deleted_tags):
                parts.append((0, weight))
        return linear_combination_of_weights(parts)
Пример #8
0
 def compare_to_release(self, release, weights):
     """
     Compare metadata to a MusicBrainz release. Produces a probability as a
     linear combination of weights that the metadata matches a certain album.
     """
     parts = self.compare_to_release_parts(release, weights)
     sim = linear_combination_of_weights(parts) * get_score(release)
     return SimMatchRelease(similarity=sim, release=release)
Пример #9
0
 def compare_to_release(self, release, weights):
     """
     Compare metadata to a MusicBrainz release. Produces a probability as a
     linear combination of weights that the metadata matches a certain album.
     """
     parts = self.compare_to_release_parts(release, weights)
     sim = linear_combination_of_weights(parts)
     if 'score' in release:
         sim *= release['score'] / 100
     return (sim, release)
Пример #10
0
 def compare_to_release(self, release, weights):
     """
     Compare metadata to a MusicBrainz release. Produces a probability as a
     linear combination of weights that the metadata matches a certain album.
     """
     parts = self.compare_to_release_parts(release, weights)
     sim = linear_combination_of_weights(parts)
     if 'score' in release:
         sim *= release['score'] / 100
     return SimMatchRelease(similarity=sim, release=release)
Пример #11
0
    def compare_to_track(self, track, weights):
        parts = []

        if 'title' in self:
            a = self['title']
            b = track.title[0].text
            parts.append((similarity2(a, b), weights["title"]))

        if 'artist' in self:
            a = self['artist']
            b = artist_credit_from_node(track.artist_credit[0])[0]
            parts.append((similarity2(a, b), weights["artist"]))

        a = self.length
        if a > 0 and 'length' in track.children:
            b = int(track.length[0].text)
            score = 1.0 - min(abs(a - b), 30000) / 30000.0
            parts.append((score, weights["length"]))

        releases = []
        if "release_list" in track.children and "release" in track.release_list[
                0].children:
            releases = track.release_list[0].release

        if not releases:
            sim = linear_combination_of_weights(parts)
            return (sim, None, None, track)

        result = (-1, )
        for release in releases:
            release_parts = self.compare_to_release_parts(release, weights)
            sim = linear_combination_of_weights(parts + release_parts)
            if sim > result[0]:
                rg = release.release_group[
                    0] if "release_group" in release.children else None
                result = (sim, rg, release, track)

        return result
Пример #12
0
    def compare_to_track(self, track, weights):
        parts = []

        if 'title' in self:
            a = self['title']
            b = track.get('title', '')
            parts.append((similarity2(a, b), weights["title"]))

        if 'artist' in self:
            a = self['artist']
            artist_credits = track.get('artist-credit', [])
            b = artist_credit_from_node(artist_credits)[0]
            parts.append((similarity2(a, b), weights["artist"]))

        a = self.length
        if a > 0 and 'length' in track:
            b = track['length']
            score = self.length_score(a, b)
            parts.append((score, weights["length"]))

        releases = []
        if "releases" in track:
            releases = track['releases']

        if not releases:
            sim = linear_combination_of_weights(parts)
            return SimMatchTrack(similarity=sim, releasegroup=None, release=None, track=track)

        result = SimMatchTrack(similarity=-1, releasegroup=None, release=None, track=None)
        for release in releases:
            release_parts = self.compare_to_release_parts(release, weights)
            sim = linear_combination_of_weights(parts + release_parts)
            if 'score' in track:
                sim *= track['score'] / 100
            if sim > result.similarity:
                rg = release['release-group'] if "release-group" in release else None
                result = SimMatchTrack(similarity=sim, releasegroup=rg, release=release, track=track)
        return result
Пример #13
0
    def compare_to_track(self, track, weights):
        parts = []

        if 'title' in self:
            a = self['title']
            b = track['title']
            parts.append((similarity2(a, b), weights["title"]))

        if 'artist' in self:
            a = self['artist']
            b = artist_credit_from_node(track['artist-credit'])[0]
            parts.append((similarity2(a, b), weights["artist"]))

        a = self.length
        if a > 0 and 'length' in track:
            b = track['length']
            score = self.length_score(a, b)
            parts.append((score, weights["length"]))

        releases = []
        if "releases" in track:
            releases = track['releases']

        if not releases:
            sim = linear_combination_of_weights(parts)
            return (sim, None, None, track)

        result = (-1, )

        for release in releases:
            release_parts = self.compare_to_release_parts(release, weights)
            sim = linear_combination_of_weights(parts + release_parts)
            if sim > result[0]:
                rg = release[
                    'release-group'] if "release-group" in release else None
                result = (sim, rg, release, track)
        return result
Пример #14
0
    def compare_to_track(self, track, weights):
        parts = []

        if 'title' in self:
            a = self['title']
            b = track.title[0].text
            parts.append((similarity2(a, b), weights["title"]))

        if 'artist' in self:
            a = self['artist']
            b = artist_credit_from_node(track.artist_credit[0])[0]
            parts.append((similarity2(a, b), weights["artist"]))

        a = self.length
        if a > 0 and 'length' in track.children:
            b = int(track.length[0].text)
            score = 1.0 - min(abs(a - b), 30000) / 30000.0
            parts.append((score, weights["length"]))

        releases = []
        if "release_list" in track.children and "release" in track.release_list[0].children:
            releases = track.release_list[0].release

        if not releases:
            sim = linear_combination_of_weights(parts)
            return (sim, None, None, track)

        result = (-1,)
        for release in releases:
            release_parts = self.compare_to_release_parts(release, weights)
            sim = linear_combination_of_weights(parts + release_parts)
            if sim > result[0]:
                rg = release.release_group[0] if "release_group" in release.children else None
                result = (sim, rg, release, track)

        return result
Пример #15
0
    def compare_to_track(self, track, weights):
        parts = []

        if 'title' in self:
            a = self['title']
            b = track['title']
            parts.append((similarity2(a, b), weights["title"]))

        if 'artist' in self:
            a = self['artist']
            b = artist_credit_from_node(track['artist-credit'])[0]
            parts.append((similarity2(a, b), weights["artist"]))

        a = self.length
        if a > 0 and 'length' in track:
            b = track['length']
            score = 1.0 - min(abs(a - b), 30000) / 30000.0
            parts.append((score, weights["length"]))

        releases = []
        if "releases" in track:
            releases = track['releases']

        if not releases:
            sim = linear_combination_of_weights(parts)
            return (sim, None, None, track)

        result = (-1,)

        for release in releases:
            release_parts = self.compare_to_release_parts(release, weights)
            sim = linear_combination_of_weights(parts + release_parts)
            if sim > result[0]:
                rg = release['release-group'] if "release-group" in release else None
                result = (sim, rg, release, track)
        return result
Пример #16
0
def _translate_artist_node(node):
    config = get_config()
    transl, translsort = None, None
    if config.setting['translate_artist_names']:
        locale = config.setting["artist_locale"]
        lang = locale.split("_")[0]
        if "aliases" in node:
            result = (-1, (None, None))
            for alias in node['aliases']:
                if not alias["primary"]:
                    continue
                if "locale" not in alias:
                    continue
                parts = []
                if alias['locale'] == locale:
                    score = 0.8
                elif alias['locale'] == lang:
                    score = 0.6
                elif alias['locale'].split("_")[0] == lang:
                    score = 0.4
                else:
                    continue
                parts.append((score, 5))
                if alias["type"] == "Artist name":
                    score = 0.8
                elif alias["type"] == "Legal Name":
                    score = 0.5
                else:
                    # as 2014/09/19, only Artist or Legal names should have the
                    # Primary flag
                    score = 0.0
                parts.append((score, 5))
                comb = linear_combination_of_weights(parts)
                if comb > result[0]:
                    result = (comb, (alias['name'], alias["sort-name"]))
            transl, translsort = result[1]
        if not transl:
            translsort = node['sort-name']
            transl = translate_from_sortname(node['name'] or "", translsort)
    else:
        transl, translsort = node['name'], node['sort-name']
    return (transl, translsort)
Пример #17
0
def _translate_artist_node(node):
    transl, translsort = None, None
    if config.setting['translate_artist_names']:
        locale = config.setting["artist_locale"]
        lang = locale.split("_")[0]
        if "alias_list" in node.children:
            result = (-1, (None, None))
            for alias in node.alias_list[0].alias:
                if alias.attribs.get("primary") != "primary":
                    continue
                if "locale" not in alias.attribs:
                    continue
                parts = []
                if alias.locale == locale:
                    score = 0.8
                elif alias.locale == lang:
                    score = 0.6
                elif alias.locale.split("_")[0] == lang:
                    score = 0.4
                else:
                    continue
                parts.append((score, 5))
                if alias.attribs.get("type") == "Artist name":
                    score = 0.8
                elif alias.attribs.get("type") == "Legal Name":
                    score = 0.5
                else:
                    # as 2014/09/19, only Artist or Legal names should have the
                    # Primary flag
                    score = 0.0
                parts.append((score, 5))
                comb = linear_combination_of_weights(parts)
                if comb > result[0]:
                    result = (comb, (alias.text, alias.attribs["sort_name"]))
            transl, translsort = result[1]
        if not transl:
            translsort = node.sort_name[0].text
            transl = translate_from_sortname(node.name[0].text, translsort)
    else:
        transl, translsort = node.name[0].text, node.sort_name[0].text
    return (transl, translsort)
Пример #18
0
def _translate_artist_node(node):
    transl, translsort = None, None
    if config.setting['translate_artist_names']:
        locale = config.setting["artist_locale"]
        lang = locale.split("_")[0]
        if "alias_list" in node.children:
            result = (-1, (None, None))
            for alias in node.alias_list[0].alias:
                if alias.attribs.get("primary") != "primary":
                    continue
                if "locale" not in alias.attribs:
                    continue
                parts = []
                if alias.locale == locale:
                    score = 0.8
                elif alias.locale == lang:
                    score = 0.6
                elif alias.locale.split("_")[0] == lang:
                    score = 0.4
                else:
                    continue
                parts.append((score, 5))
                if alias.attribs.get("type") == u"Artist name":
                    score = 0.8
                elif alias.attribs.get("type") == u"Legal Name":
                    score = 0.5
                else:
                    # as 2014/09/19, only Artist or Legal names should have the
                    # Primary flag
                    score = 0.0
                parts.append((score, 5))
                comb = linear_combination_of_weights(parts)
                if comb > result[0]:
                    result = (comb, (alias.text, alias.attribs["sort_name"]))
            transl, translsort = result[1]
        if not transl:
            translsort = node.sort_name[0].text
            transl = translate_from_sortname(node.name[0].text, translsort)
    else:
        transl, translsort = node.name[0].text, node.sort_name[0].text
    return (transl, translsort)
Пример #19
0
def _translate_artist_node(node):
    transl, translsort = None, None
    if config.setting['translate_artist_names']:
        locale = config.setting["artist_locale"]
        lang = locale.split("_")[0]
        if "aliases" in node:
            result = (-1, (None, None))
            for alias in node['aliases']:
                if not alias["primary"]:
                    continue
                if "locale" not in alias:
                    continue
                parts = []
                if alias['locale'] == locale:
                    score = 0.8
                elif alias['locale'] == lang:
                    score = 0.6
                elif alias['locale'].split("_")[0] == lang:
                    score = 0.4
                else:
                    continue
                parts.append((score, 5))
                if alias["type"] == "Artist name":
                    score = 0.8
                elif alias["type"] == "Legal Name":
                    score = 0.5
                else:
                    # as 2014/09/19, only Artist or Legal names should have the
                    # Primary flag
                    score = 0.0
                parts.append((score, 5))
                comb = linear_combination_of_weights(parts)
                if comb > result[0]:
                    result = (comb, (alias['name'], alias["sort-name"]))
            transl, translsort = result[1]
        if not transl:
            translsort = node['sort-name']
            transl = translate_from_sortname(node['name'] or "", translsort)
    else:
        transl, translsort = node['name'], node['sort-name']
    return (transl, translsort)
Пример #20
0
    def compare(self, other):
        parts = []

        if self.length and other.length:
            score = 1.0 - min(abs(self.length - other.length), 30000) / 30000.0
            parts.append((score, 8))

        for name, weight in self.__weights:
            a = self[name]
            b = other[name]
            if a and b:
                if name in ('tracknumber', 'totaltracks'):
                    try:
                        ia = int(a)
                        ib = int(b)
                    except ValueError:
                        ia = a
                        ib = b
                    score = 1.0 - abs(cmp(ia, ib))
                else:
                    score = similarity2(a, b)
                parts.append((score, weight))

        return linear_combination_of_weights(parts)
Пример #21
0
 def test_5(self):
     parts = [(0.95, 100), (0.05, 399), (0.0, 1), (1.0, 0)]
     self.assertEqual(util.linear_combination_of_weights(parts), 0.2299)
Пример #22
0
 def test_4(self):
     parts = [(0.5, 4), (1.0, 1)]
     self.assertEqual(util.linear_combination_of_weights(parts), 0.6)
Пример #23
0
 def test_2(self):
     parts = [(0.0, 1), (0.0, 0), (1.0, 0)]
     self.assertEqual(util.linear_combination_of_weights(parts), 0.0)
Пример #24
0
 def test_0(self):
     parts = []
     self.assertEqual(util.linear_combination_of_weights(parts), 0.0)
Пример #25
0
def _translate_artist_node(node):
    config = get_config()
    transl, translsort = None, None
    if config.setting['translate_artist_names']:
        if config.setting['translate_artist_names_script_exception']:
            log_text = 'Script alpha characters found in "{0}": '.format(
                node["name"], )
            detected_scripts = detect_script_weighted(node["name"])
            if detected_scripts:
                log_text += "; ".join(
                    list("{0} ({1:.1f}%)".format(
                        scr_id, detected_scripts[scr_id] * 100)
                         for scr_id in detected_scripts))
            else:
                log_text += "None"
            log.debug(log_text)
            if detected_scripts:
                if config.setting["script_exceptions"]:
                    log_text = " found in selected scripts: " + "; ".join(
                        list("{0} ({1}%)".format(scr[0], scr[1])
                             for scr in config.setting["script_exceptions"]))
                    for script_id, script_weighting in config.setting[
                            "script_exceptions"]:
                        if script_id in detected_scripts and detected_scripts[
                                script_id] >= script_weighting / 100:
                            log.debug("Match" + log_text)
                            return node['name'], node['sort-name']
                    log.debug("No match" + log_text)
                else:
                    log.warning(
                        "No scripts selected for translation exception match check."
                    )

        def check_higher_score(locale_dict, locale, score):
            return locale not in locale_dict or score > locale_dict[locale][0]

        # Prepare dictionaries of available locale aliases
        full_locales = {}
        root_locales = {}
        if "aliases" in node:
            for alias in node['aliases']:
                if not alias["primary"]:
                    continue
                if "locale" not in alias:
                    continue
                full_locale = alias['locale']
                root_locale = full_locale.split("_")[0]
                full_parts = []
                root_parts = []
                score = 0.8
                full_parts.append((score, 5))
                if '_' in full_locale:
                    score = 0.4
                root_parts.append((score, 5))
                if alias["type"] == "Artist name":
                    score = 0.8
                elif alias["type"] == "Legal Name":
                    score = 0.5
                else:
                    # as 2014/09/19, only Artist or Legal names should have the
                    # Primary flag
                    score = 0.0
                full_parts.append((score, 5))
                root_parts.append((score, 5))
                comb = linear_combination_of_weights(full_parts)
                if check_higher_score(full_locales, full_locale, comb):
                    full_locales[full_locale] = (comb, (alias['name'],
                                                        alias["sort-name"]))
                comb = linear_combination_of_weights(root_parts)
                if check_higher_score(root_locales, root_locale, comb):
                    root_locales[root_locale] = (comb, (alias['name'],
                                                        alias["sort-name"]))

            # First pass to match full locale if available
            for locale in config.setting["artist_locales"]:
                if locale in full_locales:
                    return full_locales[locale][1]

            # Second pass to match root locale if available
            for locale in config.setting["artist_locales"]:
                lang = locale.split("_")[0]
                if lang in root_locales:
                    return root_locales[lang][1]

        # No matches found in available alias locales
        translsort = node['sort-name']
        transl = translate_from_sortname(node['name'] or "", translsort)
    else:
        transl, translsort = node['name'], node['sort-name']
    return (transl, translsort)
Пример #26
0
def _translate_artist_node(node):
    config = get_config()
    transl, translsort = None, None
    if config.setting['translate_artist_names']:
        if config.setting['translate_artist_names_script_exception']:
            threshhold = config.setting["artist_script_exception_weighting"] / 100
            detected_scripts = list_script_weighted(node["name"], threshhold)
            for script_id in config.setting["artist_script_exceptions"]:
                if script_id in detected_scripts:
                    return node['name'], node['sort-name']

        def check_higher_score(locale_dict, locale, score):
            return locale not in locale_dict or score > locale_dict[locale][0]

        # Prepare dictionaries of available locale aliases
        full_locales = {}
        root_locales = {}
        if "aliases" in node:
            for alias in node['aliases']:
                if not alias["primary"]:
                    continue
                if "locale" not in alias:
                    continue
                full_locale = alias['locale']
                root_locale = full_locale.split("_")[0]
                full_parts = []
                root_parts = []
                score = 0.8
                full_parts.append((score, 5))
                if '_' in full_locale:
                    score = 0.4
                root_parts.append((score, 5))
                if alias["type"] == "Artist name":
                    score = 0.8
                elif alias["type"] == "Legal Name":
                    score = 0.5
                else:
                    # as 2014/09/19, only Artist or Legal names should have the
                    # Primary flag
                    score = 0.0
                full_parts.append((score, 5))
                root_parts.append((score, 5))
                comb = linear_combination_of_weights(full_parts)
                if check_higher_score(full_locales, full_locale, comb):
                    full_locales[full_locale] = (comb, (alias['name'], alias["sort-name"]))
                comb = linear_combination_of_weights(root_parts)
                if check_higher_score(root_locales, root_locale, comb):
                    root_locales[root_locale] = (comb, (alias['name'], alias["sort-name"]))

            # First pass to match full locale if available
            for locale in config.setting["artist_locales"]:
                if locale in full_locales:
                    return full_locales[locale][1]

            # Second pass to match root locale if available
            for locale in config.setting["artist_locales"]:
                lang = locale.split("_")[0]
                if lang in root_locales:
                    return root_locales[lang][1]

        # No matches found in available alias locales
        translsort = node['sort-name']
        transl = translate_from_sortname(node['name'] or "", translsort)
    else:
        transl, translsort = node['name'], node['sort-name']
    return (transl, translsort)
Пример #27
0
 def test_0(self):
     parts = []
     self.assertEqual(util.linear_combination_of_weights(parts), 0.0)
Пример #28
0
 def test_5(self):
     parts = [(0.95, 100), (0.05, 399), (0.0, 1), (1.0, 0)]
     self.assertEqual(util.linear_combination_of_weights(parts), 0.2299)
Пример #29
0
 def test_4(self):
     parts = [(0.5, 4), (1.0, 1)]
     self.assertEqual(util.linear_combination_of_weights(parts), 0.6)
Пример #30
0
 def test_2(self):
     parts = [(0.0, 1), (0.0, 0), (1.0, 0)]
     self.assertEqual(util.linear_combination_of_weights(parts), 0.0)