completed track search

This commit is contained in:
2020-03-04 21:56:23 +00:00
parent 2af554ffa3
commit 8bb814e618
4 changed files with 200 additions and 104 deletions

25
poetry.lock generated
View File

@@ -1,3 +1,11 @@
[[package]]
category = "main"
description = "Addict is a dictionary whose items can be set using both attribute and item syntax."
name = "addict"
optional = false
python-versions = "*"
version = "2.2.1"
[[package]] [[package]]
category = "dev" category = "dev"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
@@ -202,6 +210,14 @@ version = ">=0.12"
[package.extras] [package.extras]
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
[[package]]
category = "main"
description = "Easy to use progress bars"
name = "progress"
optional = false
python-versions = "*"
version = "1.5"
[[package]] [[package]]
category = "dev" category = "dev"
description = "A full-screen, console-based Python debugger" description = "A full-screen, console-based Python debugger"
@@ -477,10 +493,14 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"] testing = ["jaraco.itertools", "func-timeout"]
[metadata] [metadata]
content-hash = "40cc154211436945480756e120f8b39bb419e373bc51da5c6bb4d1ab72db47c5" content-hash = "50cff0758d9f4bfa77596b28f6f5f0c7f1db897b7c0d615071379c288b1d110d"
python-versions = "^3.7" python-versions = "^3.7"
[metadata.files] [metadata.files]
addict = [
{file = "addict-2.2.1-py3-none-any.whl", hash = "sha256:1948c2a5d93ba6026eb91aef2c971234aaf72488a9c07ab8a7950f82ae30eea7"},
{file = "addict-2.2.1.tar.gz", hash = "sha256:f22493f056032f50e4931a82444fcba8ef74c8fc994c5d06aa546a1433c2b8b0"},
]
appdirs = [ appdirs = [
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
@@ -556,6 +576,9 @@ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
] ]
progress = [
{file = "progress-1.5.tar.gz", hash = "sha256:69ecedd1d1bbe71bf6313d88d1e6c4d2957b7f1d4f71312c211257f7dae64372"},
]
pudb = [ pudb = [
{file = "pudb-2019.2.tar.gz", hash = "sha256:e8f0ea01b134d802872184b05bffc82af29a1eb2f9374a277434b932d68f58dc"}, {file = "pudb-2019.2.tar.gz", hash = "sha256:e8f0ea01b134d802872184b05bffc82af29a1eb2f9374a277434b932d68f58dc"},
] ]

View File

@@ -8,6 +8,8 @@ authors = ["dtomlinson <dtomlinson@williamhill.co.uk>"]
python = "^3.7" python = "^3.7"
requests = "^2.23.0" requests = "^2.23.0"
musicbrainzngs = "^0.7.1" musicbrainzngs = "^0.7.1"
addict = "^2.2.1"
progress = "^1.5"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^5.2" pytest = "^5.2"
@@ -25,3 +27,6 @@ pyls-black = "^0.4.4"
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api" build-backend = "poetry.masonry.api"
[tool.poetry.plugins."console_scripts"]
"musicbrainzapi" = "musicbrainzapi.cli.cli:cli"

View File

@@ -2,13 +2,16 @@ from __future__ import annotations
from abc import ABC, abstractmethod, abstractstaticmethod from abc import ABC, abstractmethod, abstractstaticmethod
from dataclasses import dataclass from dataclasses import dataclass
from pprint import pprint from pprint import pprint
from typing import Union from typing import Union, List
import contextlib import contextlib
import itertools
from progress.spinner import PieSpinner
# from pprint import pprint # from pprint import pprint
import musicbrainzngs import musicbrainzngs
import click import click
import addict
from musicbrainzapi.api import authenticate from musicbrainzapi.api import authenticate
@@ -16,10 +19,6 @@ from musicbrainzapi.api import authenticate
class LyricsConcreteBuilder(ABC): class LyricsConcreteBuilder(ABC):
"""docstring for Lyrics""" """docstring for Lyrics"""
@abstractstaticmethod
def set_useragent():
authenticate.set_useragent()
@property @property
@abstractmethod @abstractmethod
def product(self) -> None: def product(self) -> None:
@@ -55,6 +54,22 @@ class LyricsConcreteBuilder(ABC):
def artist_id(self, artist_id: str) -> None: def artist_id(self, artist_id: str) -> None:
pass pass
@abstractstaticmethod
def set_useragent():
authenticate.set_useragent()
@abstractstaticmethod
def browse_releases(self) -> dict:
pass
@abstractstaticmethod
def get_album_info_list(self) -> list:
pass
@abstractstaticmethod
def paginate_results(self) -> list:
pass
@abstractmethod @abstractmethod
def __init__(self) -> None: def __init__(self) -> None:
pass pass
@@ -79,14 +94,14 @@ class LyricsConcreteBuilder(ABC):
def get_top_five_results(self) -> None: def get_top_five_results(self) -> None:
pass pass
@abstractmethod
def do_search_albums(self) -> None:
pass
class LyricsBuilder(LyricsConcreteBuilder): class LyricsBuilder(LyricsConcreteBuilder):
"""docstring for LyricsBuilder""" """docstring for LyricsBuilder"""
@staticmethod
def set_useragent() -> None:
authenticate.set_useragent()
@property @property
def product(self) -> Lyrics: def product(self) -> Lyrics:
product = self._product product = self._product
@@ -119,6 +134,76 @@ class LyricsBuilder(LyricsConcreteBuilder):
self._artist_id = artist_id self._artist_id = artist_id
self._product.artist_id = artist_id self._product.artist_id = artist_id
@property
def all_tracks(self) -> set:
return self._all_tracks
@all_tracks.setter
def all_tracks(self, all_tracks: set) -> None:
self._all_tracks = all_tracks
self._product.all_tracks = all_tracks
@property
def all_albums_with_tracks(self) -> list:
return self._all_albums_with_tracks
@all_albums_with_tracks.setter
def all_albums_with_tracks(self, all_albums_with_tracks: list) -> None:
self._all_albums_with_tracks = all_albums_with_tracks
self._product.all_albums_with_tracks = all_albums_with_tracks
@staticmethod
def set_useragent() -> None:
authenticate.set_useragent()
@staticmethod
def browse_releases(
artist_id: str,
limit: int,
release_type: list,
offset: Union[int, None] = None,
includes: Union[List[str, None]] = list(),
) -> dict:
releases = musicbrainzngs.browse_releases(
artist=artist_id,
limit=limit,
release_type=release_type,
offset=offset,
includes=includes,
)
return releases
@staticmethod
def get_album_info_list(
album_info_list: list, album_tracker: set, releases: addict.Dict
) -> list:
for i in releases['release-list']:
_throwaway_dict = addict.Dict()
_throwaway_dict.album = i.title
_throwaway_dict.year = i.date.split('-')[0]
_throwaway_dict.tracks = [
j.recording.title for j in i['medium-list'][0]['track-list']
]
if i.title not in album_tracker:
album_tracker.add(i.title)
album_info_list.append(_throwaway_dict)
else:
pass
return album_info_list, album_tracker
@staticmethod
def paginate_results(
releases: addict.Dict, duplicated_tracks: list
) -> List:
tracks = [
j.recording.title
for i in releases['release-list']
for j in i['medium-list'][0]['track-list']
]
for i in itertools.chain(tracks):
duplicated_tracks.append(i)
return duplicated_tracks
def __init__(self) -> None: def __init__(self) -> None:
self.reset() self.reset()
@@ -159,95 +244,78 @@ class LyricsBuilder(LyricsConcreteBuilder):
) )
return self return self
@staticmethod def do_search_albums(self) -> None:
def browse_releases( album_info_list = list()
artist_id: str, album_tracker = set()
limit: int, duplicated_tracks = list()
release_type: list, limit, offset, page = (100, 0, 1)
offset: Union[int, None] = None,
) -> dict: raw_releases = self.browse_releases(
releases = musicbrainzngs.browse_releases( artist_id=self.artist_id,
artist=artist_id,
limit=limit, limit=limit,
release_type=release_type, release_type=['album'],
offset=offset, offset=offset,
includes=['recordings'], includes=['recordings'],
) )
return releases
@staticmethod releases = addict.Dict(raw_releases)
def browse_recordings(
release: str, limit: int, offset: Union[int, None] duplicated_tracks = self.paginate_results(releases, duplicated_tracks)
) -> dict:
recordings = musicbrainzngs.browse_recordings( # Get album info list
release=release, limit=limit, offset=offset album_info_list, album_tracker = self.get_album_info_list(
album_info_list, album_tracker, releases
) )
return recordings
def do_search_albumns(self) -> None: bar_count = len(releases['release-list'])
limit = 100 previous_bar_count = 0
offset = 0
page = 1 with PieSpinner(
self.releases_list = list() f'Finding all tracks in all albums for {self.artist}'
releases = self.browse_releases(self.artist_id, limit, ['album']) 'from Musicbrainz '
total_releases_count = releases['release-count']
page_releases_list = releases['release-list']
self.releases_list += page_releases_list
with click.progressbar(
length=total_releases_count - limit,
label='Finding all albumns from Musicbrainz',
) as bar: ) as bar:
while len(self.releases_list) < total_releases_count: while bar_count != previous_bar_count:
offset += limit offset += limit
page += 1 page += 1
page_releases = self.browse_releases( # Get next page
self.artist_id, limit, ['album'], offset raw_page_releases = self.browse_releases(
artist_id=self.artist_id,
limit=limit,
release_type=['album'],
offset=offset,
includes=['recordings'],
) )
page_releases_list = page_releases['release-list'] page_releases = addict.Dict(raw_page_releases)
self.releases_list += page_releases_list
bar.update(limit)
click.echo(f'Found {total_releases_count} albums for {self.artist}')
return self
def do_filter_albums_official(self) -> None: # Update list
# Get official albums only duplicated_tracks = self.paginate_results(
_official_albums_list = list() page_releases, duplicated_tracks
for i in self.releases_list: )
with contextlib.suppress(KeyError):
if i['status'] == 'Official':
_official_albums_list.append(i)
self.official_albums = dict(
(i['id'], i['title']) for i in _official_albums_list
)
# pprint(self.official_albums)
return self
def do_search_album_tracks(self) -> None: # Update album info list
# No album is over 100 tracks so we don't need to iterate (
limit = 100 album_info_list,
offset = 0 album_tracker,
recordings_list = list() ) = self.get_album_info_list(
self.recordings = dict() album_info_list, album_tracker, releases
with click.progressbar(
length=len(self.official_albums),
label='Finding all tracks in albums from Musicbrainz',
) as bar:
for i in self.official_albums:
_recordings_result = self.browse_recordings(
i, limit=limit, offset=offset
) )
album_recordings_list = _recordings_result['recording-list']
recordings_list.append( previous_bar_count = bar_count
dict((i['id'], i['title']) for i in album_recordings_list) bar_count += len(page_releases['release-list'])
) bar.next()
bar.update(1) total_releases_count = bar_count
for i in recordings_list:
self.recordings = {**self.recordings, **i} tracks = set(duplicated_tracks)
# pprint(self.recordings)
click.echo( click.echo(
f'Found {len(self.recordings)} tracks from ' f'Musicbrainz found {len(tracks)} unique tracks in '
f'{len(self.official_albums)} albums for {self.artist}' f'{total_releases_count} releases for {self.artist}'
) )
# Set properties
self.all_tracks = tracks
self.all_albums_with_tracks = album_info_list
return self return self
@@ -282,8 +350,8 @@ class LyricsClickDirector:
if artist_meta == 'Multiple': if artist_meta == 'Multiple':
_position = [] _position = []
click.echo( click.echo(
f'Musicbrainz found several results for {self.builder.artist[0]}' f'Musicbrainz found several results for '
'. Which artist/group do you want?', f'{self.builder.artist[0]}. Which artist/group do you want?',
) )
for i, j in zip(self.builder._top_five_results, range(1, 6)): for i, j in zip(self.builder._top_five_results, range(1, 6)):
click.echo( click.echo(
@@ -313,29 +381,29 @@ class LyricsClickDirector:
) )
raise SystemExit() raise SystemExit()
def _search_for_all_tracks(self) -> None:
self.builder.do_search_albums()
pprint(self.builder._product.all_tracks)
# pprint(self.builder._product.all_albums_with_tracks)
def _set_artist_id_on_product(self) -> None: def _set_artist_id_on_product(self) -> None:
self.builder.artist_id = self._artist_id self.builder.artist_id = self._artist_id
self.builder.artist = self._artist self.builder.artist = self._artist
# print(self.builder._product.artist_id)
# print(self.builder._product.artist)
# print(self.builder._product.__slots__)
@dataclass @dataclass
class Lyrics: class Lyrics:
"""docstring for Lyrics""" """docstring for Lyrics"""
__slots__ = ['artist_id', 'artist'] __slots__ = [
'artist_id',
'artist',
'country',
'all_tracks',
'all_albums_with_tracks',
]
artist_id: str artist_id: str
artist: str artist: str
country: Union[str, None]
all_tracks: set
if __name__ == '__main__': all_albums_with_tracks: list
# director = LyricsClickDirector()
# builder = LyricsBuilder()
# director.builder = builder
# director._get_initial_artists('One Direction')
# director._confirm_final_artist()
# director._set_artist_id_on_product()
# director.builder._product
print(type(Lyrics))

View File

@@ -100,16 +100,16 @@ def cli(ctx, artist: str, country: Union[str, None]) -> None:
""" """
Search for lyrics of an Artist/Group. Search for lyrics of an Artist/Group.
""" """
print(artist) # print(artist)
director = lyrics.LyricsClickDirector() director = lyrics.LyricsClickDirector()
builder = lyrics.LyricsBuilder() builder = lyrics.LyricsBuilder()
director.builder = builder director.builder = builder
director._get_initial_artists(artist, country) director._get_initial_artists(artist, country)
director._confirm_final_artist() director._confirm_final_artist()
director._set_artist_id_on_product() director._set_artist_id_on_product()
builder.do_search_albumns() director._search_for_all_tracks()
builder.do_filter_albums_official() # builder.do_filter_albums_official()
builder.do_search_album_tracks() # builder.do_search_album_tracks()
if __name__ == '__main__': if __name__ == '__main__':