def _find_item_in_lib(lib, track_name, artist_name): """Finds an Item in the library based on the track_name. The track_name is not guaranteed to be perfect (i.e. as soon on MB), so in that case we query MB and look for the track id and query our lib with that. """ # Query the library based on the track name query = MatchQuery('title', track_name) lib_results = lib._fetch(Item, query=query) # Maybe the provided track name isn't all too good # Search for the track on MusicBrainz, and use that info to retry our lib if not lib_results: mb_candidate = _get_mb_candidate(track_name, artist_name) if mb_candidate: query = OrQuery(( AndQuery(( MatchQuery('title', mb_candidate.title), MatchQuery('artist', mb_candidate.artist), )), MatchQuery('mb_trackid', mb_candidate.track_id) )) lib_results = lib._fetch(Item, query=query) if not lib_results: return None # If we get multiple Item results from our library, choose best match # using the distance if len(lib_results) > 1: return _get_best_match(lib_results, track_name, artist_name)[0] return lib_results[0]
def update_tags(self, playlist_dict, lib): with lib.transaction(): for query, playlist_tag in playlist_dict.items(): query = AndQuery([MatchQuery("artist", query[0]), MatchQuery("album", query[1]), MatchQuery("title", query[2])]) items = lib.items(query) if not items: self._log.warn(u"{} | track not found ({})", playlist_tag, query) continue for item in items: item.subsonic_playlist = playlist_tag item.try_sync(write=True, move=False)
def resource_object(artist_id): """Construct a JSON:API resource object for the given artist. Args: artist_id: A string which is the artist's name. """ # Get tracks where artist field exactly matches artist_id query = MatchQuery("artist", artist_id) tracks = current_app.config["lib"].items(query) if not tracks: return None # Get artist information from the first track # NOTE: It could be that the first track doesn't have a # MusicBrainz id but later tracks do, which isn't ideal. attributes = {} # Use aura => beets attribute map, e.g. artist => name for aura_attr, beets_attr in ARTIST_ATTR_MAP.items(): a = getattr(tracks[0], beets_attr) # Only set attribute if it's not None, 0, "", etc. # NOTE: This could mean required attributes are not set if a: attributes[aura_attr] = a relationships = { "tracks": { "data": [{ "type": "track", "id": str(t.id) } for t in tracks] } } album_query = MatchQuery("albumartist", artist_id) albums = current_app.config["lib"].albums(query=album_query) if len(albums) != 0: relationships["albums"] = { "data": [{ "type": "album", "id": str(a.id) } for a in albums] } return { "type": "artist", "id": artist_id, "attributes": attributes, "relationships": relationships, }
def resource_object(album): """Construct a JSON:API resource object from a beets Album. Args: album: A beets Album object. """ attributes = {} # Use aura => beets attribute name map for aura_attr, beets_attr in ALBUM_ATTR_MAP.items(): a = getattr(album, beets_attr) # Only set attribute if it's not None, 0, "", etc. # NOTE: This could mean required attributes are not set if a: attributes[aura_attr] = a # Get beets Item objects for all tracks in the album sorted by # track number. Sorting is not required but it's nice. query = MatchQuery("album_id", album.id) sort = FixedFieldSort("track", ascending=True) tracks = current_app.config["lib"].items(query, sort) # JSON:API one-to-many relationship to tracks on the album relationships = { "tracks": { "data": [{ "type": "track", "id": str(t.id) } for t in tracks] } } # Add images relationship if album has associated images if album.artpath: path = py3_path(album.artpath) filename = path.split("/")[-1] image_id = f"album-{album.id}-{filename}" relationships["images"] = { "data": [{ "type": "image", "id": image_id }] } # Add artist relationship if artist name is same on tracks # Tracks are used to define artists so don't albumartist # Check for all tracks in case some have featured artists if album.albumartist in [t.artist for t in tracks]: relationships["artists"] = { "data": [{ "type": "artist", "id": album.albumartist }] } return { "type": "album", "id": str(album.id), "attributes": attributes, "relationships": relationships, }
def retrieve_library_items(self): cmd_query = self.query parsed_cmd_query, parsed_ordering = parse_query_parts(cmd_query, Item) if self.cfg_force: full_query = parsed_cmd_query else: parsed_plg_query = OrQuery([ NumericQuery('year', '0'), MatchQuery('year', ''), NoneQuery('year'), NumericQuery('original_year', '0'), MatchQuery('original_year', ''), NoneQuery('original_year'), ]) full_query = AndQuery([parsed_cmd_query, parsed_plg_query]) self._say("Selection query: {}".format(full_query)) return self.lib.items(full_query, parsed_ordering)
def get_mean_value_for_artist(self, item: Item, field_name): answer = None query = MatchQuery('mb_artistid', item.get("mb_artistid")) items = self.lib.items(query) values = [] for it in items: if it.get(field_name): val = int(it.get(field_name)) if 0 < val < 2100: values.append(val) if values: answer = int(round(sum(values) / len(values))) return answer
def translate_filters(self): """Translate filters from request arguments to a beets Query.""" # The format of each filter key in the request parameter is: # filter[<attribute>]. This regex extracts <attribute>. pattern = re.compile(r"filter\[(?P<attribute>[a-zA-Z0-9_-]+)\]") queries = [] for key, value in request.args.items(): match = pattern.match(key) if match: # Extract attribute name from key aura_attr = match.group("attribute") # Get the beets version of the attribute name beets_attr = self.attribute_map.get(aura_attr, aura_attr) converter = self.get_attribute_converter(beets_attr) value = converter(value) # Add exact match query to list # Use a slow query so it works with all fields queries.append(MatchQuery(beets_attr, value, fast=False)) # NOTE: AURA doesn't officially support multiple queries return AndQuery(queries)