adding latest working version
This commit is contained in:
@@ -1,461 +0,0 @@
|
|||||||
{
|
|
||||||
"Please Please Me [1963]": {
|
|
||||||
"avg": 158,
|
|
||||||
"median": 160,
|
|
||||||
"std": 53,
|
|
||||||
"max": 288,
|
|
||||||
"min": 103,
|
|
||||||
"p_10": 106,
|
|
||||||
"p_25": 108,
|
|
||||||
"p_75": 185,
|
|
||||||
"p_90": 200,
|
|
||||||
"count": 12
|
|
||||||
},
|
|
||||||
"With the Beatles [1963]": {
|
|
||||||
"avg": 174,
|
|
||||||
"median": 170,
|
|
||||||
"std": 60,
|
|
||||||
"max": 274,
|
|
||||||
"min": 59,
|
|
||||||
"p_10": 92,
|
|
||||||
"p_25": 143,
|
|
||||||
"p_75": 215,
|
|
||||||
"p_90": 241,
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
"Introducing\u2026 The Beatles [1964]": {
|
|
||||||
"avg": 167,
|
|
||||||
"median": 151,
|
|
||||||
"std": 59,
|
|
||||||
"max": 288,
|
|
||||||
"min": 105,
|
|
||||||
"p_10": 108,
|
|
||||||
"p_25": 114,
|
|
||||||
"p_75": 193,
|
|
||||||
"p_90": 257,
|
|
||||||
"count": 11
|
|
||||||
},
|
|
||||||
"Meet the Beatles! [2014]": {
|
|
||||||
"avg": 160,
|
|
||||||
"median": 158,
|
|
||||||
"std": 33,
|
|
||||||
"max": 215,
|
|
||||||
"min": 91,
|
|
||||||
"p_10": 130,
|
|
||||||
"p_25": 141,
|
|
||||||
"p_75": 179,
|
|
||||||
"p_90": 200,
|
|
||||||
"count": 24
|
|
||||||
},
|
|
||||||
"Twist and Shout [1964]": {
|
|
||||||
"avg": 170,
|
|
||||||
"median": 176,
|
|
||||||
"std": 44,
|
|
||||||
"max": 257,
|
|
||||||
"min": 105,
|
|
||||||
"p_10": 109,
|
|
||||||
"p_25": 141,
|
|
||||||
"p_75": 198,
|
|
||||||
"p_90": 204,
|
|
||||||
"count": 12
|
|
||||||
},
|
|
||||||
"The Beatles\u2019 Second Album [2004]": {
|
|
||||||
"avg": 205,
|
|
||||||
"median": 199,
|
|
||||||
"std": 59,
|
|
||||||
"max": 339,
|
|
||||||
"min": 135,
|
|
||||||
"p_10": 139,
|
|
||||||
"p_25": 155,
|
|
||||||
"p_75": 240,
|
|
||||||
"p_90": 251,
|
|
||||||
"count": 20
|
|
||||||
},
|
|
||||||
"The Beatles\u2019 Long Tall Sally [1964]": {
|
|
||||||
"avg": 182,
|
|
||||||
"median": 180,
|
|
||||||
"std": 70,
|
|
||||||
"max": 339,
|
|
||||||
"min": 82,
|
|
||||||
"p_10": 94,
|
|
||||||
"p_25": 134,
|
|
||||||
"p_75": 221,
|
|
||||||
"p_90": 241,
|
|
||||||
"count": 12
|
|
||||||
},
|
|
||||||
"Something New [2014]": {
|
|
||||||
"avg": 172,
|
|
||||||
"median": 178,
|
|
||||||
"std": 28,
|
|
||||||
"max": 220,
|
|
||||||
"min": 103,
|
|
||||||
"p_10": 146,
|
|
||||||
"p_25": 165,
|
|
||||||
"p_75": 185,
|
|
||||||
"p_90": 187,
|
|
||||||
"count": 22
|
|
||||||
},
|
|
||||||
"Beatles for Sale [1964]": {
|
|
||||||
"avg": 147,
|
|
||||||
"median": 146,
|
|
||||||
"std": 76,
|
|
||||||
"max": 358,
|
|
||||||
"min": 53,
|
|
||||||
"p_10": 74,
|
|
||||||
"p_25": 102,
|
|
||||||
"p_75": 159,
|
|
||||||
"p_90": 202,
|
|
||||||
"count": 12
|
|
||||||
},
|
|
||||||
"Beatles \u201965 [2004]": {
|
|
||||||
"avg": 161,
|
|
||||||
"median": 147,
|
|
||||||
"std": 69,
|
|
||||||
"max": 358,
|
|
||||||
"min": 71,
|
|
||||||
"p_10": 104,
|
|
||||||
"p_25": 141,
|
|
||||||
"p_75": 155,
|
|
||||||
"p_90": 179,
|
|
||||||
"count": 21
|
|
||||||
},
|
|
||||||
"Beatles VI [2014]": {
|
|
||||||
"avg": 159,
|
|
||||||
"median": 177,
|
|
||||||
"std": 52,
|
|
||||||
"max": 220,
|
|
||||||
"min": 53,
|
|
||||||
"p_10": 84,
|
|
||||||
"p_25": 137,
|
|
||||||
"p_75": 190,
|
|
||||||
"p_90": 210,
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
"Rubber Soul [1999]": {
|
|
||||||
"avg": 175,
|
|
||||||
"median": 168,
|
|
||||||
"std": 42,
|
|
||||||
"max": 239,
|
|
||||||
"min": 89,
|
|
||||||
"p_10": 133,
|
|
||||||
"p_25": 147,
|
|
||||||
"p_75": 217,
|
|
||||||
"p_90": 227,
|
|
||||||
"count": 20
|
|
||||||
},
|
|
||||||
"Revolver [2014]": {
|
|
||||||
"avg": 159,
|
|
||||||
"median": 160,
|
|
||||||
"std": 48,
|
|
||||||
"max": 233,
|
|
||||||
"min": 90,
|
|
||||||
"p_10": 102,
|
|
||||||
"p_25": 113,
|
|
||||||
"p_75": 205,
|
|
||||||
"p_90": 214,
|
|
||||||
"count": 22
|
|
||||||
},
|
|
||||||
"Sgt. Pepper\u2019s Lonely Hearts Club Band [1994]": {
|
|
||||||
"avg": 194,
|
|
||||||
"median": 176,
|
|
||||||
"std": 69,
|
|
||||||
"max": 395,
|
|
||||||
"min": 87,
|
|
||||||
"p_10": 136,
|
|
||||||
"p_25": 151,
|
|
||||||
"p_75": 217,
|
|
||||||
"p_90": 276,
|
|
||||||
"count": 19
|
|
||||||
},
|
|
||||||
"The Beatles [1968]": {
|
|
||||||
"avg": 139,
|
|
||||||
"median": 131,
|
|
||||||
"std": 91,
|
|
||||||
"max": 324,
|
|
||||||
"min": 9,
|
|
||||||
"p_10": 26,
|
|
||||||
"p_25": 72,
|
|
||||||
"p_75": 198,
|
|
||||||
"p_90": 255,
|
|
||||||
"count": 15
|
|
||||||
},
|
|
||||||
"Abbey Road [2009]": {
|
|
||||||
"avg": 91,
|
|
||||||
"median": 76,
|
|
||||||
"std": 55,
|
|
||||||
"max": 234,
|
|
||||||
"min": 28,
|
|
||||||
"p_10": 35,
|
|
||||||
"p_25": 52,
|
|
||||||
"p_75": 127,
|
|
||||||
"p_90": 141,
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
"Let It Be [2010]": {
|
|
||||||
"avg": 173,
|
|
||||||
"median": 177,
|
|
||||||
"std": 70,
|
|
||||||
"max": 268,
|
|
||||||
"min": 57,
|
|
||||||
"p_10": 69,
|
|
||||||
"p_25": 117,
|
|
||||||
"p_75": 230,
|
|
||||||
"p_90": 264,
|
|
||||||
"count": 13
|
|
||||||
},
|
|
||||||
"Decca Sessions 1.1.62 [1987]": {
|
|
||||||
"avg": 182,
|
|
||||||
"median": 167,
|
|
||||||
"std": 55,
|
|
||||||
"max": 289,
|
|
||||||
"min": 107,
|
|
||||||
"p_10": 126,
|
|
||||||
"p_25": 130,
|
|
||||||
"p_75": 223,
|
|
||||||
"p_90": 241,
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
"The Unreleased Tracks, Volume 2 [1990]": {
|
|
||||||
"avg": 141,
|
|
||||||
"median": 119,
|
|
||||||
"std": 45,
|
|
||||||
"max": 241,
|
|
||||||
"min": 105,
|
|
||||||
"p_10": 108,
|
|
||||||
"p_25": 110,
|
|
||||||
"p_75": 153,
|
|
||||||
"p_90": 201,
|
|
||||||
"count": 8
|
|
||||||
},
|
|
||||||
"Reunions 74 & 92 [1992]": {
|
|
||||||
"avg": 149,
|
|
||||||
"median": 170,
|
|
||||||
"std": 65,
|
|
||||||
"max": 240,
|
|
||||||
"min": 32,
|
|
||||||
"p_10": 71,
|
|
||||||
"p_25": 125,
|
|
||||||
"p_75": 170,
|
|
||||||
"p_90": 205,
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
"Studio 2 Sessions at Abbey Road, Vol. 1 [1995]": {
|
|
||||||
"avg": 132,
|
|
||||||
"median": 108,
|
|
||||||
"std": 36,
|
|
||||||
"max": 185,
|
|
||||||
"min": 105,
|
|
||||||
"p_10": 105,
|
|
||||||
"p_25": 105,
|
|
||||||
"p_75": 185,
|
|
||||||
"p_90": 185,
|
|
||||||
"count": 20
|
|
||||||
},
|
|
||||||
"Studio 2 Sessions at Abbey Road, Vol. 2 [1995]": null,
|
|
||||||
"Studio 2 Sessions at Abbey Road, Vol. 3 [1995]": {
|
|
||||||
"avg": 179,
|
|
||||||
"median": 163,
|
|
||||||
"std": 51,
|
|
||||||
"max": 248,
|
|
||||||
"min": 117,
|
|
||||||
"p_10": 117,
|
|
||||||
"p_25": 146,
|
|
||||||
"p_75": 248,
|
|
||||||
"p_90": 248,
|
|
||||||
"count": 16
|
|
||||||
},
|
|
||||||
"Studio 2 Sessions at Abbey Road, Vol. 4 [1995]": {
|
|
||||||
"avg": 222,
|
|
||||||
"median": 233,
|
|
||||||
"std": 46,
|
|
||||||
"max": 269,
|
|
||||||
"min": 145,
|
|
||||||
"p_10": 150,
|
|
||||||
"p_25": 188,
|
|
||||||
"p_75": 269,
|
|
||||||
"p_90": 269,
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
"The Alternate Abbey Road [2000]": {
|
|
||||||
"avg": 100,
|
|
||||||
"median": 76,
|
|
||||||
"std": 59,
|
|
||||||
"max": 234,
|
|
||||||
"min": 32,
|
|
||||||
"p_10": 37,
|
|
||||||
"p_25": 57,
|
|
||||||
"p_75": 146,
|
|
||||||
"p_90": 178,
|
|
||||||
"count": 16
|
|
||||||
},
|
|
||||||
"Alf Together Now [2001]": {
|
|
||||||
"avg": 193,
|
|
||||||
"median": 170,
|
|
||||||
"std": 68,
|
|
||||||
"max": 339,
|
|
||||||
"min": 111,
|
|
||||||
"p_10": 164,
|
|
||||||
"p_25": 168,
|
|
||||||
"p_75": 170,
|
|
||||||
"p_90": 323,
|
|
||||||
"count": 12
|
|
||||||
},
|
|
||||||
"Collectors Items [2002]": {
|
|
||||||
"avg": 241,
|
|
||||||
"median": 204,
|
|
||||||
"std": 160,
|
|
||||||
"max": 639,
|
|
||||||
"min": 89,
|
|
||||||
"p_10": 101,
|
|
||||||
"p_25": 154,
|
|
||||||
"p_75": 243,
|
|
||||||
"p_90": 457,
|
|
||||||
"count": 16
|
|
||||||
},
|
|
||||||
"Complete Home Recordings 1967\u20131968 [2002]": {
|
|
||||||
"avg": 197,
|
|
||||||
"median": 204,
|
|
||||||
"std": 74,
|
|
||||||
"max": 346,
|
|
||||||
"min": 70,
|
|
||||||
"p_10": 111,
|
|
||||||
"p_25": 111,
|
|
||||||
"p_75": 270,
|
|
||||||
"p_90": 270,
|
|
||||||
"count": 20
|
|
||||||
},
|
|
||||||
"Complete Home Recordings 1968 [2002]": {
|
|
||||||
"avg": 160,
|
|
||||||
"median": 153,
|
|
||||||
"std": 88,
|
|
||||||
"max": 351,
|
|
||||||
"min": 32,
|
|
||||||
"p_10": 41,
|
|
||||||
"p_25": 94,
|
|
||||||
"p_75": 209,
|
|
||||||
"p_90": 272,
|
|
||||||
"count": 24
|
|
||||||
},
|
|
||||||
"The Seven Years of Christmas [2002]": {
|
|
||||||
"avg": 790,
|
|
||||||
"median": 659,
|
|
||||||
"std": 202,
|
|
||||||
"max": 1122,
|
|
||||||
"min": 589,
|
|
||||||
"p_10": 617,
|
|
||||||
"p_25": 659,
|
|
||||||
"p_75": 921,
|
|
||||||
"p_90": 1042,
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
"20 X 4 Remastered Edition [2005]": {
|
|
||||||
"avg": 176,
|
|
||||||
"median": 178,
|
|
||||||
"std": 69,
|
|
||||||
"max": 269,
|
|
||||||
"min": 66,
|
|
||||||
"p_10": 98,
|
|
||||||
"p_25": 145,
|
|
||||||
"p_75": 220,
|
|
||||||
"p_90": 250,
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
"Inside Sgt. Pepper (Part Three) [2007]": {
|
|
||||||
"avg": 235,
|
|
||||||
"median": 235,
|
|
||||||
"std": 0,
|
|
||||||
"max": 235,
|
|
||||||
"min": 235,
|
|
||||||
"p_10": 235,
|
|
||||||
"p_25": 235,
|
|
||||||
"p_75": 235,
|
|
||||||
"p_90": 235,
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
"Carnival of Light - Fakes, Frauds, and Phonies (Million Volt Laugh and Sound Rave) [2009]": null,
|
|
||||||
"Transcending Time [2010]": {
|
|
||||||
"avg": 130,
|
|
||||||
"median": 115,
|
|
||||||
"std": 68,
|
|
||||||
"max": 243,
|
|
||||||
"min": 57,
|
|
||||||
"p_10": 58,
|
|
||||||
"p_25": 72,
|
|
||||||
"p_75": 174,
|
|
||||||
"p_90": 218,
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
"Hot as Sun [2010]": {
|
|
||||||
"avg": 141,
|
|
||||||
"median": 156,
|
|
||||||
"std": 43,
|
|
||||||
"max": 182,
|
|
||||||
"min": 70,
|
|
||||||
"p_10": 95,
|
|
||||||
"p_25": 132,
|
|
||||||
"p_75": 166,
|
|
||||||
"p_90": 176,
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
"From the Basement to the Boardroom [2011]": {
|
|
||||||
"avg": 179,
|
|
||||||
"median": 173,
|
|
||||||
"std": 161,
|
|
||||||
"max": 589,
|
|
||||||
"min": 32,
|
|
||||||
"p_10": 32,
|
|
||||||
"p_25": 54,
|
|
||||||
"p_75": 194,
|
|
||||||
"p_90": 297,
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
"The Decca Tapes [2013]": {
|
|
||||||
"avg": 172,
|
|
||||||
"median": 167,
|
|
||||||
"std": 56,
|
|
||||||
"max": 289,
|
|
||||||
"min": 101,
|
|
||||||
"p_10": 108,
|
|
||||||
"p_25": 127,
|
|
||||||
"p_75": 211,
|
|
||||||
"p_90": 229,
|
|
||||||
"count": 12
|
|
||||||
},
|
|
||||||
"Kinfauns Demos [Missing]": {
|
|
||||||
"avg": 153,
|
|
||||||
"median": 150,
|
|
||||||
"std": 84,
|
|
||||||
"max": 324,
|
|
||||||
"min": 32,
|
|
||||||
"p_10": 32,
|
|
||||||
"p_25": 82,
|
|
||||||
"p_75": 208,
|
|
||||||
"p_90": 271,
|
|
||||||
"count": 28
|
|
||||||
},
|
|
||||||
"Rare Tracks [Missing]": {
|
|
||||||
"avg": 235,
|
|
||||||
"median": 222,
|
|
||||||
"std": 67,
|
|
||||||
"max": 395,
|
|
||||||
"min": 171,
|
|
||||||
"p_10": 176,
|
|
||||||
"p_25": 190,
|
|
||||||
"p_75": 244,
|
|
||||||
"p_90": 299,
|
|
||||||
"count": 8
|
|
||||||
},
|
|
||||||
"The Lost Album (Two and a Half) [Missing]": {
|
|
||||||
"avg": 168,
|
|
||||||
"median": 163,
|
|
||||||
"std": 67,
|
|
||||||
"max": 282,
|
|
||||||
"min": 32,
|
|
||||||
"p_10": 112,
|
|
||||||
"p_25": 122,
|
|
||||||
"p_75": 210,
|
|
||||||
"p_90": 265,
|
|
||||||
"count": 22
|
|
||||||
},
|
|
||||||
"8 Mile and Abbey: Eminem Meets the Beatles [2014]": null
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB |
-154967
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-6569
File diff suppressed because it is too large
Load Diff
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
from . import lyrics
|
|
||||||
@@ -1,758 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
from abc import ABC, abstractmethod, abstractstaticmethod
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from pprint import pprint
|
|
||||||
from typing import Union, List, Dict
|
|
||||||
from collections import Counter
|
|
||||||
import html
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import string
|
|
||||||
import math
|
|
||||||
|
|
||||||
from beautifultable import BeautifulTable
|
|
||||||
import musicbrainzngs
|
|
||||||
import click
|
|
||||||
import addict
|
|
||||||
import requests
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from musicbrainzapi.api import authenticate
|
|
||||||
|
|
||||||
|
|
||||||
class LyricsConcreteBuilder(ABC):
|
|
||||||
"""docstring for Lyrics"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
@abstractmethod
|
|
||||||
def product(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
@abstractmethod
|
|
||||||
def artist(self) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@artist.setter
|
|
||||||
@abstractmethod
|
|
||||||
def artist(self, artist: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
@abstractmethod
|
|
||||||
def country(self) -> Union[str, None]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@country.setter
|
|
||||||
@abstractmethod
|
|
||||||
def country(self, country: Union[str, None]) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
@abstractmethod
|
|
||||||
def artist_id(self) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@artist_id.setter
|
|
||||||
@abstractmethod
|
|
||||||
def artist_id(self, artist_id: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractstaticmethod
|
|
||||||
def set_useragent():
|
|
||||||
authenticate.set_useragent()
|
|
||||||
|
|
||||||
# @abstractstaticmethod
|
|
||||||
# def browse_releases(self) -> dict:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def __init__(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def reset(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def find_artists(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def sort_artists(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_accuracy_scores(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_top_five_results(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def find_all_albums(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def find_all_tracks(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LyricsBuilder(LyricsConcreteBuilder):
|
|
||||||
"""docstring for LyricsBuilder"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def product(self) -> Lyrics:
|
|
||||||
product = self._product
|
|
||||||
return product
|
|
||||||
|
|
||||||
@property
|
|
||||||
def artist(self) -> str:
|
|
||||||
return self._artist
|
|
||||||
|
|
||||||
@artist.setter
|
|
||||||
def artist(self, artist: str) -> None:
|
|
||||||
self._artist = artist
|
|
||||||
self._product.artist = artist
|
|
||||||
|
|
||||||
@property
|
|
||||||
def country(self) -> Union[str, None]:
|
|
||||||
return self._country
|
|
||||||
|
|
||||||
@country.setter
|
|
||||||
def country(self, country: Union[str, None]) -> None:
|
|
||||||
self._country = country
|
|
||||||
self._product.country = country
|
|
||||||
|
|
||||||
@property
|
|
||||||
def artist_id(self) -> str:
|
|
||||||
return self._artist_id
|
|
||||||
|
|
||||||
@artist_id.setter
|
|
||||||
def artist_id(self, artist_id: str) -> None:
|
|
||||||
self._artist_id = artist_id
|
|
||||||
self._product.artist_id = artist_id
|
|
||||||
|
|
||||||
@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()
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def reset(self) -> None:
|
|
||||||
self._product = Lyrics()
|
|
||||||
|
|
||||||
def find_artists(self) -> None:
|
|
||||||
self.musicbrainz_artists = musicbrainzngs.search_artists(
|
|
||||||
artist=self.artist, country=self.country
|
|
||||||
)
|
|
||||||
# pprint(self.musicbrainz_artists['artist-list'])
|
|
||||||
# for i in self.musicbrainz_artists['artist-list']:
|
|
||||||
# print(i['name'])
|
|
||||||
# raise(SystemExit)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def sort_artists(self) -> None:
|
|
||||||
self._sort_names = dict(
|
|
||||||
(i.get('id'), f'{i.get("name")} | {i.get("disambiguation")}')
|
|
||||||
if i.get('disambiguation') is not None
|
|
||||||
else (i.get('id'), f'{i.get("name")}')
|
|
||||||
for i in self.musicbrainz_artists['artist-list']
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get_accuracy_scores(self) -> None:
|
|
||||||
self._accuracy_scores = dict(
|
|
||||||
(i.get('id'), int(i.get('ext:score', '0')))
|
|
||||||
for i in self.musicbrainz_artists['artist-list']
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get_top_five_results(self) -> None:
|
|
||||||
self._top_five_results = dict(
|
|
||||||
(i, self._accuracy_scores.get(i))
|
|
||||||
for i in sorted(
|
|
||||||
self._accuracy_scores,
|
|
||||||
key=self._accuracy_scores.get,
|
|
||||||
reverse=True,
|
|
||||||
)[0:5]
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def find_all_albums(self) -> None:
|
|
||||||
limit, offset, page = (100, 0, 1)
|
|
||||||
|
|
||||||
resp_0 = addict.Dict(
|
|
||||||
musicbrainzngs.browse_release_groups(
|
|
||||||
artist=self.artist_id, release_type=['album'], limit=limit
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
total_releases = resp_0['release-group-count']
|
|
||||||
response_releases = len(resp_0['release-group-list'])
|
|
||||||
|
|
||||||
with click.progressbar(
|
|
||||||
length=total_releases,
|
|
||||||
label=f'Searching Musicbrainz for all albums from {self.artist}',
|
|
||||||
) as bar:
|
|
||||||
|
|
||||||
release_group_ids = addict.Dict(
|
|
||||||
(i.id, i.title)
|
|
||||||
for i in resp_0['release-group-list']
|
|
||||||
if i.type == 'Album'
|
|
||||||
)
|
|
||||||
|
|
||||||
bar.update(response_releases)
|
|
||||||
|
|
||||||
while response_releases > 0:
|
|
||||||
# Get next page
|
|
||||||
offset += limit
|
|
||||||
page += 1
|
|
||||||
|
|
||||||
resp_1 = addict.Dict(
|
|
||||||
musicbrainzngs.browse_release_groups(
|
|
||||||
artist=self.artist_id,
|
|
||||||
release_type=['album'],
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
response_releases = len(resp_1['release-group-list'])
|
|
||||||
|
|
||||||
release_group_ids = addict.Dict(
|
|
||||||
**release_group_ids,
|
|
||||||
**addict.Dict(
|
|
||||||
(i.id, i.title)
|
|
||||||
for i in resp_1['release-group-list']
|
|
||||||
if i.type == 'Album'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
bar.update(response_releases)
|
|
||||||
|
|
||||||
self.release_group_ids = release_group_ids
|
|
||||||
click.echo(f'Found {len(release_group_ids)} albums for {self.artist}.')
|
|
||||||
|
|
||||||
del (resp_0, resp_1)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def find_all_tracks(self) -> None:
|
|
||||||
self.all_albums = list()
|
|
||||||
total_albums = len(self.release_group_ids)
|
|
||||||
self.total_track_count = 0
|
|
||||||
|
|
||||||
with click.progressbar(
|
|
||||||
length=total_albums,
|
|
||||||
label=(
|
|
||||||
'Searching Musicbrainz for all tracks in all albums for '
|
|
||||||
f'{self.artist}'
|
|
||||||
),
|
|
||||||
) as bar:
|
|
||||||
for id, alb in self.release_group_ids.items():
|
|
||||||
resp_0 = addict.Dict(
|
|
||||||
musicbrainzngs.browse_releases(
|
|
||||||
release_group=id,
|
|
||||||
release_type=['album'],
|
|
||||||
includes=['recordings'],
|
|
||||||
limit=100,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
album_track_count = [
|
|
||||||
i['medium-list'][0]['track-count']
|
|
||||||
for i in resp_0['release-list']
|
|
||||||
]
|
|
||||||
|
|
||||||
self.total_track_count += max(album_track_count)
|
|
||||||
|
|
||||||
max_track_pos = album_track_count.index(max(album_track_count))
|
|
||||||
|
|
||||||
album_tracks = resp_0['release-list'][max_track_pos]
|
|
||||||
|
|
||||||
try:
|
|
||||||
album_year = resp_0['release-list'][
|
|
||||||
max_track_pos
|
|
||||||
].date.split('-')[0]
|
|
||||||
except TypeError:
|
|
||||||
album_year = 'Missing'
|
|
||||||
|
|
||||||
album_tracks = addict.Dict(
|
|
||||||
(
|
|
||||||
alb + f' [{album_year}]',
|
|
||||||
[
|
|
||||||
i.recording.title
|
|
||||||
for i in resp_0['release-list'][max_track_pos][
|
|
||||||
'medium-list'
|
|
||||||
][0]['track-list']
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.all_albums.append(album_tracks)
|
|
||||||
|
|
||||||
bar.update(1)
|
|
||||||
|
|
||||||
# pprint(self.all_albums)
|
|
||||||
click.echo(
|
|
||||||
f'Found {self.total_track_count} tracks across'
|
|
||||||
f' {len(self.release_group_ids)} albums for {self.artist}'
|
|
||||||
)
|
|
||||||
del resp_0
|
|
||||||
return self
|
|
||||||
|
|
||||||
def find_lyrics_urls(self) -> None:
|
|
||||||
self.all_albums_lyrics_url = list()
|
|
||||||
for x in self.all_albums:
|
|
||||||
for alb, tracks in x.items():
|
|
||||||
lyrics = addict.Dict(
|
|
||||||
(
|
|
||||||
alb,
|
|
||||||
[
|
|
||||||
self.construct_lyrics_url(self.artist, i)
|
|
||||||
for i in tracks
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.all_albums_lyrics_url.append(lyrics)
|
|
||||||
|
|
||||||
# pprint(self.all_albums_lyrics_url)
|
|
||||||
return self
|
|
||||||
|
|
||||||
# change this for progressbar for i loop
|
|
||||||
def find_all_lyrics(self) -> None:
|
|
||||||
self.all_albums_lyrics = list()
|
|
||||||
|
|
||||||
with click.progressbar(
|
|
||||||
length=self.total_track_count,
|
|
||||||
label=f'Finding lyrics for {self.total_track_count}'
|
|
||||||
f' tracks for {self.artist}. This may take some time! ☕️',
|
|
||||||
) as bar:
|
|
||||||
bar.update(5)
|
|
||||||
for x in self.all_albums_lyrics_url:
|
|
||||||
for alb, urls in x.items():
|
|
||||||
# bar.update(1)
|
|
||||||
update = len(urls)
|
|
||||||
lyrics = addict.Dict(
|
|
||||||
(alb, [self.request_lyrics_from_url(i) for i in urls])
|
|
||||||
)
|
|
||||||
self.all_albums_lyrics.append(lyrics)
|
|
||||||
bar.update(update)
|
|
||||||
|
|
||||||
with open(f'{os.getcwd()}/all_albums_lyrics.json', 'w') as f:
|
|
||||||
json.dump(self.all_albums_lyrics, f, indent=2)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def count_words_in_lyrics(self) -> None:
|
|
||||||
# remove punctuation, fix click bar
|
|
||||||
self.all_albums_lyrics_count = list()
|
|
||||||
# print(self.total_track_count)
|
|
||||||
with click.progressbar(
|
|
||||||
length=self.total_track_count, label=f'Processing lyrics'
|
|
||||||
) as bar:
|
|
||||||
for x in self.all_albums_lyrics:
|
|
||||||
for alb, lyrics in x.items():
|
|
||||||
update = len(lyrics)
|
|
||||||
bar.update(1)
|
|
||||||
lyrics = addict.Dict(
|
|
||||||
(
|
|
||||||
alb,
|
|
||||||
[
|
|
||||||
Counter(i.split()).most_common()
|
|
||||||
if i is not None
|
|
||||||
else 'No Lyrics'
|
|
||||||
for i in lyrics
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.all_albums_lyrics_count.append(lyrics)
|
|
||||||
bar.update(update - 1)
|
|
||||||
click.echo(f'Processed lyrics for {self.total_track_count} tracks.')
|
|
||||||
return self
|
|
||||||
|
|
||||||
# rename this
|
|
||||||
def calculate_average_all_albums(self) -> None:
|
|
||||||
self.all_albums_lyrics_sum = list()
|
|
||||||
# album_lyrics = self.all_albums_lyrics_count
|
|
||||||
with open(f'{os.getcwd()}/lyrics_count.json', 'r') as f:
|
|
||||||
album_lyrics = json.load(f)
|
|
||||||
count = 0
|
|
||||||
for i in album_lyrics:
|
|
||||||
count += len(i)
|
|
||||||
for album, lyrics_list in i.items():
|
|
||||||
album_avg = list()
|
|
||||||
d = addict.Dict()
|
|
||||||
# print(album)
|
|
||||||
for j in lyrics_list:
|
|
||||||
if j != 'No Lyrics':
|
|
||||||
song_total = 0
|
|
||||||
for k in j:
|
|
||||||
song_total += k[1]
|
|
||||||
else:
|
|
||||||
song_total = "No Lyrics"
|
|
||||||
album_avg.append(song_total)
|
|
||||||
# We want to avoid a ValueError when we loop through
|
|
||||||
# the first time
|
|
||||||
try:
|
|
||||||
d = addict.Dict(**d, **addict.Dict(album, album_avg))
|
|
||||||
except ValueError:
|
|
||||||
d = addict.Dict((album, album_avg))
|
|
||||||
# print(d)
|
|
||||||
self.all_albums_lyrics_sum.append(d)
|
|
||||||
# print(count)
|
|
||||||
with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'w+') as f:
|
|
||||||
json.dump(self.all_albums_lyrics_sum, f)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def calculate_final_average_by_album(self) -> None:
|
|
||||||
self.album_statistics = addict.Dict()
|
|
||||||
# album_lyrics = self.all_albums_lyrics_sum
|
|
||||||
with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'r') as f:
|
|
||||||
album_lyrics = json.load(f)
|
|
||||||
|
|
||||||
for i in album_lyrics:
|
|
||||||
for album, count in i.items():
|
|
||||||
# We filter twice, once to remove strings, then to filter
|
|
||||||
# the integers
|
|
||||||
_count = [d for d in count if isinstance(d, int)]
|
|
||||||
_count = [d for d in _count if d > 1]
|
|
||||||
_d = self.get_descriptive_statistics(_count)
|
|
||||||
self.album_statistics = addict.Dict(
|
|
||||||
**self.album_statistics, **addict.Dict((album, _d))
|
|
||||||
)
|
|
||||||
with open(f'{os.getcwd()}/album_statistics.json', 'w') as f:
|
|
||||||
json.dump(self.album_statistics, f, indent=2)
|
|
||||||
# pprint(self.album_statistics)
|
|
||||||
|
|
||||||
# implement above in this
|
|
||||||
def calculate_final_average_by_year(self) -> None:
|
|
||||||
group_by_years = addict.Dict()
|
|
||||||
self.year_statistics = addict.Dict()
|
|
||||||
# album_lyrics = self.all_albums_lyrics_sum
|
|
||||||
with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'r') as f:
|
|
||||||
album_lyrics = json.load(f)
|
|
||||||
|
|
||||||
# Merge years together
|
|
||||||
for i in album_lyrics:
|
|
||||||
for album, count in i.items():
|
|
||||||
year = album.split('[')[-1].strip(']')
|
|
||||||
try:
|
|
||||||
group_by_years = addict.Dict(
|
|
||||||
**group_by_years, **addict.Dict((year, count))
|
|
||||||
)
|
|
||||||
# First loop returns value error for empty dict
|
|
||||||
except ValueError:
|
|
||||||
group_by_years = addict.Dict((year, count))
|
|
||||||
# Multiple years raise a TypeError - we append
|
|
||||||
except TypeError:
|
|
||||||
group_by_years.get(year).extend(count)
|
|
||||||
|
|
||||||
for year, y_count in group_by_years.items():
|
|
||||||
_y_count = [d for d in y_count if isinstance(d, int)]
|
|
||||||
_y_count = [d for d in _y_count if d > 1]
|
|
||||||
_d = self.get_descriptive_statistics(_y_count)
|
|
||||||
self.year_statistics = addict.Dict(
|
|
||||||
**self.year_statistics, **addict.Dict((year, _d))
|
|
||||||
)
|
|
||||||
# pprint(self.year_statistics)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def construct_lyrics_url(artist: str, song: str) -> str:
|
|
||||||
lyrics_api_base = 'https://api.lyrics.ovh/v1'
|
|
||||||
lyrics_api_url = html.escape(f'{lyrics_api_base}/{artist}/{song}')
|
|
||||||
return lyrics_api_url
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def request_lyrics_from_url(url: str) -> str:
|
|
||||||
resp = requests.get(url)
|
|
||||||
|
|
||||||
# No lyrics for a song will return a key of 'error', we pass on this.
|
|
||||||
try:
|
|
||||||
lyrics = LyricsBuilder.strip_punctuation(resp.json()['lyrics'])
|
|
||||||
return lyrics
|
|
||||||
except (KeyError, json.decoder.JSONDecodeError):
|
|
||||||
return
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def strip_punctuation(word: str) -> str:
|
|
||||||
_strip = word.translate(str.maketrans('', '', string.punctuation))
|
|
||||||
return _strip
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_descriptive_statistics(nums: list) -> Dict[str, int]:
|
|
||||||
if len(nums) == 0:
|
|
||||||
return
|
|
||||||
avg = math.ceil(np.mean(nums))
|
|
||||||
median = math.ceil(np.median(nums))
|
|
||||||
std = math.ceil(np.std(nums))
|
|
||||||
max = math.ceil(np.max(nums))
|
|
||||||
min = math.ceil(np.min(nums))
|
|
||||||
p_10 = math.ceil(np.percentile(nums, 10))
|
|
||||||
p_25 = math.ceil(np.percentile(nums, 25))
|
|
||||||
p_75 = math.ceil(np.percentile(nums, 75))
|
|
||||||
p_90 = math.ceil(np.percentile(nums, 90))
|
|
||||||
count = len(nums)
|
|
||||||
_d = addict.Dict(
|
|
||||||
('avg', avg),
|
|
||||||
('median', median),
|
|
||||||
('std', std),
|
|
||||||
('max', max),
|
|
||||||
('min', min),
|
|
||||||
('p_10', p_10),
|
|
||||||
('p_25', p_25),
|
|
||||||
('p_75', p_75),
|
|
||||||
('p_90', p_90),
|
|
||||||
('count', count),
|
|
||||||
)
|
|
||||||
return _d
|
|
||||||
|
|
||||||
|
|
||||||
class LyricsClickDirector:
|
|
||||||
"""docstring for LyricsClickDirector"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self._builder = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def builder(self) -> LyricsBuilder:
|
|
||||||
return self._builder
|
|
||||||
|
|
||||||
@builder.setter
|
|
||||||
def builder(self, builder: LyricsBuilder) -> None:
|
|
||||||
self._builder = builder
|
|
||||||
|
|
||||||
def _get_initial_artists(self, artist: str, country: str) -> None:
|
|
||||||
self.builder.artist = artist
|
|
||||||
self.builder.country = country
|
|
||||||
self.builder.set_useragent()
|
|
||||||
self.builder.find_artists()
|
|
||||||
self.builder.sort_artists()
|
|
||||||
self.builder.get_accuracy_scores()
|
|
||||||
self.builder.get_top_five_results()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _confirm_final_artist(self) -> None:
|
|
||||||
artist_meta = None
|
|
||||||
for i, j in self.builder._top_five_results.items():
|
|
||||||
artist_meta = 'Multiple' if j <= 100 else None
|
|
||||||
|
|
||||||
if artist_meta == 'Multiple':
|
|
||||||
_position = []
|
|
||||||
click.echo(
|
|
||||||
click.style(
|
|
||||||
f'Musicbrainz found several results for '
|
|
||||||
f'{self.builder.artist[0]}. Which artist/group do you want'
|
|
||||||
'?',
|
|
||||||
fg='green',
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for i, j in zip(self.builder._top_five_results, range(1, 6)):
|
|
||||||
click.echo(
|
|
||||||
f'[{j}] {self.builder._sort_names.get(i)}'
|
|
||||||
f' ({self.builder._accuracy_scores.get(i)}% match)'
|
|
||||||
)
|
|
||||||
_position.append(i)
|
|
||||||
chosen = int(
|
|
||||||
click.prompt(
|
|
||||||
click.style(f'Enter choice, default is', blink=True),
|
|
||||||
default=1,
|
|
||||||
type=click.IntRange(
|
|
||||||
1, len(self.builder._top_five_results)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
choice = _position[chosen - 1]
|
|
||||||
click.echo(f'You chose {self.builder._sort_names.get(choice)}')
|
|
||||||
self._artist = self.builder._sort_names.get(choice).split('|')[0]
|
|
||||||
self._artist_id = choice
|
|
||||||
|
|
||||||
# Set artist and artistID on builder + product
|
|
||||||
self.builder.artist_id = self._artist_id
|
|
||||||
self.builder.artist = self._artist
|
|
||||||
|
|
||||||
elif artist_meta is None:
|
|
||||||
click.echo(
|
|
||||||
f'Musicbrainz did not find any results for '
|
|
||||||
f'{self.builder.artist[0]}. Check the spelling or consider '
|
|
||||||
'alternative names that the artist/group may go by.'
|
|
||||||
)
|
|
||||||
raise SystemExit()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _query_for_data(self) -> None:
|
|
||||||
self.builder.find_all_albums()
|
|
||||||
self.builder.find_all_tracks()
|
|
||||||
self.builder._product.all_albums_with_tracks = self.builder.all_albums
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _get_lyrics(self) -> None:
|
|
||||||
self.builder.find_lyrics_urls()
|
|
||||||
self.builder.find_all_lyrics()
|
|
||||||
self.builder._product.all_albums_with_lyrics = (
|
|
||||||
self.builder.all_albums_lyrics
|
|
||||||
)
|
|
||||||
self.builder.count_words_in_lyrics()
|
|
||||||
with open(f'{os.getcwd()}/lyrics_count.json', 'w+') as file:
|
|
||||||
json.dump(
|
|
||||||
self.builder.all_albums_lyrics_count,
|
|
||||||
file,
|
|
||||||
indent=2,
|
|
||||||
sort_keys=True,
|
|
||||||
)
|
|
||||||
self.builder._product.all_albums_lyrics_count = (
|
|
||||||
self.builder.all_albums_lyrics_count
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _calculate_basic_statistics(self) -> None:
|
|
||||||
self.builder.calculate_average_all_albums()
|
|
||||||
self.builder._product.all_albums_lyrics_sum = (
|
|
||||||
self.builder.all_albums_lyrics_sum
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _calculate_descriptive_statistics(self) -> None:
|
|
||||||
self.builder.calculate_final_average_by_album()
|
|
||||||
self.builder.calculate_final_average_by_year()
|
|
||||||
self.builder._product.album_statistics = self.builder.album_statistics
|
|
||||||
self.builder._product.year_statistics = self.builder.year_statistics
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _dev(self) -> None:
|
|
||||||
self.builder.calculate_final_average_by_album()
|
|
||||||
self.builder.calculate_final_average_by_year()
|
|
||||||
self.builder._product.album_statistics = self.builder.album_statistics
|
|
||||||
self.builder._product.year_statistics = self.builder.year_statistics
|
|
||||||
self.builder._product.artist_id = None
|
|
||||||
self.builder._product.artist = 'Katzenjammer'
|
|
||||||
self.builder._product.show_summary()
|
|
||||||
self.builder._product.show_summary_statistics(group_by='year')
|
|
||||||
return self
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_product(builder_inst: LyricsBuilder) -> Lyrics:
|
|
||||||
return builder_inst._product
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Lyrics:
|
|
||||||
"""docstring for Lyrics"""
|
|
||||||
|
|
||||||
artist_id: str
|
|
||||||
artist: str
|
|
||||||
country: Union[str, None]
|
|
||||||
all_albums_with_tracks: List[Dict[str, List[str]]]
|
|
||||||
all_albums_with_lyrics: List[Dict[str, List[str]]]
|
|
||||||
all_albums_lyrics_count: List[Dict[str, List[List[str, int]]]]
|
|
||||||
all_albums_lyrics_sum: List[Dict[str, List[int, str]]]
|
|
||||||
album_statistics: Dict[str, Dict[str, int]]
|
|
||||||
year_statistics: Dict[str, Dict[str, int]]
|
|
||||||
|
|
||||||
__attributes = [
|
|
||||||
'all_albums_with_tracks',
|
|
||||||
'all_albums_with_lyrics',
|
|
||||||
'all_albums_lyrics_count',
|
|
||||||
'all_albums_lyrics_sum',
|
|
||||||
'album_statistics',
|
|
||||||
'year_statistics',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def show_summary(self):
|
|
||||||
all_averages = []
|
|
||||||
|
|
||||||
for i in self.album_statistics.values():
|
|
||||||
try:
|
|
||||||
all_averages.append(i['avg'])
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
pass
|
|
||||||
# print(all_averages)
|
|
||||||
try:
|
|
||||||
final_average = math.ceil(np.mean(all_averages))
|
|
||||||
except ValueError:
|
|
||||||
click.echo(
|
|
||||||
'Oops! https://lyrics.ovh couldn\'t find any lyrics across any'
|
|
||||||
' album. This is caused by inconsistent Artist names from'
|
|
||||||
' Musicbrainz and lyrics.ovh. Try another artist.'
|
|
||||||
)
|
|
||||||
raise (SystemExit)
|
|
||||||
output = BeautifulTable(max_width=200)
|
|
||||||
output.set_style(BeautifulTable.STYLE_BOX_ROUNDED)
|
|
||||||
output.column_headers = [
|
|
||||||
'Average number of words in tracks across all albums\n'
|
|
||||||
f'for {self.artist}'
|
|
||||||
]
|
|
||||||
output.append_row([final_average])
|
|
||||||
click.echo(output)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def show_summary_statistics(self, group_by: str) -> None:
|
|
||||||
stats_obj = getattr(self, f'{group_by}_statistics')
|
|
||||||
stats = [
|
|
||||||
'avg',
|
|
||||||
'std',
|
|
||||||
'min',
|
|
||||||
'max',
|
|
||||||
'median',
|
|
||||||
'count',
|
|
||||||
'p_10',
|
|
||||||
'p_25',
|
|
||||||
'p_75',
|
|
||||||
'p_90',
|
|
||||||
]
|
|
||||||
output_0 = BeautifulTable(max_width=200)
|
|
||||||
output_0.set_style(BeautifulTable.STYLE_BOX_ROUNDED)
|
|
||||||
output_0.column_headers = [
|
|
||||||
'Descriptive statistics for number of words in tracks across all'
|
|
||||||
f' {group_by}s\nfor {self.artist}'
|
|
||||||
]
|
|
||||||
output_1 = BeautifulTable(max_width=200)
|
|
||||||
output_1.set_style(BeautifulTable.STYLE_BOX_ROUNDED)
|
|
||||||
output_1.column_headers = [
|
|
||||||
group_by,
|
|
||||||
stats[0],
|
|
||||||
stats[1],
|
|
||||||
stats[2],
|
|
||||||
stats[3],
|
|
||||||
stats[4],
|
|
||||||
stats[5],
|
|
||||||
stats[6],
|
|
||||||
stats[7],
|
|
||||||
stats[8],
|
|
||||||
stats[9],
|
|
||||||
]
|
|
||||||
for group, s in stats_obj.items():
|
|
||||||
try:
|
|
||||||
output_1.append_row(
|
|
||||||
[
|
|
||||||
group,
|
|
||||||
s.get(stats[0]),
|
|
||||||
s.get(stats[1]),
|
|
||||||
s.get(stats[2]),
|
|
||||||
s.get(stats[3]),
|
|
||||||
s.get(stats[4]),
|
|
||||||
s.get(stats[5]),
|
|
||||||
s.get(stats[6]),
|
|
||||||
s.get(stats[7]),
|
|
||||||
s.get(stats[8]),
|
|
||||||
s.get(stats[9]),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
except AttributeError:
|
|
||||||
continue
|
|
||||||
output_0.append_row([output_1])
|
|
||||||
click.echo(output_0)
|
|
||||||
return self
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,140 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import Union, Dict, List
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import math
|
||||||
|
|
||||||
|
from beautifultable import BeautifulTable
|
||||||
|
import click
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Lyrics:
|
||||||
|
"""Lyrics object for an artist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
artist_id: str
|
||||||
|
artist: str
|
||||||
|
country: Union[str, None]
|
||||||
|
all_albums_with_tracks: List[Dict[str, List[str]]]
|
||||||
|
all_albums_with_lyrics: List[Dict[str, List[str]]]
|
||||||
|
all_albums_lyrics_count: List[Dict[str, List[List[str, int]]]]
|
||||||
|
all_albums_lyrics_sum: List[Dict[str, List[int, str]]]
|
||||||
|
album_statistics: Dict[str, Dict[str, int]]
|
||||||
|
year_statistics: Dict[str, Dict[str, int]]
|
||||||
|
|
||||||
|
_attributes = [
|
||||||
|
'all_albums_with_tracks',
|
||||||
|
'all_albums_with_lyrics',
|
||||||
|
'all_albums_lyrics_count',
|
||||||
|
'all_albums_lyrics_sum',
|
||||||
|
'album_statistics',
|
||||||
|
'year_statistics',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def show_summary(self) -> None:
|
||||||
|
"""Show the average word count for all lyrics
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
all_averages = []
|
||||||
|
|
||||||
|
for i in self.album_statistics.values():
|
||||||
|
try:
|
||||||
|
all_averages.append(i['avg'])
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
# print(all_averages)
|
||||||
|
try:
|
||||||
|
final_average = math.ceil(np.mean(all_averages))
|
||||||
|
except ValueError:
|
||||||
|
click.echo(
|
||||||
|
'Oops! https://lyrics.ovh couldn\'t find any lyrics across any'
|
||||||
|
' album. This is caused by inconsistent Artist names from'
|
||||||
|
' Musicbrainz and lyrics.ovh. Try another artist.'
|
||||||
|
)
|
||||||
|
raise (SystemExit)
|
||||||
|
output = BeautifulTable(max_width=200)
|
||||||
|
output.set_style(BeautifulTable.STYLE_BOX_ROUNDED)
|
||||||
|
output.column_headers = [
|
||||||
|
'Average number of words in tracks across all albums\n'
|
||||||
|
f'for {self.artist}'
|
||||||
|
]
|
||||||
|
output.append_row([final_average])
|
||||||
|
click.echo(output)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def show_summary_statistics(self, group_by: str) -> None:
|
||||||
|
"""Summary
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
group_by : str
|
||||||
|
Parameter to group statistics by. Valid options are album or year
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
stats_obj = getattr(self, f'{group_by}_statistics')
|
||||||
|
stats = [
|
||||||
|
'avg',
|
||||||
|
'std',
|
||||||
|
'min',
|
||||||
|
'max',
|
||||||
|
'median',
|
||||||
|
'count',
|
||||||
|
'p_10',
|
||||||
|
'p_25',
|
||||||
|
'p_75',
|
||||||
|
'p_90',
|
||||||
|
]
|
||||||
|
output_0 = BeautifulTable(max_width=200)
|
||||||
|
output_0.set_style(BeautifulTable.STYLE_BOX_ROUNDED)
|
||||||
|
output_0.column_headers = [
|
||||||
|
'Descriptive statistics for number of words in tracks across all'
|
||||||
|
f' {group_by}s\nfor {self.artist}'
|
||||||
|
]
|
||||||
|
output_1 = BeautifulTable(max_width=200)
|
||||||
|
output_1.set_style(BeautifulTable.STYLE_BOX_ROUNDED)
|
||||||
|
output_1.column_headers = [
|
||||||
|
group_by,
|
||||||
|
stats[0],
|
||||||
|
stats[1],
|
||||||
|
stats[2],
|
||||||
|
stats[3],
|
||||||
|
stats[4],
|
||||||
|
stats[5],
|
||||||
|
stats[6],
|
||||||
|
stats[7],
|
||||||
|
stats[8],
|
||||||
|
stats[9],
|
||||||
|
]
|
||||||
|
for group, s in stats_obj.items():
|
||||||
|
try:
|
||||||
|
output_1.append_row(
|
||||||
|
[
|
||||||
|
group,
|
||||||
|
s.get(stats[0]),
|
||||||
|
s.get(stats[1]),
|
||||||
|
s.get(stats[2]),
|
||||||
|
s.get(stats[3]),
|
||||||
|
s.get(stats[4]),
|
||||||
|
s.get(stats[5]),
|
||||||
|
s.get(stats[6]),
|
||||||
|
s.get(stats[7]),
|
||||||
|
s.get(stats[8]),
|
||||||
|
s.get(stats[9]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
output_0.append_row([output_1])
|
||||||
|
click.echo(output_0)
|
||||||
|
return self
|
||||||
|
|||||||
@@ -0,0 +1,568 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from collections import Counter
|
||||||
|
import html
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
from typing import Union, Dict
|
||||||
|
|
||||||
|
import addict
|
||||||
|
import click
|
||||||
|
import musicbrainzngs
|
||||||
|
import numpy as np
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from musicbrainzapi.api.lyrics.concrete_builder import LyricsConcreteBuilder
|
||||||
|
from musicbrainzapi.api.lyrics import Lyrics
|
||||||
|
from musicbrainzapi.api import authenticate
|
||||||
|
|
||||||
|
|
||||||
|
class LyricsBuilder(LyricsConcreteBuilder):
|
||||||
|
"""docstring for LyricsBuilder
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
album_statistics : addict.Dict
|
||||||
|
Dictionary containing album statistics
|
||||||
|
all_albums : list
|
||||||
|
List of all albums + track titles
|
||||||
|
all_albums_lyrics : list
|
||||||
|
List of all albums + track lyrics
|
||||||
|
all_albums_lyrics_count : list
|
||||||
|
List of all albums + track lyrics counted by each word
|
||||||
|
all_albums_lyrics_sum : list
|
||||||
|
List of all albums + track lyrics counted and summed up.
|
||||||
|
all_albums_lyrics_url : list
|
||||||
|
List of all albums + link to lyrics api for each track.
|
||||||
|
musicbrainz_artists : addict.Dict
|
||||||
|
Dictionary of response from Musicbrainzapi
|
||||||
|
release_group_ids : addict.Dict
|
||||||
|
Dictionary of Musicbrainz release-group ids
|
||||||
|
total_track_count : int
|
||||||
|
Total number of tracks across all albums
|
||||||
|
year_statistics : addict.Dict
|
||||||
|
Dictionary containing album statistics
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def product(self) -> Lyrics:
|
||||||
|
product = self._product
|
||||||
|
return product
|
||||||
|
|
||||||
|
@property
|
||||||
|
def artist(self) -> str:
|
||||||
|
return self._artist
|
||||||
|
|
||||||
|
@artist.setter
|
||||||
|
def artist(self, artist: str) -> None:
|
||||||
|
self._artist = artist
|
||||||
|
self._product.artist = artist
|
||||||
|
|
||||||
|
@property
|
||||||
|
def country(self) -> Union[str, None]:
|
||||||
|
return self._country
|
||||||
|
|
||||||
|
@country.setter
|
||||||
|
def country(self, country: Union[str, None]) -> None:
|
||||||
|
self._country = country
|
||||||
|
self._product.country = country
|
||||||
|
|
||||||
|
@property
|
||||||
|
def artist_id(self) -> str:
|
||||||
|
return self._artist_id
|
||||||
|
|
||||||
|
@artist_id.setter
|
||||||
|
def artist_id(self, artist_id: str) -> None:
|
||||||
|
self._artist_id = artist_id
|
||||||
|
self._product.artist_id = artist_id
|
||||||
|
|
||||||
|
@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()
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Reset the builder and create new product.
|
||||||
|
"""
|
||||||
|
self._product = Lyrics()
|
||||||
|
|
||||||
|
def find_artists(self) -> None:
|
||||||
|
"""Find artists from the musicbrainz api
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.musicbrainz_artists = musicbrainzngs.search_artists(
|
||||||
|
artist=self.artist, country=self.country
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def sort_artists(self) -> None:
|
||||||
|
"""Sort the artists from the Musicbrainzapi
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self._sort_names = dict(
|
||||||
|
(i.get('id'), f'{i.get("name")} | {i.get("disambiguation")}')
|
||||||
|
if i.get('disambiguation') is not None
|
||||||
|
else (i.get('id'), f'{i.get("name")}')
|
||||||
|
for i in self.musicbrainz_artists['artist-list']
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_accuracy_scores(self) -> None:
|
||||||
|
"""Get accuracy scores from the Musicbrainzapi
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self._accuracy_scores = dict(
|
||||||
|
(i.get('id'), int(i.get('ext:score', '0')))
|
||||||
|
for i in self.musicbrainz_artists['artist-list']
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_top_five_results(self) -> None:
|
||||||
|
"""Get the top five artists from the Musicbrainzapi
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self._top_five_results = dict(
|
||||||
|
(i, self._accuracy_scores.get(i))
|
||||||
|
for i in sorted(
|
||||||
|
self._accuracy_scores,
|
||||||
|
key=self._accuracy_scores.get,
|
||||||
|
reverse=True,
|
||||||
|
)[0:5]
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def find_all_albums(self) -> None:
|
||||||
|
"""Find all albums for the chosen artist
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
limit, offset, page = (100, 0, 1)
|
||||||
|
|
||||||
|
resp_0 = addict.Dict(
|
||||||
|
musicbrainzngs.browse_release_groups(
|
||||||
|
artist=self.artist_id, release_type=['album'], limit=limit
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
total_releases = resp_0['release-group-count']
|
||||||
|
response_releases = len(resp_0['release-group-list'])
|
||||||
|
|
||||||
|
with click.progressbar(
|
||||||
|
length=total_releases,
|
||||||
|
label=f'Searching Musicbrainz for all albums from {self.artist}',
|
||||||
|
) as bar:
|
||||||
|
|
||||||
|
release_group_ids = addict.Dict(
|
||||||
|
(i.id, i.title)
|
||||||
|
for i in resp_0['release-group-list']
|
||||||
|
if i.type == 'Album'
|
||||||
|
)
|
||||||
|
|
||||||
|
bar.update(response_releases)
|
||||||
|
|
||||||
|
while response_releases > 0:
|
||||||
|
# Get next page
|
||||||
|
offset += limit
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
resp_1 = addict.Dict(
|
||||||
|
musicbrainzngs.browse_release_groups(
|
||||||
|
artist=self.artist_id,
|
||||||
|
release_type=['album'],
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
response_releases = len(resp_1['release-group-list'])
|
||||||
|
|
||||||
|
release_group_ids = addict.Dict(
|
||||||
|
**release_group_ids,
|
||||||
|
**addict.Dict(
|
||||||
|
(i.id, i.title)
|
||||||
|
for i in resp_1['release-group-list']
|
||||||
|
if i.type == 'Album'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
bar.update(response_releases)
|
||||||
|
|
||||||
|
self.release_group_ids = release_group_ids
|
||||||
|
click.echo(f'Found {len(release_group_ids)} albums for {self.artist}.')
|
||||||
|
|
||||||
|
del (resp_0, resp_1)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def find_all_tracks(self) -> None:
|
||||||
|
"""Find all tracks from all albums.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.all_albums = list()
|
||||||
|
total_albums = len(self.release_group_ids)
|
||||||
|
self.total_track_count = 0
|
||||||
|
|
||||||
|
with click.progressbar(
|
||||||
|
length=total_albums,
|
||||||
|
label=(
|
||||||
|
'Searching Musicbrainz for all tracks in all albums for '
|
||||||
|
f'{self.artist}'
|
||||||
|
),
|
||||||
|
) as bar:
|
||||||
|
for id, alb in self.release_group_ids.items():
|
||||||
|
resp_0 = addict.Dict(
|
||||||
|
musicbrainzngs.browse_releases(
|
||||||
|
release_group=id,
|
||||||
|
release_type=['album'],
|
||||||
|
includes=['recordings'],
|
||||||
|
limit=100,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
album_track_count = [
|
||||||
|
i['medium-list'][0]['track-count']
|
||||||
|
for i in resp_0['release-list']
|
||||||
|
]
|
||||||
|
|
||||||
|
self.total_track_count += max(album_track_count)
|
||||||
|
|
||||||
|
max_track_pos = album_track_count.index(max(album_track_count))
|
||||||
|
|
||||||
|
album_tracks = resp_0['release-list'][max_track_pos]
|
||||||
|
|
||||||
|
try:
|
||||||
|
album_year = resp_0['release-list'][
|
||||||
|
max_track_pos
|
||||||
|
].date.split('-')[0]
|
||||||
|
except TypeError:
|
||||||
|
album_year = 'Missing'
|
||||||
|
|
||||||
|
album_tracks = addict.Dict(
|
||||||
|
(
|
||||||
|
alb + f' [{album_year}]',
|
||||||
|
[
|
||||||
|
i.recording.title
|
||||||
|
for i in resp_0['release-list'][max_track_pos][
|
||||||
|
'medium-list'
|
||||||
|
][0]['track-list']
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.all_albums.append(album_tracks)
|
||||||
|
|
||||||
|
bar.update(1)
|
||||||
|
|
||||||
|
# pprint(self.all_albums)
|
||||||
|
click.echo(
|
||||||
|
f'Found {self.total_track_count} tracks across'
|
||||||
|
f' {len(self.release_group_ids)} albums for {self.artist}'
|
||||||
|
)
|
||||||
|
del resp_0
|
||||||
|
return self
|
||||||
|
|
||||||
|
def find_lyrics_urls(self) -> None:
|
||||||
|
"""Construct the URL for the lyrics api.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.all_albums_lyrics_url = list()
|
||||||
|
for x in self.all_albums:
|
||||||
|
for alb, tracks in x.items():
|
||||||
|
lyrics = addict.Dict(
|
||||||
|
(
|
||||||
|
alb,
|
||||||
|
[
|
||||||
|
self.construct_lyrics_url(self.artist, i)
|
||||||
|
for i in tracks
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.all_albums_lyrics_url.append(lyrics)
|
||||||
|
|
||||||
|
# pprint(self.all_albums_lyrics_url)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def find_all_lyrics(self) -> None:
|
||||||
|
"""Get lyrics for each track from the lyrics api
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.all_albums_lyrics = list()
|
||||||
|
|
||||||
|
with click.progressbar(
|
||||||
|
length=self.total_track_count,
|
||||||
|
label=f'Finding lyrics for {self.total_track_count}'
|
||||||
|
f' tracks for {self.artist}. This may take some time! ☕️',
|
||||||
|
) as bar:
|
||||||
|
bar.update(5)
|
||||||
|
for x in self.all_albums_lyrics_url:
|
||||||
|
for alb, urls in x.items():
|
||||||
|
# bar.update(1)
|
||||||
|
update = len(urls)
|
||||||
|
lyrics = addict.Dict(
|
||||||
|
(alb, [self.request_lyrics_from_url(i) for i in urls])
|
||||||
|
)
|
||||||
|
self.all_albums_lyrics.append(lyrics)
|
||||||
|
bar.update(update)
|
||||||
|
|
||||||
|
with open(f'{os.getcwd()}/all_albums_lyrics.json', 'w') as f:
|
||||||
|
json.dump(self.all_albums_lyrics, f, indent=2)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def count_words_in_lyrics(self) -> None:
|
||||||
|
"""Count all words in each track
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.all_albums_lyrics_count = list()
|
||||||
|
# print(self.total_track_count)
|
||||||
|
with click.progressbar(
|
||||||
|
length=self.total_track_count, label=f'Processing lyrics'
|
||||||
|
) as bar:
|
||||||
|
for x in self.all_albums_lyrics:
|
||||||
|
for alb, lyrics in x.items():
|
||||||
|
update = len(lyrics)
|
||||||
|
bar.update(1)
|
||||||
|
lyrics = addict.Dict(
|
||||||
|
(
|
||||||
|
alb,
|
||||||
|
[
|
||||||
|
Counter(i.split()).most_common()
|
||||||
|
if i is not None
|
||||||
|
else 'No Lyrics'
|
||||||
|
for i in lyrics
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.all_albums_lyrics_count.append(lyrics)
|
||||||
|
bar.update(update - 1)
|
||||||
|
click.echo(f'Processed lyrics for {self.total_track_count} tracks.')
|
||||||
|
return self
|
||||||
|
|
||||||
|
def calculate_average_all_albums(self) -> None:
|
||||||
|
"""Summary
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
Description
|
||||||
|
"""
|
||||||
|
self.all_albums_lyrics_sum = list()
|
||||||
|
album_lyrics = self.all_albums_lyrics_count
|
||||||
|
# with open(f'{os.getcwd()}/lyrics_count.json', 'r') as f:
|
||||||
|
# album_lyrics = json.load(f)
|
||||||
|
count = 0
|
||||||
|
for i in album_lyrics:
|
||||||
|
count += len(i)
|
||||||
|
for album, lyrics_list in i.items():
|
||||||
|
album_avg = list()
|
||||||
|
d = addict.Dict()
|
||||||
|
# print(album)
|
||||||
|
for j in lyrics_list:
|
||||||
|
if j != 'No Lyrics':
|
||||||
|
song_total = 0
|
||||||
|
for k in j:
|
||||||
|
song_total += k[1]
|
||||||
|
else:
|
||||||
|
song_total = "No Lyrics"
|
||||||
|
album_avg.append(song_total)
|
||||||
|
# We want to avoid a ValueError when we loop through
|
||||||
|
# the first time
|
||||||
|
try:
|
||||||
|
d = addict.Dict(**d, **addict.Dict(album, album_avg))
|
||||||
|
except ValueError:
|
||||||
|
d = addict.Dict((album, album_avg))
|
||||||
|
# print(d)
|
||||||
|
self.all_albums_lyrics_sum.append(d)
|
||||||
|
# print(count)
|
||||||
|
# with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'w+') as f:
|
||||||
|
# json.dump(self.all_albums_lyrics_sum, f)
|
||||||
|
# return self
|
||||||
|
|
||||||
|
def calculate_final_average_by_album(self) -> None:
|
||||||
|
"""Calculates descriptive statistics by album.
|
||||||
|
"""
|
||||||
|
self.album_statistics = addict.Dict()
|
||||||
|
album_lyrics = self.all_albums_lyrics_sum
|
||||||
|
# with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'r') as f:
|
||||||
|
# album_lyrics = json.load(f)
|
||||||
|
|
||||||
|
for i in album_lyrics:
|
||||||
|
for album, count in i.items():
|
||||||
|
# We filter twice, once to remove strings, then to filter
|
||||||
|
# the integers
|
||||||
|
_count = [d for d in count if isinstance(d, int)]
|
||||||
|
_count = [d for d in _count if d > 1]
|
||||||
|
_d = self.get_descriptive_statistics(_count)
|
||||||
|
self.album_statistics = addict.Dict(
|
||||||
|
**self.album_statistics, **addict.Dict((album, _d))
|
||||||
|
)
|
||||||
|
# with open(f'{os.getcwd()}/album_statistics.json', 'w') as f:
|
||||||
|
# json.dump(self.album_statistics, f, indent=2)
|
||||||
|
# pprint(self.album_statistics)
|
||||||
|
|
||||||
|
def calculate_final_average_by_year(self) -> None:
|
||||||
|
"""Calculates descriptive statistic by year.
|
||||||
|
"""
|
||||||
|
group_by_years = addict.Dict()
|
||||||
|
self.year_statistics = addict.Dict()
|
||||||
|
album_lyrics = self.all_albums_lyrics_sum
|
||||||
|
# with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'r') as f:
|
||||||
|
# album_lyrics = json.load(f)
|
||||||
|
|
||||||
|
# Merge years together
|
||||||
|
for i in album_lyrics:
|
||||||
|
for album, count in i.items():
|
||||||
|
year = album.split('[')[-1].strip(']')
|
||||||
|
try:
|
||||||
|
group_by_years = addict.Dict(
|
||||||
|
**group_by_years, **addict.Dict((year, count))
|
||||||
|
)
|
||||||
|
# First loop returns value error for empty dict
|
||||||
|
except ValueError:
|
||||||
|
group_by_years = addict.Dict((year, count))
|
||||||
|
# Multiple years raise a TypeError - we append
|
||||||
|
except TypeError:
|
||||||
|
group_by_years.get(year).extend(count)
|
||||||
|
|
||||||
|
for year, y_count in group_by_years.items():
|
||||||
|
_y_count = [d for d in y_count if isinstance(d, int)]
|
||||||
|
_y_count = [d for d in _y_count if d > 1]
|
||||||
|
_d = self.get_descriptive_statistics(_y_count)
|
||||||
|
self.year_statistics = addict.Dict(
|
||||||
|
**self.year_statistics, **addict.Dict((year, _d))
|
||||||
|
)
|
||||||
|
# pprint(self.year_statistics)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def construct_lyrics_url(artist: str, song: str) -> str:
|
||||||
|
"""Builds the URL for the lyrics api.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
artist : str
|
||||||
|
Artist
|
||||||
|
song : str
|
||||||
|
Track title
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
URL for lyrics from the lyrics api.
|
||||||
|
"""
|
||||||
|
lyrics_api_base = 'https://api.lyrics.ovh/v1'
|
||||||
|
lyrics_api_url = html.escape(f'{lyrics_api_base}/{artist}/{song}')
|
||||||
|
return lyrics_api_url
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def request_lyrics_from_url(url: str) -> str:
|
||||||
|
"""Gets lyrics from the lyrics api.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
url : str
|
||||||
|
URL of the track for the lyrics api.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
Lyrics of the trakc
|
||||||
|
"""
|
||||||
|
resp = requests.get(url)
|
||||||
|
|
||||||
|
# No lyrics for a song will return a key of 'error', we pass on this.
|
||||||
|
try:
|
||||||
|
lyrics = LyricsBuilder.strip_punctuation(resp.json()['lyrics'])
|
||||||
|
return lyrics
|
||||||
|
except (KeyError, json.decoder.JSONDecodeError):
|
||||||
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def strip_punctuation(word: str) -> str:
|
||||||
|
"""Removes punctuation from lyrics.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
word : str
|
||||||
|
Word to remove punctuation from.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
Same word without any punctuation.
|
||||||
|
"""
|
||||||
|
_strip = word.translate(str.maketrans('', '', string.punctuation))
|
||||||
|
return _strip
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_descriptive_statistics(nums: list) -> Dict[str, int]:
|
||||||
|
"""Calculates descriptive statistics.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
nums : list
|
||||||
|
A list containing total number of words from a track.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Dict[str, int]
|
||||||
|
Dictionary of statistic and value.
|
||||||
|
"""
|
||||||
|
if len(nums) == 0:
|
||||||
|
return
|
||||||
|
avg = math.ceil(np.mean(nums))
|
||||||
|
median = math.ceil(np.median(nums))
|
||||||
|
std = math.ceil(np.std(nums))
|
||||||
|
max = math.ceil(np.max(nums))
|
||||||
|
min = math.ceil(np.min(nums))
|
||||||
|
p_10 = math.ceil(np.percentile(nums, 10))
|
||||||
|
p_25 = math.ceil(np.percentile(nums, 25))
|
||||||
|
p_75 = math.ceil(np.percentile(nums, 75))
|
||||||
|
p_90 = math.ceil(np.percentile(nums, 90))
|
||||||
|
count = len(nums)
|
||||||
|
_d = addict.Dict(
|
||||||
|
('avg', avg),
|
||||||
|
('median', median),
|
||||||
|
('std', std),
|
||||||
|
('max', max),
|
||||||
|
('min', min),
|
||||||
|
('p_10', p_10),
|
||||||
|
('p_25', p_25),
|
||||||
|
('p_75', p_75),
|
||||||
|
('p_90', p_90),
|
||||||
|
('count', count),
|
||||||
|
)
|
||||||
|
return _d
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from abc import ABC, abstractstaticmethod, abstractmethod
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from musicbrainzapi.api import authenticate
|
||||||
|
|
||||||
|
|
||||||
|
class LyricsConcreteBuilder(ABC):
|
||||||
|
"""Abstract concrete builder for Lyrics
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def product(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def artist(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@artist.setter
|
||||||
|
@abstractmethod
|
||||||
|
def artist(self, artist: str) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def country(self) -> Union[str, None]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@country.setter
|
||||||
|
@abstractmethod
|
||||||
|
def country(self, country: Union[str, None]) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def artist_id(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@artist_id.setter
|
||||||
|
@abstractmethod
|
||||||
|
def artist_id(self, artist_id: str) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractstaticmethod
|
||||||
|
def set_useragent():
|
||||||
|
authenticate.set_useragent()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def __init__(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def reset(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_artists(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def sort_artists(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_accuracy_scores(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_top_five_results(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_all_albums(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_all_tracks(self) -> None:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from musicbrainzapi.api.lyrics.builder import LyricsBuilder
|
||||||
|
from musicbrainzapi.api.lyrics import Lyrics
|
||||||
|
|
||||||
|
|
||||||
|
class LyricsClickDirector:
|
||||||
|
"""Director for Lyrics builder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._builder = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def builder(self) -> LyricsBuilder:
|
||||||
|
return self._builder
|
||||||
|
|
||||||
|
@builder.setter
|
||||||
|
def builder(self, builder: LyricsBuilder) -> None:
|
||||||
|
self._builder = builder
|
||||||
|
|
||||||
|
def _get_initial_artists(self, artist: str, country: str) -> None:
|
||||||
|
"""Search Musicbrainz api for an artist
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
artist : str
|
||||||
|
Artist to search for
|
||||||
|
country : str
|
||||||
|
Country artist comes from.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.builder.artist = artist
|
||||||
|
self.builder.country = country
|
||||||
|
self.builder.set_useragent()
|
||||||
|
self.builder.find_artists()
|
||||||
|
self.builder.sort_artists()
|
||||||
|
self.builder.get_accuracy_scores()
|
||||||
|
self.builder.get_top_five_results()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _confirm_final_artist(self) -> None:
|
||||||
|
"""Confirm the artist from the user.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
SystemExit
|
||||||
|
If no artist is found will cleanly quit.
|
||||||
|
"""
|
||||||
|
artist_meta = None
|
||||||
|
for i, j in self.builder._top_five_results.items():
|
||||||
|
artist_meta = 'Multiple' if j <= 100 else None
|
||||||
|
|
||||||
|
if artist_meta == 'Multiple':
|
||||||
|
_position = []
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
f'Musicbrainz found several results for '
|
||||||
|
f'{self.builder.artist[0]}. Which artist/group do you want'
|
||||||
|
'?',
|
||||||
|
fg='green',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for i, j in zip(self.builder._top_five_results, range(1, 6)):
|
||||||
|
click.echo(
|
||||||
|
f'[{j}] {self.builder._sort_names.get(i)}'
|
||||||
|
f' ({self.builder._accuracy_scores.get(i)}% match)'
|
||||||
|
)
|
||||||
|
_position.append(i)
|
||||||
|
chosen = int(
|
||||||
|
click.prompt(
|
||||||
|
click.style(f'Enter choice, default is', blink=True),
|
||||||
|
default=1,
|
||||||
|
type=click.IntRange(
|
||||||
|
1, len(self.builder._top_five_results)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
choice = _position[chosen - 1]
|
||||||
|
click.echo(f'You chose {self.builder._sort_names.get(choice)}')
|
||||||
|
self._artist = self.builder._sort_names.get(choice).split('|')[0]
|
||||||
|
self._artist_id = choice
|
||||||
|
|
||||||
|
# Set artist and artistID on builder + product
|
||||||
|
self.builder.artist_id = self._artist_id
|
||||||
|
self.builder.artist = self._artist
|
||||||
|
|
||||||
|
elif artist_meta is None:
|
||||||
|
click.echo(
|
||||||
|
f'Musicbrainz did not find any results for '
|
||||||
|
f'{self.builder.artist[0]}. Check the spelling or consider '
|
||||||
|
'alternative names that the artist/group may go by.'
|
||||||
|
)
|
||||||
|
raise SystemExit()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _query_for_data(self) -> None:
|
||||||
|
"""Query Musicbrainz api for albums + track data.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.builder.find_all_albums()
|
||||||
|
self.builder.find_all_tracks()
|
||||||
|
self.builder._product.all_albums_with_tracks = self.builder.all_albums
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _get_lyrics(self) -> None:
|
||||||
|
"""Get Lyrics for each track
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.builder.find_lyrics_urls()
|
||||||
|
self.builder.find_all_lyrics()
|
||||||
|
self.builder._product.all_albums_with_lyrics = (
|
||||||
|
self.builder.all_albums_lyrics
|
||||||
|
)
|
||||||
|
self.builder.count_words_in_lyrics()
|
||||||
|
with open(f'{os.getcwd()}/lyrics_count.json', 'w+') as file:
|
||||||
|
json.dump(
|
||||||
|
self.builder.all_albums_lyrics_count,
|
||||||
|
file,
|
||||||
|
indent=2,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
self.builder._product.all_albums_lyrics_count = (
|
||||||
|
self.builder.all_albums_lyrics_count
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _calculate_basic_statistics(self) -> None:
|
||||||
|
"""Calculate a basic average for all tracks.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.builder.calculate_average_all_albums()
|
||||||
|
self.builder._product.all_albums_lyrics_sum = (
|
||||||
|
self.builder.all_albums_lyrics_sum
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _calculate_descriptive_statistics(self) -> None:
|
||||||
|
"""Calculate descriptive statistics for album and/or year.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.builder.calculate_final_average_by_album()
|
||||||
|
self.builder.calculate_final_average_by_year()
|
||||||
|
self.builder._product.album_statistics = self.builder.album_statistics
|
||||||
|
self.builder._product.year_statistics = self.builder.year_statistics
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _dev(self) -> None:
|
||||||
|
"""Dev function - used for testing
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.builder.calculate_final_average_by_album()
|
||||||
|
self.builder.calculate_final_average_by_year()
|
||||||
|
self.builder._product.album_statistics = self.builder.album_statistics
|
||||||
|
self.builder._product.year_statistics = self.builder.year_statistics
|
||||||
|
self.builder._product.artist_id = None
|
||||||
|
self.builder._product.artist = 'Katzenjammer'
|
||||||
|
self.builder._product.show_summary()
|
||||||
|
self.builder._product.show_summary_statistics(group_by='year')
|
||||||
|
return self
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_product(builder_inst: LyricsBuilder) -> Lyrics:
|
||||||
|
"""Returns the constructed Lyrics object
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
builder_inst : LyricsBuilder
|
||||||
|
Builder class for Lyrics object
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Lyrics
|
||||||
|
Lyrics object
|
||||||
|
"""
|
||||||
|
return builder_inst._product
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@@ -31,12 +30,16 @@ class ComplexCLI(click.MultiCommand):
|
|||||||
rv.sort()
|
rv.sort()
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
# def get_command(self, ctx, name):
|
||||||
|
# try:
|
||||||
|
# mod = import_module(f'musicbrainzapi.cli.commands.cmd_{name}')
|
||||||
|
# except ImportError as e:
|
||||||
|
# print(e)
|
||||||
|
# return
|
||||||
|
# return mod.cli
|
||||||
|
|
||||||
def get_command(self, ctx, name):
|
def get_command(self, ctx, name):
|
||||||
try:
|
mod = import_module(f'musicbrainzapi.cli.commands.cmd_{name}')
|
||||||
mod = import_module(f'musicbrainzapi.cli.commands.cmd_{name}')
|
|
||||||
except ImportError as e:
|
|
||||||
print(e)
|
|
||||||
return
|
|
||||||
return mod.cli
|
return mod.cli
|
||||||
|
|
||||||
|
|
||||||
@@ -61,9 +64,5 @@ def cli(ctx, path):
|
|||||||
"""A complex command line interface."""
|
"""A complex command line interface."""
|
||||||
# ctx.verbose = verbose
|
# ctx.verbose = verbose
|
||||||
if path is not None:
|
if path is not None:
|
||||||
# click.echo(f'Path set to {os.path.expanduser(path)}')
|
click.echo(f'Path set to {os.path.expanduser(path)}')
|
||||||
ctx.path = os.path.expanduser(path)
|
ctx.path = os.path.expanduser(path)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
cli()
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@@ -5,20 +6,18 @@ import click
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
from musicbrainzapi.cli.cli import pass_environment
|
from musicbrainzapi.cli.cli import pass_environment
|
||||||
from musicbrainzapi.api.command_builders import lyrics
|
|
||||||
import musicbrainzapi.wordcloud
|
import musicbrainzapi.wordcloud
|
||||||
|
from musicbrainzapi.api.lyrics.builder import LyricsBuilder
|
||||||
|
from musicbrainzapi.api.lyrics.director import LyricsClickDirector
|
||||||
|
|
||||||
|
|
||||||
# @click.argument('path', required=False, type=click.Path(resolve_path=True))
|
@click.option('--dev', is_flag=True, help='Development flag. Do not use.')
|
||||||
# @click.command(short_help='a test command')
|
|
||||||
|
|
||||||
|
|
||||||
@click.option('--dev', is_flag=True)
|
|
||||||
@click.option(
|
@click.option(
|
||||||
'--save-output',
|
'--save-output',
|
||||||
required=False,
|
required=False,
|
||||||
help='Save the output to json files locally. Will use the path parameter if'
|
help='Save the output to json files locally. Will use the path parameter'
|
||||||
' provided else defaults to current working directory.',
|
' if provided else defaults to current working directory.',
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
@@ -64,12 +63,27 @@ def cli(
|
|||||||
wordcloud: bool,
|
wordcloud: bool,
|
||||||
save_output: bool,
|
save_output: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Search for lyrics statistics of an Artist/Group."""
|
"""Search for lyrics statistics of an Artist/Group.
|
||||||
path = ctx.path
|
|
||||||
print(f'home={ctx.home}')
|
Parameters
|
||||||
# lyrics_obj = list()
|
----------
|
||||||
director = lyrics.LyricsClickDirector()
|
ctx : musicbrainzapi.cli.cli.Environment
|
||||||
builder = lyrics.LyricsBuilder()
|
click environment class
|
||||||
|
artist : str
|
||||||
|
artist
|
||||||
|
country : Union[str, None]
|
||||||
|
country
|
||||||
|
dev : bool
|
||||||
|
dev flag - not to be used
|
||||||
|
show_summary : str
|
||||||
|
summary flag - used to display descriptive statistics
|
||||||
|
wordcloud : bool
|
||||||
|
wordcloud flag - used to create a wordcloud from lyrics
|
||||||
|
save_output : bool
|
||||||
|
save output flag - used to save output locally to disk
|
||||||
|
"""
|
||||||
|
director = LyricsClickDirector()
|
||||||
|
builder = LyricsBuilder()
|
||||||
director.builder = builder
|
director.builder = builder
|
||||||
if dev:
|
if dev:
|
||||||
director._dev()
|
director._dev()
|
||||||
@@ -105,7 +119,9 @@ def cli(
|
|||||||
lyrics_0.all_albums_lyrics_count
|
lyrics_0.all_albums_lyrics_count
|
||||||
)
|
)
|
||||||
cloud.create_word_cloud()
|
cloud.create_word_cloud()
|
||||||
click.confirm('Wordcloud ready - press enter to show.', default=True)
|
show = click.confirm(
|
||||||
|
'Wordcloud ready - press enter to show.', default=True
|
||||||
|
)
|
||||||
plt.imshow(
|
plt.imshow(
|
||||||
cloud.wc.recolor(
|
cloud.wc.recolor(
|
||||||
color_func=cloud.generate_grey_colours, random_state=3
|
color_func=cloud.generate_grey_colours, random_state=3
|
||||||
@@ -113,4 +129,12 @@ def cli(
|
|||||||
interpolation='bilinear',
|
interpolation='bilinear',
|
||||||
)
|
)
|
||||||
plt.axis('off')
|
plt.axis('off')
|
||||||
plt.show()
|
if show:
|
||||||
|
plt.show()
|
||||||
|
if save_output:
|
||||||
|
click.echo(f'Saving output to {ctx.path}')
|
||||||
|
path = ctx.path if ctx.path[-1] == '/' else ctx.path + '/'
|
||||||
|
attr = lyrics_0._attributes
|
||||||
|
for a in attr:
|
||||||
|
with open(f'{path}{a}.json', 'w') as f:
|
||||||
|
json.dump(getattr(lyrics_0, a), f, indent=2)
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -10,21 +10,44 @@ from PIL import Image
|
|||||||
from wordcloud import STOPWORDS, WordCloud
|
from wordcloud import STOPWORDS, WordCloud
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from musicbrainzapi.api.command_builders.lyrics import Lyrics
|
from musicbrainzapi.api.lyrics import Lyrics
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
import PIL.PngImagePlugin.PngImageFile
|
import PIL.PngImagePlugin.PngImageFile
|
||||||
|
|
||||||
|
|
||||||
class LyricsWordcloud:
|
class LyricsWordcloud:
|
||||||
|
"""Create a word cloud from Lyrics.
|
||||||
|
|
||||||
"""docstring for LyricsWordcloud"""
|
Attributes
|
||||||
|
----------
|
||||||
|
all_albums_lyrics_count : list
|
||||||
|
List of all albums + track lyrics counted by each word
|
||||||
|
char_mask : np.array
|
||||||
|
numpy array containing data for the word cloud image
|
||||||
|
freq : collections.Counter
|
||||||
|
Counter object containing counts for all words across all tracks
|
||||||
|
lyrics_list : list
|
||||||
|
List of all words from all lyrics across all tracks.
|
||||||
|
pillow_img : PIL.PngImagePlugin.PngImageFile
|
||||||
|
pillow image of the word cloud base
|
||||||
|
wc : wordcloud.WordCloud
|
||||||
|
WordCloud object
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
pillow_img: 'PIL.PngImagePlugin.PngImageFile',
|
pillow_img: 'PIL.PngImagePlugin.PngImageFile',
|
||||||
all_albums_lyrics_count: 'Lyrics.all_albums_lyrics_count',
|
all_albums_lyrics_count: 'Lyrics.all_albums_lyrics_count',
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
pillow_img : PIL.PngImagePlugin.PngImageFile
|
||||||
|
pillow image of the word cloud base
|
||||||
|
all_albums_lyrics_count : Lyrics.all_albums_lyrics_count
|
||||||
|
List of all albums + track lyrics counted by each word
|
||||||
|
"""
|
||||||
self.pillow_img = pillow_img
|
self.pillow_img = pillow_img
|
||||||
self.all_albums_lyrics_count = all_albums_lyrics_count
|
self.all_albums_lyrics_count = all_albums_lyrics_count
|
||||||
|
|
||||||
@@ -32,6 +55,13 @@ class LyricsWordcloud:
|
|||||||
def use_microphone(
|
def use_microphone(
|
||||||
cls, all_albums_lyrics_count: 'Lyrics.all_albums_lyrics_count',
|
cls, all_albums_lyrics_count: 'Lyrics.all_albums_lyrics_count',
|
||||||
) -> LyricsWordcloud:
|
) -> LyricsWordcloud:
|
||||||
|
"""Class method to instantiate with a microphone base image.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
all_albums_lyrics_count : Lyrics.all_albums_lyrics_count
|
||||||
|
List of all albums + track lyrics counted by each word
|
||||||
|
"""
|
||||||
mic_resource = resources.path(
|
mic_resource = resources.path(
|
||||||
'musicbrainzapi.wordcloud.resources', 'mic.png'
|
'musicbrainzapi.wordcloud.resources', 'mic.png'
|
||||||
)
|
)
|
||||||
@@ -41,6 +71,12 @@ class LyricsWordcloud:
|
|||||||
return cls(mic_img, all_albums_lyrics_count)
|
return cls(mic_img, all_albums_lyrics_count)
|
||||||
|
|
||||||
def _get_lyrics_list(self) -> None:
|
def _get_lyrics_list(self) -> None:
|
||||||
|
"""Gets all words from lyrics in a single list + cleans them.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self.lyrics_list = list()
|
self.lyrics_list = list()
|
||||||
for i in self.all_albums_lyrics_count:
|
for i in self.all_albums_lyrics_count:
|
||||||
for album, lyric in i.items():
|
for album, lyric in i.items():
|
||||||
@@ -64,9 +100,13 @@ class LyricsWordcloud:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def _get_frequencies(self) -> None:
|
def _get_frequencies(self) -> None:
|
||||||
|
"""Get frequencies of words from a list.
|
||||||
|
"""
|
||||||
self.freq = collections.Counter(self.lyrics_list)
|
self.freq = collections.Counter(self.lyrics_list)
|
||||||
|
|
||||||
def _get_char_mask(self) -> None:
|
def _get_char_mask(self) -> None:
|
||||||
|
"""Gets a numpy array for the image file.
|
||||||
|
"""
|
||||||
self.char_mask = np.array(self.pillow_img)
|
self.char_mask = np.array(self.pillow_img)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -81,16 +121,28 @@ class LyricsWordcloud:
|
|||||||
return colour
|
return colour
|
||||||
|
|
||||||
def _generate_word_cloud(self) -> None:
|
def _generate_word_cloud(self) -> None:
|
||||||
|
"""Generates a word cloud
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self.wc = WordCloud(
|
self.wc = WordCloud(
|
||||||
max_words=50,
|
max_words=150,
|
||||||
width=500,
|
width=1500,
|
||||||
height=500,
|
height=1500,
|
||||||
mask=self.char_mask,
|
mask=self.char_mask,
|
||||||
random_state=1,
|
random_state=1,
|
||||||
).generate_from_frequencies(self.freq)
|
).generate_from_frequencies(self.freq)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _generate_plot(self) -> None:
|
def _generate_plot(self) -> None:
|
||||||
|
"""Plots the wordcloud and sets matplotlib options.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
plt.imshow(
|
plt.imshow(
|
||||||
self.wc.recolor(
|
self.wc.recolor(
|
||||||
color_func=self.generate_grey_colours, random_state=3
|
color_func=self.generate_grey_colours, random_state=3
|
||||||
@@ -100,13 +152,18 @@ class LyricsWordcloud:
|
|||||||
plt.axis('off')
|
plt.axis('off')
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def save_to_disk(self, path: str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def show_word_cloud(self):
|
def show_word_cloud(self):
|
||||||
|
"""Shows the word cloud.
|
||||||
|
"""
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
def create_word_cloud(self) -> None:
|
def create_word_cloud(self) -> None:
|
||||||
|
"""Creates a word cloud
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self._get_lyrics_list()
|
self._get_lyrics_list()
|
||||||
self._get_frequencies()
|
self._get_frequencies()
|
||||||
self._get_char_mask()
|
self._get_char_mask()
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 220 KiB |
Reference in New Issue
Block a user