Merge branch 'rework' into develop
This commit is contained in:
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"The All\u2010American Rejects [2003]": null,
|
|
||||||
"Move Along [2005]": null,
|
|
||||||
"B-Sides & Rarities [2007]": null,
|
|
||||||
"When the World Comes Down [2009]": null,
|
|
||||||
"Kids in the Street [2012]": null,
|
|
||||||
"The All\u2010American Rejects [2000]": null
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"The All\u2010American Rejects [2003]": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Move Along [2005]": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"B-Sides & Rarities [2007]": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"When the World Comes Down [2009]": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kids in the Street [2012]": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"The All\u2010American Rejects [2000]": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"The All\u2010American Rejects [2003]": [
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Move Along [2005]": [
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"B-Sides & Rarities [2007]": [
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"When the World Comes Down [2009]": [
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kids in the Street [2012]": [
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"The All\u2010American Rejects [2000]": [
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics",
|
|
||||||
"No Lyrics"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[{"The All\u2010American Rejects [2003]": ["No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics"]}, {"Move Along [2005]": ["No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics"]}, {"B-Sides & Rarities [2007]": ["No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics"]}, {"When the World Comes Down [2009]": ["No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics"]}, {"Kids in the Street [2012]": ["No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics"]}, {"The All\u2010American Rejects [2000]": ["No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics", "No Lyrics"]}]
|
|
||||||
6569
output.json
6569
output.json
File diff suppressed because it is too large
Load Diff
208
poetry.lock
generated
208
poetry.lock
generated
@@ -109,6 +109,17 @@ optional = false
|
|||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Composable style cycles"
|
||||||
|
name = "cycler"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.10.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
@@ -147,6 +158,32 @@ parso = ">=0.5.2"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["colorama (0.4.1)", "docopt", "pytest (>=3.9.0,<5.0.0)"]
|
testing = ["colorama (0.4.1)", "docopt", "pytest (>=3.9.0,<5.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A fast implementation of the Cassowary constraint solver"
|
||||||
|
name = "kiwisolver"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.1.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
setuptools = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python plotting package"
|
||||||
|
name = "matplotlib"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
version = "3.2.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cycler = ">=0.10"
|
||||||
|
kiwisolver = ">=1.0.1"
|
||||||
|
numpy = ">=1.11"
|
||||||
|
pyparsing = ">=2.0.1,<2.0.4 || >2.0.4,<2.1.2 || >2.1.2,<2.1.6 || >2.1.6"
|
||||||
|
python-dateutil = ">=2.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
description = "McCabe checker, plugin for flake8"
|
description = "McCabe checker, plugin for flake8"
|
||||||
@@ -163,6 +200,14 @@ optional = false
|
|||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
version = "8.2.0"
|
version = "8.2.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "multidict implementation"
|
||||||
|
name = "multidict"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "4.7.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Python bindings for the MusicBrainz NGS and the Cover Art Archive webservices"
|
description = "Python bindings for the MusicBrainz NGS and the Cover Art Archive webservices"
|
||||||
@@ -210,6 +255,14 @@ optional = false
|
|||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python Imaging Library (Fork)"
|
||||||
|
name = "pillow"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "7.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
description = "plugin and hook calling mechanisms for python"
|
description = "plugin and hook calling mechanisms for python"
|
||||||
@@ -306,7 +359,7 @@ toml = "*"
|
|||||||
dev = ["isort", "flake8", "pytest", "mypy"]
|
dev = ["isort", "flake8", "pytest", "mypy"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "main"
|
||||||
description = "Python parsing module"
|
description = "Python parsing module"
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -339,6 +392,17 @@ version = ">=0.12"
|
|||||||
checkqa-mypy = ["mypy (v0.761)"]
|
checkqa-mypy = ["mypy (v0.761)"]
|
||||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Extensions to the standard Python datetime module"
|
||||||
|
name = "python-dateutil"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||||
|
version = "2.8.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = ">=1.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
description = "JSON RPC 2.0 server library"
|
description = "JSON RPC 2.0 server library"
|
||||||
@@ -418,7 +482,7 @@ version = "0.16.0"
|
|||||||
dev = ["pytest"]
|
dev = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "main"
|
||||||
description = "Python 2 and 3 compatibility utilities"
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
name = "six"
|
name = "six"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -487,6 +551,19 @@ optional = false
|
|||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A little word cloud generator"
|
||||||
|
name = "wordcloud"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "1.6.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
matplotlib = "*"
|
||||||
|
numpy = ">=1.6.1"
|
||||||
|
pillow = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
description = "A formatter for Python code."
|
description = "A formatter for Python code."
|
||||||
@@ -509,7 +586,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
|||||||
testing = ["jaraco.itertools", "func-timeout"]
|
testing = ["jaraco.itertools", "func-timeout"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
content-hash = "b3e363ce109826fbe16ba73b6102a62254689ead837a371aecd2857fa5ed9f45"
|
content-hash = "6755748d710bead6ddf5f543c09fb33a09b340dcff15466f7e6759ce9c659004"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
@@ -556,6 +633,10 @@ colorama = [
|
|||||||
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
||||||
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
||||||
]
|
]
|
||||||
|
cycler = [
|
||||||
|
{file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"},
|
||||||
|
{file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"},
|
||||||
|
]
|
||||||
idna = [
|
idna = [
|
||||||
{file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
|
{file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
|
||||||
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
|
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
|
||||||
@@ -568,6 +649,61 @@ jedi = [
|
|||||||
{file = "jedi-0.15.2-py2.py3-none-any.whl", hash = "sha256:1349c1e8c107095a55386628bb3b2a79422f3a2cab8381e34ce19909e0cf5064"},
|
{file = "jedi-0.15.2-py2.py3-none-any.whl", hash = "sha256:1349c1e8c107095a55386628bb3b2a79422f3a2cab8381e34ce19909e0cf5064"},
|
||||||
{file = "jedi-0.15.2.tar.gz", hash = "sha256:e909527104a903606dd63bea6e8e888833f0ef087057829b89a18364a856f807"},
|
{file = "jedi-0.15.2.tar.gz", hash = "sha256:e909527104a903606dd63bea6e8e888833f0ef087057829b89a18364a856f807"},
|
||||||
]
|
]
|
||||||
|
kiwisolver = [
|
||||||
|
{file = "kiwisolver-1.1.0-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:fe51b79da0062f8e9d49ed0182a626a7dc7a0cbca0328f612c6ee5e4711c81e4"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp27-none-win32.whl", hash = "sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp27-none-win_amd64.whl", hash = "sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp34-cp34m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp34-none-win32.whl", hash = "sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp34-none-win_amd64.whl", hash = "sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:aa716b9122307c50686356cfb47bfbc66541868078d0c801341df31dca1232a9"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp35-none-win32.whl", hash = "sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp35-none-win_amd64.whl", hash = "sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:9105ce82dcc32c73eb53a04c869b6a4bc756b43e4385f76ea7943e827f529e4d"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp36-none-win32.whl", hash = "sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp36-none-win_amd64.whl", hash = "sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9491578147849b93e70d7c1d23cb1229458f71fc79c51d52dce0809b2ca44eea"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp37-none-win32.whl", hash = "sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp37-none-win_amd64.whl", hash = "sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:933df612c453928f1c6faa9236161a1d999a26cd40abf1dc5d7ebbc6dbfb8fca"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d22702cadb86b6fcba0e6b907d9f84a312db9cd6934ee728144ce3018e715ee1"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:210d8c39d01758d76c2b9a693567e1657ec661229bc32eac30761fa79b2474b0"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp38-none-win32.whl", hash = "sha256:76275ee077772c8dde04fb6c5bc24b91af1bb3e7f4816fd1852f1495a64dad93"},
|
||||||
|
{file = "kiwisolver-1.1.0-cp38-none-win_amd64.whl", hash = "sha256:3b15d56a9cd40c52d7ab763ff0bc700edbb4e1a298dc43715ecccd605002cf11"},
|
||||||
|
{file = "kiwisolver-1.1.0.tar.gz", hash = "sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75"},
|
||||||
|
]
|
||||||
|
matplotlib = [
|
||||||
|
{file = "matplotlib-3.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0711b07920919951b2c508a773c433cbe07bdad952ea84ed9d18ca7853ccbe8b"},
|
||||||
|
{file = "matplotlib-3.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b93377c6720e7db9cbba57e856a21aae2ff707677a6ee6b3b9d485f22ed82697"},
|
||||||
|
{file = "matplotlib-3.2.0-cp36-cp36m-win32.whl", hash = "sha256:8e931015769322ee6860cabb8f975f628788e851092fd5edbdb065b5a516e3af"},
|
||||||
|
{file = "matplotlib-3.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b21479a4478070c1c0f460e1bf1b65341e6a70ae0da905fcee836651450c66bb"},
|
||||||
|
{file = "matplotlib-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab307e610302971012dc2387c97fc68e58c8eb00045a2c735da1b16353a3e3f"},
|
||||||
|
{file = "matplotlib-3.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d75f5e952562f5e494ae92c1f917fc96c2ce09305a7c1bdc2e6502d3c61fbdc3"},
|
||||||
|
{file = "matplotlib-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:9d174cc9681184023a7d520079eb0c085208761c6562710c1de7263d08217ab6"},
|
||||||
|
{file = "matplotlib-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d281862a68b0bfce8f9e02a8e5acaa5cfbec37f37320f59b52eaf54b6423ec13"},
|
||||||
|
{file = "matplotlib-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee8acb1d4ee204e5cfe361d8f00d7e52c68f81c099b6c6048a3c76bf2c6b46e6"},
|
||||||
|
{file = "matplotlib-3.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:be937f34047bc09ed22d6a19d970fdc61d5d3191aa62f3262fc7f308e6d2e7f9"},
|
||||||
|
{file = "matplotlib-3.2.0-cp38-cp38-win32.whl", hash = "sha256:97a03e73f9ab71db8e4084894550c3af420c8ab1989b5e1306261b17576bf61b"},
|
||||||
|
{file = "matplotlib-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:d5287cfcabad6f0f71a2627c1bbb6fb0cddacb9844f6c91f210604faa508f562"},
|
||||||
|
{file = "matplotlib-3.2.0-pp373-pypy36_pp73-win32.whl", hash = "sha256:fc84f7c7cf1c5a9dbceadb7546818228f019d3b113ce5e362120c895fbba2944"},
|
||||||
|
{file = "matplotlib-3.2.0.tar.gz", hash = "sha256:651d76daf9168250370d4befb09f79875daa2224a9096d97dfc3ed764c842be4"},
|
||||||
|
]
|
||||||
mccabe = [
|
mccabe = [
|
||||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||||
@@ -576,6 +712,25 @@ more-itertools = [
|
|||||||
{file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"},
|
{file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"},
|
||||||
{file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"},
|
{file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"},
|
||||||
]
|
]
|
||||||
|
multidict = [
|
||||||
|
{file = "multidict-4.7.5-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3"},
|
||||||
|
{file = "multidict-4.7.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35"},
|
||||||
|
{file = "multidict-4.7.5-cp35-cp35m-win32.whl", hash = "sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1"},
|
||||||
|
{file = "multidict-4.7.5-cp35-cp35m-win_amd64.whl", hash = "sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd"},
|
||||||
|
{file = "multidict-4.7.5-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20"},
|
||||||
|
{file = "multidict-4.7.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136"},
|
||||||
|
{file = "multidict-4.7.5-cp36-cp36m-win32.whl", hash = "sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e"},
|
||||||
|
{file = "multidict-4.7.5-cp36-cp36m-win_amd64.whl", hash = "sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78"},
|
||||||
|
{file = "multidict-4.7.5-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8"},
|
||||||
|
{file = "multidict-4.7.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab"},
|
||||||
|
{file = "multidict-4.7.5-cp37-cp37m-win32.whl", hash = "sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928"},
|
||||||
|
{file = "multidict-4.7.5-cp37-cp37m-win_amd64.whl", hash = "sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1"},
|
||||||
|
{file = "multidict-4.7.5-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4"},
|
||||||
|
{file = "multidict-4.7.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2"},
|
||||||
|
{file = "multidict-4.7.5-cp38-cp38-win32.whl", hash = "sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5"},
|
||||||
|
{file = "multidict-4.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969"},
|
||||||
|
{file = "multidict-4.7.5.tar.gz", hash = "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e"},
|
||||||
|
]
|
||||||
musicbrainzngs = [
|
musicbrainzngs = [
|
||||||
{file = "musicbrainzngs-0.7.1-py2.py3-none-any.whl", hash = "sha256:e841a8f975104c0a72290b09f59326050194081a5ae62ee512f41915090e1a10"},
|
{file = "musicbrainzngs-0.7.1-py2.py3-none-any.whl", hash = "sha256:e841a8f975104c0a72290b09f59326050194081a5ae62ee512f41915090e1a10"},
|
||||||
{file = "musicbrainzngs-0.7.1.tar.gz", hash = "sha256:ab1c0100fd0b305852e65f2ed4113c6de12e68afd55186987b8ed97e0f98e627"},
|
{file = "musicbrainzngs-0.7.1.tar.gz", hash = "sha256:ab1c0100fd0b305852e65f2ed4113c6de12e68afd55186987b8ed97e0f98e627"},
|
||||||
@@ -615,6 +770,30 @@ pathspec = [
|
|||||||
{file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"},
|
{file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"},
|
||||||
{file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"},
|
{file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"},
|
||||||
]
|
]
|
||||||
|
pillow = [
|
||||||
|
{file = "Pillow-7.0.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00"},
|
||||||
|
{file = "Pillow-7.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff"},
|
||||||
|
{file = "Pillow-7.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865"},
|
||||||
|
{file = "Pillow-7.0.0-cp35-cp35m-win32.whl", hash = "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386"},
|
||||||
|
{file = "Pillow-7.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435"},
|
||||||
|
{file = "Pillow-7.0.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2"},
|
||||||
|
{file = "Pillow-7.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317"},
|
||||||
|
{file = "Pillow-7.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2"},
|
||||||
|
{file = "Pillow-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313"},
|
||||||
|
{file = "Pillow-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0"},
|
||||||
|
{file = "Pillow-7.0.0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f"},
|
||||||
|
{file = "Pillow-7.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636"},
|
||||||
|
{file = "Pillow-7.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9"},
|
||||||
|
{file = "Pillow-7.0.0-cp37-cp37m-win32.whl", hash = "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837"},
|
||||||
|
{file = "Pillow-7.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda"},
|
||||||
|
{file = "Pillow-7.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be"},
|
||||||
|
{file = "Pillow-7.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533"},
|
||||||
|
{file = "Pillow-7.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614"},
|
||||||
|
{file = "Pillow-7.0.0-cp38-cp38-win32.whl", hash = "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a"},
|
||||||
|
{file = "Pillow-7.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d"},
|
||||||
|
{file = "Pillow-7.0.0-pp373-pypy36_pp73-win32.whl", hash = "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358"},
|
||||||
|
{file = "Pillow-7.0.0.tar.gz", hash = "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946"},
|
||||||
|
]
|
||||||
pluggy = [
|
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"},
|
||||||
@@ -657,6 +836,10 @@ pytest = [
|
|||||||
{file = "pytest-5.3.5-py3-none-any.whl", hash = "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6"},
|
{file = "pytest-5.3.5-py3-none-any.whl", hash = "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6"},
|
||||||
{file = "pytest-5.3.5.tar.gz", hash = "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d"},
|
{file = "pytest-5.3.5.tar.gz", hash = "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d"},
|
||||||
]
|
]
|
||||||
|
python-dateutil = [
|
||||||
|
{file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
|
||||||
|
{file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
|
||||||
|
]
|
||||||
python-jsonrpc-server = [
|
python-jsonrpc-server = [
|
||||||
{file = "python-jsonrpc-server-0.3.4.tar.gz", hash = "sha256:c73bf5495c9dd4d2f902755bedeb6da5afe778e0beee82f0e195c4655352fe37"},
|
{file = "python-jsonrpc-server-0.3.4.tar.gz", hash = "sha256:c73bf5495c9dd4d2f902755bedeb6da5afe778e0beee82f0e195c4655352fe37"},
|
||||||
{file = "python_jsonrpc_server-0.3.4-py3-none-any.whl", hash = "sha256:1f85f75f37f923149cc0aa078474b6df55b708e82ed819ca8846a65d7d0ada7f"},
|
{file = "python_jsonrpc_server-0.3.4-py3-none-any.whl", hash = "sha256:1f85f75f37f923149cc0aa078474b6df55b708e82ed819ca8846a65d7d0ada7f"},
|
||||||
@@ -747,6 +930,25 @@ wcwidth = [
|
|||||||
{file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"},
|
{file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"},
|
||||||
{file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"},
|
{file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"},
|
||||||
]
|
]
|
||||||
|
wordcloud = [
|
||||||
|
{file = "wordcloud-1.6.0-cp27-cp27m-macosx_10_6_x86_64.whl", hash = "sha256:b99157f068826697d93d2e5e61b1acff35591d5e534818368ccd56945b9a5f29"},
|
||||||
|
{file = "wordcloud-1.6.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:60c9178ea11d6537f19dad7eb5387f2516737796827710c9409ab9602d9493c7"},
|
||||||
|
{file = "wordcloud-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0baf47567bd426bf65963d53a1aaa69af35c2e096dc0ad9073efd5833cccd20a"},
|
||||||
|
{file = "wordcloud-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:52d0772e385e38144be2bdb58a0d7817f2c80db0640e1efad699cff8ea86533d"},
|
||||||
|
{file = "wordcloud-1.6.0-cp34-cp34m-macosx_10_6_x86_64.whl", hash = "sha256:61156874a21fffb46cdfb3518bbc9865fbfe9973ecc36eff20e86792687e439b"},
|
||||||
|
{file = "wordcloud-1.6.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:358f4ead931bc8297de3dbd3a26ce8d1e3fe27c1027cce091c1b7037e4ba4904"},
|
||||||
|
{file = "wordcloud-1.6.0-cp34-cp34m-win_amd64.whl", hash = "sha256:473b660baee64578dad272a18253b59245a337f5dfa3a186e32cf20b0eee4110"},
|
||||||
|
{file = "wordcloud-1.6.0-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:950882b89298c318e5f7cf10027f00b4e09402e18f719cb656aea5209a57e5a9"},
|
||||||
|
{file = "wordcloud-1.6.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a8d829e19431709c1310a505687fc7c0f869c48259f4a55b5bf387642ed6da46"},
|
||||||
|
{file = "wordcloud-1.6.0-cp35-cp35m-win_amd64.whl", hash = "sha256:e9ae81e8dbb5953f8cf94083b990c760b179b4000dae2babd14827d61230fc69"},
|
||||||
|
{file = "wordcloud-1.6.0-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:ae6c0030a7fd09bd35713592ba005da9457f7d38f46dc807484c5e0a379d813c"},
|
||||||
|
{file = "wordcloud-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c59387b35af772626d4a87b986eb8ab29d3d7ffca6f94da95f4c3a0961407df3"},
|
||||||
|
{file = "wordcloud-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b0256ca213eb52e5261307e64faaf242742ada1322bb9d5090ecdaa9b44540ee"},
|
||||||
|
{file = "wordcloud-1.6.0-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:fc3db0cc71e4d5666f732c5b4b3c04a0d58242579cb6c6e5146ffd2890cc5d57"},
|
||||||
|
{file = "wordcloud-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d4b970d4d30bc9baec9e8b2d7e69fb9771576bb09d6b6f6ce6f22403ca58d6de"},
|
||||||
|
{file = "wordcloud-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3971ca6042745169e9645b3bbce64b790f8c211ad7c7d265049992506e033212"},
|
||||||
|
{file = "wordcloud-1.6.0.tar.gz", hash = "sha256:4335deb87b7cd9f8a6ce12de0257d15f14f98874f326e7a839f27b2c8ac792ca"},
|
||||||
|
]
|
||||||
yapf = [
|
yapf = [
|
||||||
{file = "yapf-0.29.0-py2.py3-none-any.whl", hash = "sha256:cad8a272c6001b3401de3278238fdc54997b6c2e56baa751788915f879a52fca"},
|
{file = "yapf-0.29.0-py2.py3-none-any.whl", hash = "sha256:cad8a272c6001b3401de3278238fdc54997b6c2e56baa751788915f879a52fca"},
|
||||||
{file = "yapf-0.29.0.tar.gz", hash = "sha256:712e23c468506bf12cadd10169f852572ecc61b266258422d45aaf4ad7ef43de"},
|
{file = "yapf-0.29.0.tar.gz", hash = "sha256:712e23c468506bf12cadd10169f852572ecc61b266258422d45aaf4ad7ef43de"},
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ addict = "^2.2.1"
|
|||||||
progress = "^1.5"
|
progress = "^1.5"
|
||||||
numpy = "^1.18.1"
|
numpy = "^1.18.1"
|
||||||
beautifultable = "^0.8.0"
|
beautifultable = "^0.8.0"
|
||||||
|
wordcloud = "^1.6.0"
|
||||||
|
multidict = "^4.7.5"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pytest = "^5.2"
|
pytest = "^5.2"
|
||||||
|
|||||||
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/musicbrainzapi/.DS_Store
vendored
Normal file
BIN
src/musicbrainzapi/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/musicbrainzapi/api/.DS_Store
vendored
Normal file
BIN
src/musicbrainzapi/api/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
from . import lyrics
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import musicbrainzngs
|
|
||||||
from musicbrainzapi import __header__, __version__
|
|
||||||
import addict
|
|
||||||
|
|
||||||
from pprint import pprint
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
musicbrainzngs.set_useragent(
|
|
||||||
__header__.__header__, __version__.__version__
|
|
||||||
)
|
|
||||||
resp_0 = musicbrainzngs.browse_release_groups(
|
|
||||||
# artist='1a425bbd-cca4-4b2c-aeb7-71cb176c828a',
|
|
||||||
artist='0383dadf-2a4e-4d10-a46a-e9e041da8eb3',
|
|
||||||
release_type=['album'],
|
|
||||||
limit=100,
|
|
||||||
)
|
|
||||||
# pprint(resp_0)
|
|
||||||
resp_0 = addict.Dict(resp_0)
|
|
||||||
|
|
||||||
release_group_ids = addict.Dict()
|
|
||||||
album_info = addict.Dict()
|
|
||||||
|
|
||||||
release_group_ids = addict.Dict(
|
|
||||||
(i.id, i.title)
|
|
||||||
for i in resp_0['release-group-list']
|
|
||||||
if i.type == 'Album'
|
|
||||||
)
|
|
||||||
|
|
||||||
all_albums = list()
|
|
||||||
|
|
||||||
pprint(release_group_ids)
|
|
||||||
|
|
||||||
pprint(len(release_group_ids))
|
|
||||||
|
|
||||||
for id, alb in release_group_ids.items():
|
|
||||||
# print(id, alb)
|
|
||||||
|
|
||||||
resp_1 = 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_1['release-list']
|
|
||||||
]
|
|
||||||
|
|
||||||
max_track_pos = album_track_count.index(max(album_track_count))
|
|
||||||
|
|
||||||
# print(max_track_pos)
|
|
||||||
|
|
||||||
# print(album_track_count)
|
|
||||||
|
|
||||||
album_tracks = resp_1['release-list'][max_track_pos]
|
|
||||||
|
|
||||||
album_year = resp_1['release-list'][max_track_pos].date.split('-')[0]
|
|
||||||
|
|
||||||
album_tracks = addict.Dict(
|
|
||||||
(
|
|
||||||
alb + f' [{album_year}]',
|
|
||||||
[
|
|
||||||
i.recording.title
|
|
||||||
for i in resp_1['release-list'][max_track_pos][
|
|
||||||
'medium-list'
|
|
||||||
][0]['track-list']
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# pprint(resp_1['release-list'][3])
|
|
||||||
# print(max_track_pos)
|
|
||||||
# pprint(album_tracks)
|
|
||||||
|
|
||||||
all_albums.append(album_tracks)
|
|
||||||
|
|
||||||
pprint(all_albums)
|
|
||||||
raise (SystemExit)
|
|
||||||
|
|
||||||
# pprint(album_info)
|
|
||||||
|
|
||||||
# resp_1 = addict.Dict(
|
|
||||||
# musicbrainzngs.browse_releases(
|
|
||||||
# release_group='1174aa3d-1c9e-4745-be8d-e21a61b1a22d',
|
|
||||||
# release_type=['album'],
|
|
||||||
# includes=['recordings'],
|
|
||||||
# limit=100,
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
|
|
||||||
# resp_1 = addict.Dict(resp_1)
|
|
||||||
|
|
||||||
# pprint(resp_1)
|
|
||||||
|
|
||||||
# print(resp_1['release-list'][0]['medium-list'][0]['track-count'])
|
|
||||||
|
|
||||||
# album_track_count = [
|
|
||||||
# i['medium-list'][0]['track-count'] for i in resp_1['release-list']
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# max_track_count = print(max(album_track_count))
|
|
||||||
|
|
||||||
# album = addict.Dict(())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
140
src/musicbrainzapi/api/lyrics/__init__.py
Normal file
140
src/musicbrainzapi/api/lyrics/__init__.py
Normal file
@@ -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
|
||||||
@@ -1,107 +1,49 @@
|
|||||||
from __future__ import annotations
|
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
|
from collections import Counter
|
||||||
import html
|
import html
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import string
|
import string
|
||||||
import math
|
from typing import Union, Dict
|
||||||
|
|
||||||
from beautifultable import BeautifulTable
|
|
||||||
import musicbrainzngs
|
|
||||||
import click
|
|
||||||
import addict
|
import addict
|
||||||
import requests
|
import click
|
||||||
|
import musicbrainzngs
|
||||||
import numpy as np
|
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
|
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):
|
class LyricsBuilder(LyricsConcreteBuilder):
|
||||||
"""docstring for LyricsBuilder"""
|
"""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
|
@property
|
||||||
def product(self) -> Lyrics:
|
def product(self) -> Lyrics:
|
||||||
@@ -152,25 +94,44 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
|
"""Reset the builder and create new product.
|
||||||
|
"""
|
||||||
self._product = Lyrics()
|
self._product = Lyrics()
|
||||||
|
|
||||||
def find_artists(self) -> None:
|
def find_artists(self) -> None:
|
||||||
|
"""Find artists from the musicbrainz api
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self.musicbrainz_artists = musicbrainzngs.search_artists(
|
self.musicbrainz_artists = musicbrainzngs.search_artists(
|
||||||
artist=self.artist, country=self.country
|
artist=self.artist, country=self.country
|
||||||
)
|
)
|
||||||
# pprint(self.musicbrainz_artists['artist-list'])
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def sort_artists(self) -> None:
|
def sort_artists(self) -> None:
|
||||||
|
"""Sort the artists from the Musicbrainzapi
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self._sort_names = dict(
|
self._sort_names = dict(
|
||||||
(i.get('id'), f'{i.get("sort-name")} | {i.get("disambiguation")}')
|
(i.get('id'), f'{i.get("name")} | {i.get("disambiguation")}')
|
||||||
if i.get('disambiguation') is not None
|
if i.get('disambiguation') is not None
|
||||||
else (i.get('id'), f'{i.get("sort-name")}')
|
else (i.get('id'), f'{i.get("name")}')
|
||||||
for i in self.musicbrainz_artists['artist-list']
|
for i in self.musicbrainz_artists['artist-list']
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_accuracy_scores(self) -> None:
|
def get_accuracy_scores(self) -> None:
|
||||||
|
"""Get accuracy scores from the Musicbrainzapi
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self._accuracy_scores = dict(
|
self._accuracy_scores = dict(
|
||||||
(i.get('id'), int(i.get('ext:score', '0')))
|
(i.get('id'), int(i.get('ext:score', '0')))
|
||||||
for i in self.musicbrainz_artists['artist-list']
|
for i in self.musicbrainz_artists['artist-list']
|
||||||
@@ -178,6 +139,12 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def get_top_five_results(self) -> None:
|
def get_top_five_results(self) -> None:
|
||||||
|
"""Get the top five artists from the Musicbrainzapi
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self._top_five_results = dict(
|
self._top_five_results = dict(
|
||||||
(i, self._accuracy_scores.get(i))
|
(i, self._accuracy_scores.get(i))
|
||||||
for i in sorted(
|
for i in sorted(
|
||||||
@@ -189,6 +156,12 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def find_all_albums(self) -> None:
|
def find_all_albums(self) -> None:
|
||||||
|
"""Find all albums for the chosen artist
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
limit, offset, page = (100, 0, 1)
|
limit, offset, page = (100, 0, 1)
|
||||||
|
|
||||||
resp_0 = addict.Dict(
|
resp_0 = addict.Dict(
|
||||||
@@ -245,6 +218,12 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def find_all_tracks(self) -> None:
|
def find_all_tracks(self) -> None:
|
||||||
|
"""Find all tracks from all albums.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self.all_albums = list()
|
self.all_albums = list()
|
||||||
total_albums = len(self.release_group_ids)
|
total_albums = len(self.release_group_ids)
|
||||||
self.total_track_count = 0
|
self.total_track_count = 0
|
||||||
@@ -309,6 +288,12 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def find_lyrics_urls(self) -> None:
|
def find_lyrics_urls(self) -> None:
|
||||||
|
"""Construct the URL for the lyrics api.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self.all_albums_lyrics_url = list()
|
self.all_albums_lyrics_url = list()
|
||||||
for x in self.all_albums:
|
for x in self.all_albums:
|
||||||
for alb, tracks in x.items():
|
for alb, tracks in x.items():
|
||||||
@@ -326,8 +311,13 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
# pprint(self.all_albums_lyrics_url)
|
# pprint(self.all_albums_lyrics_url)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# change this for progressbar for i loop
|
|
||||||
def find_all_lyrics(self) -> None:
|
def find_all_lyrics(self) -> None:
|
||||||
|
"""Get lyrics for each track from the lyrics api
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self.all_albums_lyrics = list()
|
self.all_albums_lyrics = list()
|
||||||
|
|
||||||
with click.progressbar(
|
with click.progressbar(
|
||||||
@@ -335,22 +325,28 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
label=f'Finding lyrics for {self.total_track_count}'
|
label=f'Finding lyrics for {self.total_track_count}'
|
||||||
f' tracks for {self.artist}. This may take some time! ☕️',
|
f' tracks for {self.artist}. This may take some time! ☕️',
|
||||||
) as bar:
|
) as bar:
|
||||||
|
bar.update(5)
|
||||||
for x in self.all_albums_lyrics_url:
|
for x in self.all_albums_lyrics_url:
|
||||||
for alb, urls in x.items():
|
for alb, urls in x.items():
|
||||||
bar.update(1)
|
# bar.update(1)
|
||||||
update = len(urls)
|
update = len(urls)
|
||||||
lyrics = addict.Dict(
|
lyrics = addict.Dict(
|
||||||
(alb, [self.request_lyrics_from_url(i) for i in urls])
|
(alb, [self.request_lyrics_from_url(i) for i in urls])
|
||||||
)
|
)
|
||||||
self.all_albums_lyrics.append(lyrics)
|
self.all_albums_lyrics.append(lyrics)
|
||||||
bar.update(update - 1)
|
bar.update(update)
|
||||||
|
|
||||||
with open(f'{os.getcwd()}/all_albums_lyrics.json', 'w') as f:
|
with open(f'{os.getcwd()}/all_albums_lyrics.json', 'w') as f:
|
||||||
json.dump(self.all_albums_lyrics, f, indent=2)
|
json.dump(self.all_albums_lyrics, f, indent=2)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def count_words_in_lyrics(self) -> None:
|
def count_words_in_lyrics(self) -> None:
|
||||||
# remove punctuation, fix click bar
|
"""Count all words in each track
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
self.all_albums_lyrics_count = list()
|
self.all_albums_lyrics_count = list()
|
||||||
# print(self.total_track_count)
|
# print(self.total_track_count)
|
||||||
with click.progressbar(
|
with click.progressbar(
|
||||||
@@ -376,12 +372,18 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
click.echo(f'Processed lyrics for {self.total_track_count} tracks.')
|
click.echo(f'Processed lyrics for {self.total_track_count} tracks.')
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# rename this
|
|
||||||
def calculate_average_all_albums(self) -> None:
|
def calculate_average_all_albums(self) -> None:
|
||||||
|
"""Summary
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
Description
|
||||||
|
"""
|
||||||
self.all_albums_lyrics_sum = list()
|
self.all_albums_lyrics_sum = list()
|
||||||
# album_lyrics = self.all_albums_lyrics_count
|
album_lyrics = self.all_albums_lyrics_count
|
||||||
with open(f'{os.getcwd()}/lyrics_count.json', 'r') as f:
|
# with open(f'{os.getcwd()}/lyrics_count.json', 'r') as f:
|
||||||
album_lyrics = json.load(f)
|
# album_lyrics = json.load(f)
|
||||||
count = 0
|
count = 0
|
||||||
for i in album_lyrics:
|
for i in album_lyrics:
|
||||||
count += len(i)
|
count += len(i)
|
||||||
@@ -406,15 +408,17 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
# print(d)
|
# print(d)
|
||||||
self.all_albums_lyrics_sum.append(d)
|
self.all_albums_lyrics_sum.append(d)
|
||||||
# print(count)
|
# print(count)
|
||||||
with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'w+') as f:
|
# with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'w+') as f:
|
||||||
json.dump(self.all_albums_lyrics_sum, f)
|
# json.dump(self.all_albums_lyrics_sum, f)
|
||||||
return self
|
# return self
|
||||||
|
|
||||||
def calculate_final_average_by_album(self) -> None:
|
def calculate_final_average_by_album(self) -> None:
|
||||||
|
"""Calculates descriptive statistics by album.
|
||||||
|
"""
|
||||||
self.album_statistics = addict.Dict()
|
self.album_statistics = addict.Dict()
|
||||||
# album_lyrics = self.all_albums_lyrics_sum
|
album_lyrics = self.all_albums_lyrics_sum
|
||||||
with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'r') as f:
|
# with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'r') as f:
|
||||||
album_lyrics = json.load(f)
|
# album_lyrics = json.load(f)
|
||||||
|
|
||||||
for i in album_lyrics:
|
for i in album_lyrics:
|
||||||
for album, count in i.items():
|
for album, count in i.items():
|
||||||
@@ -426,17 +430,18 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
self.album_statistics = addict.Dict(
|
self.album_statistics = addict.Dict(
|
||||||
**self.album_statistics, **addict.Dict((album, _d))
|
**self.album_statistics, **addict.Dict((album, _d))
|
||||||
)
|
)
|
||||||
with open(f'{os.getcwd()}/album_statistics.json', 'w') as f:
|
# with open(f'{os.getcwd()}/album_statistics.json', 'w') as f:
|
||||||
json.dump(self.album_statistics, f, indent=2)
|
# json.dump(self.album_statistics, f, indent=2)
|
||||||
# pprint(self.album_statistics)
|
# pprint(self.album_statistics)
|
||||||
|
|
||||||
# implement above in this
|
|
||||||
def calculate_final_average_by_year(self) -> None:
|
def calculate_final_average_by_year(self) -> None:
|
||||||
|
"""Calculates descriptive statistic by year.
|
||||||
|
"""
|
||||||
group_by_years = addict.Dict()
|
group_by_years = addict.Dict()
|
||||||
self.year_statistics = addict.Dict()
|
self.year_statistics = addict.Dict()
|
||||||
# album_lyrics = self.all_albums_lyrics_sum
|
album_lyrics = self.all_albums_lyrics_sum
|
||||||
with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'r') as f:
|
# with open(f'{os.getcwd()}/lyrics_sum_all_album.json', 'r') as f:
|
||||||
album_lyrics = json.load(f)
|
# album_lyrics = json.load(f)
|
||||||
|
|
||||||
# Merge years together
|
# Merge years together
|
||||||
for i in album_lyrics:
|
for i in album_lyrics:
|
||||||
@@ -464,12 +469,38 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def construct_lyrics_url(artist: str, song: str) -> str:
|
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_base = 'https://api.lyrics.ovh/v1'
|
||||||
lyrics_api_url = html.escape(f'{lyrics_api_base}/{artist}/{song}')
|
lyrics_api_url = html.escape(f'{lyrics_api_base}/{artist}/{song}')
|
||||||
return lyrics_api_url
|
return lyrics_api_url
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def request_lyrics_from_url(url: str) -> str:
|
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)
|
resp = requests.get(url)
|
||||||
|
|
||||||
# No lyrics for a song will return a key of 'error', we pass on this.
|
# No lyrics for a song will return a key of 'error', we pass on this.
|
||||||
@@ -481,11 +512,35 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def strip_punctuation(word: str) -> str:
|
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))
|
_strip = word.translate(str.maketrans('', '', string.punctuation))
|
||||||
return _strip
|
return _strip
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_descriptive_statistics(nums: list) -> Dict[str, int]:
|
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:
|
if len(nums) == 0:
|
||||||
return
|
return
|
||||||
avg = math.ceil(np.mean(nums))
|
avg = math.ceil(np.mean(nums))
|
||||||
@@ -511,232 +566,3 @@ class LyricsBuilder(LyricsConcreteBuilder):
|
|||||||
('count', count),
|
('count', count),
|
||||||
)
|
)
|
||||||
return _d
|
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]]
|
|
||||||
|
|
||||||
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 all'
|
|
||||||
' albums. 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():
|
|
||||||
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]),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
output_0.append_row([output_1])
|
|
||||||
click.echo(output_0)
|
|
||||||
return self
|
|
||||||
81
src/musicbrainzapi/api/lyrics/concrete_builder.py
Normal file
81
src/musicbrainzapi/api/lyrics/concrete_builder.py
Normal file
@@ -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
|
||||||
202
src/musicbrainzapi/api/lyrics/director.py
Normal file
202
src/musicbrainzapi/api/lyrics/director.py
Normal file
@@ -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,17 +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}')
|
||||||
if sys.version_info[0] == 2:
|
|
||||||
name = name.encode('ascii', 'replace')
|
|
||||||
mod = import_module(f'musicbrainzapi.cli.commands.cmd_{name}')
|
|
||||||
# mod = __import__(
|
|
||||||
# 'complex.commands.cmd_' + name, None, None, ['cli']
|
|
||||||
# )
|
|
||||||
except ImportError as e:
|
|
||||||
print(e)
|
|
||||||
return
|
|
||||||
return mod.cli
|
return mod.cli
|
||||||
|
|
||||||
|
|
||||||
@@ -50,25 +48,21 @@ class ComplexCLI(click.MultiCommand):
|
|||||||
'-p',
|
'-p',
|
||||||
'--path',
|
'--path',
|
||||||
type=click.Path(
|
type=click.Path(
|
||||||
exists=False, file_okay=False, resolve_path=True, writable=True
|
exists=True, file_okay=False, resolve_path=True, writable=True
|
||||||
),
|
),
|
||||||
help='Path to save results.',
|
help='Path to save results.',
|
||||||
default=os.path.expanduser('~/.musicbrainzapi')
|
default=os.getcwd()
|
||||||
)
|
)
|
||||||
@click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode.')
|
# @click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode.')
|
||||||
@click.version_option(
|
@click.version_option(
|
||||||
version=__version__,
|
version=__version__,
|
||||||
prog_name=__header__,
|
prog_name=__header__,
|
||||||
message=f'{__header__} version {__version__} 🎤',
|
message=f'{__header__} version {__version__} 🎤',
|
||||||
)
|
)
|
||||||
@pass_environment
|
@pass_environment
|
||||||
def cli(ctx, verbose, path):
|
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,23 +1,32 @@
|
|||||||
|
import json
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
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
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'--wordcloud',
|
||||||
|
required=False,
|
||||||
|
help='Generate a wordcloud from lyrics.',
|
||||||
|
is_flag=True,
|
||||||
|
default=False,
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--show-summary',
|
'--show-summary',
|
||||||
@@ -51,12 +60,30 @@ def cli(
|
|||||||
country: Union[str, None],
|
country: Union[str, None],
|
||||||
dev: bool,
|
dev: bool,
|
||||||
show_summary: str,
|
show_summary: str,
|
||||||
save_output: bool
|
wordcloud: bool,
|
||||||
|
save_output: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Search for lyrics statistics of an Artist/Group."""
|
"""Search for lyrics statistics of an Artist/Group.
|
||||||
# lyrics_obj = list()
|
|
||||||
director = lyrics.LyricsClickDirector()
|
Parameters
|
||||||
builder = lyrics.LyricsBuilder()
|
----------
|
||||||
|
ctx : musicbrainzapi.cli.cli.Environment
|
||||||
|
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()
|
||||||
@@ -77,9 +104,37 @@ def cli(
|
|||||||
|
|
||||||
# Show basic count
|
# Show basic count
|
||||||
lyrics_0.show_summary()
|
lyrics_0.show_summary()
|
||||||
|
|
||||||
# Show summary statistics
|
# Show summary statistics
|
||||||
if show_summary == 'all':
|
if show_summary == 'all':
|
||||||
lyrics_0.show_summary_statistics(group_by='album')
|
lyrics_0.show_summary_statistics(group_by='album')
|
||||||
lyrics_0.show_summary_statistics(group_by='year')
|
lyrics_0.show_summary_statistics(group_by='year')
|
||||||
elif show_summary in ['album', 'year']:
|
elif show_summary in ['album', 'year']:
|
||||||
lyrics_0.show_summary_statistics(group_by=show_summary)
|
lyrics_0.show_summary_statistics(group_by=show_summary)
|
||||||
|
|
||||||
|
# Show wordcloud
|
||||||
|
if wordcloud:
|
||||||
|
click.echo('Generating wordcloud')
|
||||||
|
cloud = musicbrainzapi.wordcloud.LyricsWordcloud.use_microphone(
|
||||||
|
lyrics_0.all_albums_lyrics_count
|
||||||
|
)
|
||||||
|
cloud.create_word_cloud()
|
||||||
|
show = click.confirm(
|
||||||
|
'Wordcloud ready - press enter to show.', default=True
|
||||||
|
)
|
||||||
|
plt.imshow(
|
||||||
|
cloud.wc.recolor(
|
||||||
|
color_func=cloud.generate_grey_colours, random_state=3
|
||||||
|
),
|
||||||
|
interpolation='bilinear',
|
||||||
|
)
|
||||||
|
plt.axis('off')
|
||||||
|
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
src/musicbrainzapi/wordcloud/.DS_Store
vendored
Normal file
BIN
src/musicbrainzapi/wordcloud/.DS_Store
vendored
Normal file
Binary file not shown.
172
src/musicbrainzapi/wordcloud/__init__.py
Normal file
172
src/musicbrainzapi/wordcloud/__init__.py
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
import collections
|
||||||
|
from importlib import resources
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from matplotlib import pyplot as plt
|
||||||
|
from PIL import Image
|
||||||
|
from wordcloud import STOPWORDS, WordCloud
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from musicbrainzapi.api.lyrics import Lyrics
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
import PIL.PngImagePlugin.PngImageFile
|
||||||
|
|
||||||
|
|
||||||
|
class LyricsWordcloud:
|
||||||
|
"""Create a word cloud from Lyrics.
|
||||||
|
|
||||||
|
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__(
|
||||||
|
self,
|
||||||
|
pillow_img: 'PIL.PngImagePlugin.PngImageFile',
|
||||||
|
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.all_albums_lyrics_count = all_albums_lyrics_count
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def use_microphone(
|
||||||
|
cls, all_albums_lyrics_count: 'Lyrics.all_albums_lyrics_count',
|
||||||
|
) -> 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(
|
||||||
|
'musicbrainzapi.wordcloud.resources', 'mic.png'
|
||||||
|
)
|
||||||
|
with mic_resource as m:
|
||||||
|
mic_img = Image.open(m)
|
||||||
|
|
||||||
|
return cls(mic_img, all_albums_lyrics_count)
|
||||||
|
|
||||||
|
def _get_lyrics_list(self) -> None:
|
||||||
|
"""Gets all words from lyrics in a single list + cleans them.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.lyrics_list = list()
|
||||||
|
for i in self.all_albums_lyrics_count:
|
||||||
|
for album, lyric in i.items():
|
||||||
|
for track in lyric:
|
||||||
|
try:
|
||||||
|
for word in track:
|
||||||
|
for _ in range(1, word[1]):
|
||||||
|
cleaned = word[0]
|
||||||
|
cleaned = re.sub(
|
||||||
|
r'[\(\[].*?[\)\]]', ' ', cleaned
|
||||||
|
)
|
||||||
|
cleaned = re.sub(
|
||||||
|
r'[^a-zA-Z0-9\s]', '', cleaned
|
||||||
|
)
|
||||||
|
cleaned = cleaned.lower()
|
||||||
|
if cleaned in STOPWORDS:
|
||||||
|
continue
|
||||||
|
self.lyrics_list.append(cleaned)
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _get_frequencies(self) -> None:
|
||||||
|
"""Get frequencies of words from a list.
|
||||||
|
"""
|
||||||
|
self.freq = collections.Counter(self.lyrics_list)
|
||||||
|
|
||||||
|
def _get_char_mask(self) -> None:
|
||||||
|
"""Gets a numpy array for the image file.
|
||||||
|
"""
|
||||||
|
self.char_mask = np.array(self.pillow_img)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_grey_colours(
|
||||||
|
word: str,
|
||||||
|
font_size: str,
|
||||||
|
random_state: typing.Union[None, bool] = None,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
) -> str:
|
||||||
|
colour = f'hsl(0, 0%, {random.randint(60, 100)}%)'
|
||||||
|
return colour
|
||||||
|
|
||||||
|
def _generate_word_cloud(self) -> None:
|
||||||
|
"""Generates a word cloud
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.wc = WordCloud(
|
||||||
|
max_words=150,
|
||||||
|
width=1500,
|
||||||
|
height=1500,
|
||||||
|
mask=self.char_mask,
|
||||||
|
random_state=1,
|
||||||
|
).generate_from_frequencies(self.freq)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _generate_plot(self) -> None:
|
||||||
|
"""Plots the wordcloud and sets matplotlib options.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
plt.imshow(
|
||||||
|
self.wc.recolor(
|
||||||
|
color_func=self.generate_grey_colours, random_state=3
|
||||||
|
),
|
||||||
|
interpolation='bilinear',
|
||||||
|
)
|
||||||
|
plt.axis('off')
|
||||||
|
return self
|
||||||
|
|
||||||
|
def show_word_cloud(self):
|
||||||
|
"""Shows the word cloud.
|
||||||
|
"""
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
def create_word_cloud(self) -> None:
|
||||||
|
"""Creates a word cloud
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self._get_lyrics_list()
|
||||||
|
self._get_frequencies()
|
||||||
|
self._get_char_mask()
|
||||||
|
self._generate_word_cloud()
|
||||||
|
self._generate_plot()
|
||||||
|
return self
|
||||||
BIN
src/musicbrainzapi/wordcloud/resources/.DS_Store
vendored
Normal file
BIN
src/musicbrainzapi/wordcloud/resources/.DS_Store
vendored
Normal file
Binary file not shown.
0
src/musicbrainzapi/wordcloud/resources/__init__.py
Normal file
0
src/musicbrainzapi/wordcloud/resources/__init__.py
Normal file
BIN
src/musicbrainzapi/wordcloud/resources/mic.png
Normal file
BIN
src/musicbrainzapi/wordcloud/resources/mic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 220 KiB |
Reference in New Issue
Block a user