from collections import defaultdict from dataclasses import dataclass from enum import Enum from typing import List, Optional, Type, Union, Generator, Dict, Any from urllib.parse import urlparse import pycountry import musicbrainzngs from bs4 import BeautifulSoup from ..connection import Connection from .abstract import Page from ..utils.enums import SourceType, ALL_SOURCE_TYPES from ..utils.enums.album import AlbumType, AlbumStatus from ..objects import ( Artist, Source, Song, Album, ID3Timestamp, FormattedText, Label, Target, DatabaseObject, Lyrics, Artwork ) from ..utils.config import logging_settings, main_settings from ..utils import string_processing, shared from ..utils.string_processing import clean_song_title from ..utils.support_classes.query import Query from ..utils.support_classes.download_result import DownloadResult class MusicbrainzTypes(Enum): ARTIST = "artist" RELEASE = "release" SONG = "track" @dataclass class MusicbrainzUrl: source_type: MusicbrainzTypes name_without_id: str name_with_id: str musicbrainz_id: str url: str class Musicbrainz(Page): SOURCE_TYPE = ALL_SOURCE_TYPES.MUSICBRAINZ HOST = "https://musicbrainz.org/" def __init__(self, *args, **kwargs): self.connection: Connection = Connection( host="https://musicbrainz.org/", logger=self.LOGGER, module="musicbrainz", ) self.stream_connection: Connection = Connection( host="https://musicbrainz.org/", logger=self.LOGGER, semantic_not_found=False, ) super().__init__(*args, **kwargs) def get_source_type(self, source: Source) -> Optional[Type[DatabaseObject]]: if source.url is None: return None musicbrainz_url = parse_url(source.url) # Has no labels, because afaik musicbrainz has no Labels musicbrainz_type_to_database_type = { musicbrainzTypes.SONG: Song, musicbrainzTypes.RELEASE: Album, musicbrainzTypes.ARTIST: Artist } return musicbrainz_type_to_database_type.get(musicbrainz_url.source_type) def parse_url(url: str) -> MusicbrainzUrl: parsed = urlparse(url) path = parsed.path.split("/") split_name = path[2].split("-") url_id = split_name[-1] name_for_url = "-".join(split_name[:-1]) try: type_enum = MusicbrainzTypes(path[1]) except ValueError as e: logging_settings["musicbrainz_logger"].warning(f"{path[1]} is not yet implemented, add it to MusicbrainzTypes") raise e return MusicbrainzUrl( source_type=type_enum, name_without_id=name_for_url, name_with_id=path[2], musicbrainz_id=url_id, url=url ) def general_search(self, search_query: str) -> List[DatabaseObject]: search_results = [] r = self.connection.get(f"https://musicbrainz.org/search?query={search_query}&type=artist&method=indexed", name="search_" + search_query) if r is None: return [] search_soup: BeautifulSoup = self.get_soup_from_response(r) def artist_search(self, artist: Artist) -> List[Artist]: artist_list = [] r = self.connection.get(f"https://musicbrainz.org/search?query={artist.name}&type=artist&method=indexed", name="search_" + artist.name) if r is None: return [] return artist_list def song_search(self, song: Song) -> List[Song]: song_list = [] r = self.connection.get(f"https://musicbrainz.org/search?query={song.title_string}&type=recording&method=indexed", name="search_" + song.title_string) if r is None: return [] return song_list def album_search(self, album: Album) -> List[Album]: album_list = [] r = self.connection.get(f"https://musicbrainz.org/search?query={album.title_string}&type=release_group&method=indexed", name="search_" + album.title_string) if r is None: return [] return album_list