mirror of
https://github.com/nathom/streamrip.git
synced 2025-05-13 14:44:49 -04:00
Begin move to cleo
This commit is contained in:
parent
54f4ab99af
commit
9970ac548f
13 changed files with 528 additions and 150 deletions
|
@ -22,6 +22,9 @@ ignore_missing_imports = True
|
||||||
[mypy-tomlkit.*]
|
[mypy-tomlkit.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-Crypto.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
[mypy-Cryptodome.*]
|
[mypy-Cryptodome.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
@ -30,3 +33,9 @@ ignore_missing_imports = True
|
||||||
|
|
||||||
[mypy-PIL.*]
|
[mypy-PIL.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-cleo.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-deezer.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
185
poetry.lock
generated
185
poetry.lock
generated
|
@ -6,6 +6,14 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "appdirs"
|
||||||
|
version = "1.4.4"
|
||||||
|
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autodoc"
|
name = "autodoc"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -44,6 +52,28 @@ soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""}
|
||||||
html5lib = ["html5lib"]
|
html5lib = ["html5lib"]
|
||||||
lxml = ["lxml"]
|
lxml = ["lxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "black"
|
||||||
|
version = "21.7b0"
|
||||||
|
description = "The uncompromising code formatter."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
appdirs = "*"
|
||||||
|
click = ">=7.1.2"
|
||||||
|
mypy-extensions = ">=0.4.3"
|
||||||
|
pathspec = ">=0.8.1,<1"
|
||||||
|
regex = ">=2020.1.8"
|
||||||
|
tomli = ">=0.2.6,<2.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
colorama = ["colorama (>=0.4.3)"]
|
||||||
|
d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
|
||||||
|
python2 = ["typed-ast (>=1.4.2)"]
|
||||||
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2021.5.30"
|
version = "2021.5.30"
|
||||||
|
@ -63,6 +93,18 @@ python-versions = ">=3.5.0"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
unicode_backport = ["unicodedata2"]
|
unicode_backport = ["unicodedata2"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cleo"
|
||||||
|
version = "1.0.0a3"
|
||||||
|
description = "Cleo allows you to create beautiful and testable command-line interfaces."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6,<4.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
crashtest = ">=0.3.1,<0.4.0"
|
||||||
|
pylev = ">=1.3.0,<2.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.0.1"
|
version = "8.0.1"
|
||||||
|
@ -82,6 +124,14 @@ category = "main"
|
||||||
optional = false
|
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.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crashtest"
|
||||||
|
version = "0.3.1"
|
||||||
|
description = "Manage Python errors with ease"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6,<4.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "decorator"
|
name = "decorator"
|
||||||
version = "5.0.9"
|
version = "5.0.9"
|
||||||
|
@ -125,6 +175,20 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "isort"
|
||||||
|
version = "5.9.3"
|
||||||
|
description = "A Python utility / library to sort Python imports."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1,<4.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
||||||
|
requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
||||||
|
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||||
|
plugins = ["setuptools"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
|
@ -155,6 +219,14 @@ category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5, <4"
|
python-versions = ">=3.5, <4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "0.4.3"
|
||||||
|
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "21.0"
|
version = "21.0"
|
||||||
|
@ -166,6 +238,14 @@ python-versions = ">=3.6"
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pyparsing = ">=2.0.2"
|
pyparsing = ">=2.0.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathspec"
|
||||||
|
version = "0.9.0"
|
||||||
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathvalidate"
|
name = "pathvalidate"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
|
@ -212,6 +292,14 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pylev"
|
||||||
|
version = "1.4.0"
|
||||||
|
description = "A pure Python Levenshtein implementation that's not freaking GPL'd."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
version = "2.4.7"
|
version = "2.4.7"
|
||||||
|
@ -228,6 +316,14 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "2021.7.6"
|
||||||
|
description = "Alternative regular expression module, to replace re."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.26.0"
|
version = "2.26.0"
|
||||||
|
@ -380,6 +476,14 @@ python-versions = ">=3.5"
|
||||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
test = ["pytest"]
|
test = ["pytest"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "A lil' TOML parser"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomlkit"
|
name = "tomlkit"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -486,13 +590,17 @@ python-versions = "*"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
content-hash = "8128cd9440b4931b23509f72a3952e1d4115a3783ded70c3b458de921ed30c56"
|
content-hash = "3e7ca36060a8049300d1408d6b7595c1fdf27cd5b4ed9cfab68af94d083d7935"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
alabaster = [
|
alabaster = [
|
||||||
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
|
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
|
||||||
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
|
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
|
||||||
]
|
]
|
||||||
|
appdirs = [
|
||||||
|
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
|
||||||
|
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
||||||
|
]
|
||||||
autodoc = [
|
autodoc = [
|
||||||
{file = "autodoc-0.5.0.tar.gz", hash = "sha256:c4387c5a0f1c09b055bb2e384542ee1e016542f313b2a33d904ca77f0460ded3"},
|
{file = "autodoc-0.5.0.tar.gz", hash = "sha256:c4387c5a0f1c09b055bb2e384542ee1e016542f313b2a33d904ca77f0460ded3"},
|
||||||
]
|
]
|
||||||
|
@ -505,6 +613,10 @@ beautifulsoup4 = [
|
||||||
{file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"},
|
{file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"},
|
||||||
{file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"},
|
{file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"},
|
||||||
]
|
]
|
||||||
|
black = [
|
||||||
|
{file = "black-21.7b0-py3-none-any.whl", hash = "sha256:1c7aa6ada8ee864db745b22790a32f94b2795c253a75d6d9b5e439ff10d23116"},
|
||||||
|
{file = "black-21.7b0.tar.gz", hash = "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219"},
|
||||||
|
]
|
||||||
certifi = [
|
certifi = [
|
||||||
{file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
|
{file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
|
||||||
{file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
|
{file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
|
||||||
|
@ -513,6 +625,10 @@ charset-normalizer = [
|
||||||
{file = "charset-normalizer-2.0.3.tar.gz", hash = "sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"},
|
{file = "charset-normalizer-2.0.3.tar.gz", hash = "sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"},
|
||||||
{file = "charset_normalizer-2.0.3-py3-none-any.whl", hash = "sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1"},
|
{file = "charset_normalizer-2.0.3-py3-none-any.whl", hash = "sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1"},
|
||||||
]
|
]
|
||||||
|
cleo = [
|
||||||
|
{file = "cleo-1.0.0a3-py3-none-any.whl", hash = "sha256:46b2f970d06caa311d1e12a1013b0ce2a8149502669ac82cbedafb9e0bfdbccd"},
|
||||||
|
{file = "cleo-1.0.0a3.tar.gz", hash = "sha256:9c1c8dd06635c936f45e4649aa2f7581517b4d52c7a9414d1b42586e63c2fe5d"},
|
||||||
|
]
|
||||||
click = [
|
click = [
|
||||||
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
|
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
|
||||||
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
|
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
|
||||||
|
@ -521,6 +637,10 @@ colorama = [
|
||||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||||
]
|
]
|
||||||
|
crashtest = [
|
||||||
|
{file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"},
|
||||||
|
{file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"},
|
||||||
|
]
|
||||||
decorator = [
|
decorator = [
|
||||||
{file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"},
|
{file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"},
|
||||||
{file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"},
|
{file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"},
|
||||||
|
@ -541,6 +661,10 @@ imagesize = [
|
||||||
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
|
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
|
||||||
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
|
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
|
||||||
]
|
]
|
||||||
|
isort = [
|
||||||
|
{file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"},
|
||||||
|
{file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"},
|
||||||
|
]
|
||||||
jinja2 = [
|
jinja2 = [
|
||||||
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
|
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
|
||||||
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
|
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
|
||||||
|
@ -585,10 +709,18 @@ mutagen = [
|
||||||
{file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"},
|
{file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"},
|
||||||
{file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"},
|
{file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"},
|
||||||
]
|
]
|
||||||
|
mypy-extensions = [
|
||||||
|
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||||
|
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||||
|
]
|
||||||
packaging = [
|
packaging = [
|
||||||
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
|
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
|
||||||
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
|
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
|
||||||
]
|
]
|
||||||
|
pathspec = [
|
||||||
|
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||||
|
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||||
|
]
|
||||||
pathvalidate = [
|
pathvalidate = [
|
||||||
{file = "pathvalidate-2.4.1-py3-none-any.whl", hash = "sha256:f5dde7efeeb4262784c5e1331e02752d07c1ec3ee5ea42683fe211155652b808"},
|
{file = "pathvalidate-2.4.1-py3-none-any.whl", hash = "sha256:f5dde7efeeb4262784c5e1331e02752d07c1ec3ee5ea42683fe211155652b808"},
|
||||||
{file = "pathvalidate-2.4.1.tar.gz", hash = "sha256:3c9bd94c7ec23e9cfb211ffbe356ae75f979d6c099a2c745ee9490f524f32468"},
|
{file = "pathvalidate-2.4.1.tar.gz", hash = "sha256:3c9bd94c7ec23e9cfb211ffbe356ae75f979d6c099a2c745ee9490f524f32468"},
|
||||||
|
@ -674,6 +806,10 @@ pygments = [
|
||||||
{file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
|
{file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
|
||||||
{file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"},
|
{file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"},
|
||||||
]
|
]
|
||||||
|
pylev = [
|
||||||
|
{file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"},
|
||||||
|
{file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"},
|
||||||
|
]
|
||||||
pyparsing = [
|
pyparsing = [
|
||||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||||
|
@ -682,6 +818,49 @@ pytz = [
|
||||||
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
|
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
|
||||||
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
|
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
|
||||||
]
|
]
|
||||||
|
regex = [
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-win32.whl", hash = "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b"},
|
||||||
|
{file = "regex-2021.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-win32.whl", hash = "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5"},
|
||||||
|
{file = "regex-2021.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-win32.whl", hash = "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0"},
|
||||||
|
{file = "regex-2021.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-win32.whl", hash = "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035"},
|
||||||
|
{file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"},
|
||||||
|
{file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"},
|
||||||
|
]
|
||||||
requests = [
|
requests = [
|
||||||
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
|
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
|
||||||
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
|
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
|
||||||
|
@ -730,6 +909,10 @@ sphinxcontrib-serializinghtml = [
|
||||||
{file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
|
{file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
|
||||||
{file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
|
{file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
|
||||||
]
|
]
|
||||||
|
tomli = [
|
||||||
|
{file = "tomli-1.1.0-py3-none-any.whl", hash = "sha256:f4a182048010e89cbec0ae4686b21f550a7f2903f665e34a6de58ec15424f919"},
|
||||||
|
{file = "tomli-1.1.0.tar.gz", hash = "sha256:33d7984738f8bb699c9b0a816eb646a8178a69eaa792d258486776a5d21b8ca5"},
|
||||||
|
]
|
||||||
tomlkit = [
|
tomlkit = [
|
||||||
{file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"},
|
{file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"},
|
||||||
{file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"},
|
{file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"},
|
||||||
|
|
|
@ -19,7 +19,7 @@ classifiers = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
rip = "rip.cli:main"
|
rip = "rip.cli_cleo:main"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
|
@ -35,6 +35,8 @@ windows-curses = {version = "^2.2.0", platform = 'win32 or cygwin'}
|
||||||
Pillow = "^8.3.0"
|
Pillow = "^8.3.0"
|
||||||
deezer-py = "^1.0.4"
|
deezer-py = "^1.0.4"
|
||||||
pycryptodomex = "^3.10.1"
|
pycryptodomex = "^3.10.1"
|
||||||
|
cleo = {version = "1.0.0a3", allow-prereleases = true}
|
||||||
|
appdirs = "^1.4.4"
|
||||||
|
|
||||||
[tool.poetry.urls]
|
[tool.poetry.urls]
|
||||||
"Bug Reports" = "https://github.com/nathom/streamrip/issues"
|
"Bug Reports" = "https://github.com/nathom/streamrip/issues"
|
||||||
|
@ -44,6 +46,8 @@ Sphinx = "^4.1.1"
|
||||||
autodoc = "^0.5.0"
|
autodoc = "^0.5.0"
|
||||||
types-click = "^7.1.2"
|
types-click = "^7.1.2"
|
||||||
types-Pillow = "^8.3.1"
|
types-Pillow = "^8.3.1"
|
||||||
|
black = "^21.7b0"
|
||||||
|
isort = "^5.9.3"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
|
75
rip/cli.py
75
rip/cli.py
|
@ -9,15 +9,31 @@ logging.basicConfig(level="WARNING")
|
||||||
logger = logging.getLogger("streamrip")
|
logger = logging.getLogger("streamrip")
|
||||||
|
|
||||||
|
|
||||||
@click.group(invoke_without_command=True)
|
class SkipArg(click.Group):
|
||||||
|
def parse_args(self, ctx, args):
|
||||||
|
if len(args) == 0:
|
||||||
|
click.echo(self.get_help(ctx))
|
||||||
|
exit()
|
||||||
|
|
||||||
|
if args[0] in self.commands:
|
||||||
|
print('command found')
|
||||||
|
args.insert(0, "")
|
||||||
|
# if args[0] in self.commands:
|
||||||
|
# if len(args) == 1 or args[1] not in self.commands:
|
||||||
|
# # This condition needs updating for multiple positional arguments
|
||||||
|
# args.insert(0, "")
|
||||||
|
super(SkipArg, self).parse_args(ctx, args)
|
||||||
|
|
||||||
|
|
||||||
|
# @click.option(
|
||||||
|
# "-u",
|
||||||
|
# "--urls",
|
||||||
|
# metavar="URLS",
|
||||||
|
# help="Url from Qobuz, Tidal, SoundCloud, or Deezer",
|
||||||
|
# multiple=True,
|
||||||
|
# )
|
||||||
|
@click.group(cls=SkipArg, invoke_without_command=True)
|
||||||
@click.option("-c", "--convert", metavar="CODEC", help="alac, mp3, flac, or ogg")
|
@click.option("-c", "--convert", metavar="CODEC", help="alac, mp3, flac, or ogg")
|
||||||
@click.option(
|
|
||||||
"-u",
|
|
||||||
"--urls",
|
|
||||||
metavar="URLS",
|
|
||||||
help="Url from Qobuz, Tidal, SoundCloud, or Deezer",
|
|
||||||
multiple=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
@click.option(
|
||||||
"-q",
|
"-q",
|
||||||
"--quality",
|
"--quality",
|
||||||
|
@ -27,6 +43,7 @@ logger = logging.getLogger("streamrip")
|
||||||
@click.option("-t", "--text", metavar="PATH", help="Download urls from a text file.")
|
@click.option("-t", "--text", metavar="PATH", help="Download urls from a text file.")
|
||||||
@click.option("-nd", "--no-db", is_flag=True, help="Ignore the database.")
|
@click.option("-nd", "--no-db", is_flag=True, help="Ignore the database.")
|
||||||
@click.option("--debug", is_flag=True, help="Show debugging logs.")
|
@click.option("--debug", is_flag=True, help="Show debugging logs.")
|
||||||
|
@click.argument("URLS", nargs=1)
|
||||||
@click.version_option(prog_name="rip", version=__version__)
|
@click.version_option(prog_name="rip", version=__version__)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx, **kwargs):
|
def cli(ctx, **kwargs):
|
||||||
|
@ -54,9 +71,7 @@ def cli(ctx, **kwargs):
|
||||||
from .constants import CONFIG_DIR
|
from .constants import CONFIG_DIR
|
||||||
from .core import RipCore
|
from .core import RipCore
|
||||||
|
|
||||||
logging.basicConfig(level="WARNING")
|
print(kwargs)
|
||||||
logger = logging.getLogger("streamrip")
|
|
||||||
|
|
||||||
if not os.path.isdir(CONFIG_DIR):
|
if not os.path.isdir(CONFIG_DIR):
|
||||||
os.makedirs(CONFIG_DIR, exist_ok=True)
|
os.makedirs(CONFIG_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
@ -68,7 +83,8 @@ def cli(ctx, **kwargs):
|
||||||
logger.debug("Starting debug log")
|
logger.debug("Starting debug log")
|
||||||
|
|
||||||
if ctx.invoked_subcommand is None and not ctx.params["urls"]:
|
if ctx.invoked_subcommand is None and not ctx.params["urls"]:
|
||||||
echo(cli.get_help(ctx))
|
print(dir(cli))
|
||||||
|
click.echo(cli.get_help(ctx))
|
||||||
|
|
||||||
if ctx.invoked_subcommand not in {
|
if ctx.invoked_subcommand not in {
|
||||||
None,
|
None,
|
||||||
|
@ -90,13 +106,13 @@ def cli(ctx, **kwargs):
|
||||||
r = requests.get("https://pypi.org/pypi/streamrip/json").json()
|
r = requests.get("https://pypi.org/pypi/streamrip/json").json()
|
||||||
newest = r["info"]["version"]
|
newest = r["info"]["version"]
|
||||||
if __version__ != newest:
|
if __version__ != newest:
|
||||||
secho(
|
click.secho(
|
||||||
"A new version of streamrip is available! "
|
"A new version of streamrip is available! "
|
||||||
"Run `pip3 install streamrip --upgrade` to update.",
|
"Run `pip3 install streamrip --upgrade` to update.",
|
||||||
fg="yellow",
|
fg="yellow",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
secho("streamrip is up-to-date!", fg="green")
|
click.secho("streamrip is up-to-date!", fg="green")
|
||||||
|
|
||||||
if kwargs["no_db"]:
|
if kwargs["no_db"]:
|
||||||
config.session["database"]["enabled"] = False
|
config.session["database"]["enabled"] = False
|
||||||
|
@ -108,7 +124,7 @@ def cli(ctx, **kwargs):
|
||||||
if kwargs["quality"] is not None:
|
if kwargs["quality"] is not None:
|
||||||
quality = int(kwargs["quality"])
|
quality = int(kwargs["quality"])
|
||||||
if quality not in range(5):
|
if quality not in range(5):
|
||||||
secho("Invalid quality", fg="red")
|
click.secho("Invalid quality", fg="red")
|
||||||
return
|
return
|
||||||
|
|
||||||
config.session["qobuz"]["quality"] = quality
|
config.session["qobuz"]["quality"] = quality
|
||||||
|
@ -126,7 +142,7 @@ def cli(ctx, **kwargs):
|
||||||
logger.debug(f"Handling {kwargs['text']}")
|
logger.debug(f"Handling {kwargs['text']}")
|
||||||
core.handle_txt(kwargs["text"])
|
core.handle_txt(kwargs["text"])
|
||||||
else:
|
else:
|
||||||
secho(f"Text file {kwargs['text']} does not exist.")
|
click.secho(f"Text file {kwargs['text']} does not exist.")
|
||||||
|
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
core.download()
|
core.download()
|
||||||
|
@ -150,6 +166,7 @@ def filter_discography(ctx, **kwargs):
|
||||||
|
|
||||||
For basic filtering, use the `--repeats` and `--features` filters.
|
For basic filtering, use the `--repeats` and `--features` filters.
|
||||||
"""
|
"""
|
||||||
|
raise Exception
|
||||||
filters = kwargs.copy()
|
filters = kwargs.copy()
|
||||||
filters.pop("urls")
|
filters.pop("urls")
|
||||||
config.session["filters"] = filters
|
config.session["filters"] = filters
|
||||||
|
@ -206,7 +223,7 @@ def search(ctx, **kwargs):
|
||||||
if core.interactive_search(query, kwargs["source"], kwargs["type"]):
|
if core.interactive_search(query, kwargs["source"], kwargs["type"]):
|
||||||
core.download()
|
core.download()
|
||||||
else:
|
else:
|
||||||
secho("No items chosen, exiting.", fg="bright_red")
|
click.secho("No items chosen, exiting.", fg="bright_red")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@ -333,10 +350,10 @@ def config(ctx, **kwargs):
|
||||||
config.update()
|
config.update()
|
||||||
|
|
||||||
if kwargs["path"]:
|
if kwargs["path"]:
|
||||||
echo(CONFIG_PATH)
|
click.echo(CONFIG_PATH)
|
||||||
|
|
||||||
if kwargs["open"]:
|
if kwargs["open"]:
|
||||||
secho(f"Opening {CONFIG_PATH}", fg="green")
|
click.secho(f"Opening {CONFIG_PATH}", fg="green")
|
||||||
click.launch(CONFIG_PATH)
|
click.launch(CONFIG_PATH)
|
||||||
|
|
||||||
if kwargs["open_vim"]:
|
if kwargs["open_vim"]:
|
||||||
|
@ -347,41 +364,41 @@ def config(ctx, **kwargs):
|
||||||
|
|
||||||
if kwargs["directory"]:
|
if kwargs["directory"]:
|
||||||
config_dir = os.path.dirname(CONFIG_PATH)
|
config_dir = os.path.dirname(CONFIG_PATH)
|
||||||
secho(f"Opening {config_dir}", fg="green")
|
click.secho(f"Opening {config_dir}", fg="green")
|
||||||
click.launch(config_dir)
|
click.launch(config_dir)
|
||||||
|
|
||||||
if kwargs["qobuz"]:
|
if kwargs["qobuz"]:
|
||||||
config.file["qobuz"]["email"] = input(style("Qobuz email: ", fg="blue"))
|
config.file["qobuz"]["email"] = input(click.style("Qobuz email: ", fg="blue"))
|
||||||
|
|
||||||
secho("Qobuz password (will not show on screen):", fg="blue")
|
click.secho("Qobuz password (will not show on screen):", fg="blue")
|
||||||
config.file["qobuz"]["password"] = md5(
|
config.file["qobuz"]["password"] = md5(
|
||||||
getpass(prompt="").encode("utf-8")
|
getpass(prompt="").encode("utf-8")
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
|
|
||||||
config.save()
|
config.save()
|
||||||
secho("Qobuz credentials hashed and saved to config.", fg="green")
|
click.secho("Qobuz credentials hashed and saved to config.", fg="green")
|
||||||
|
|
||||||
if kwargs["tidal"]:
|
if kwargs["tidal"]:
|
||||||
client = TidalClient()
|
client = TidalClient()
|
||||||
client.login()
|
client.login()
|
||||||
config.file["tidal"].update(client.get_tokens())
|
config.file["tidal"].update(client.get_tokens())
|
||||||
config.save()
|
config.save()
|
||||||
secho("Credentials saved to config.", fg="green")
|
click.secho("Credentials saved to config.", fg="green")
|
||||||
|
|
||||||
if kwargs["deezer"]:
|
if kwargs["deezer"]:
|
||||||
secho(
|
click.secho(
|
||||||
"If you're not sure how to find the ARL cookie, see the instructions at ",
|
"If you're not sure how to find the ARL cookie, see the instructions at ",
|
||||||
italic=True,
|
italic=True,
|
||||||
nl=False,
|
nl=False,
|
||||||
dim=True,
|
dim=True,
|
||||||
)
|
)
|
||||||
secho(
|
click.secho(
|
||||||
"https://github.com/nathom/streamrip/wiki/Finding-your-Deezer-ARL-Cookie",
|
"https://github.com/nathom/streamrip/wiki/Finding-your-Deezer-ARL-Cookie",
|
||||||
underline=True,
|
underline=True,
|
||||||
italic=True,
|
italic=True,
|
||||||
fg="blue",
|
fg="blue",
|
||||||
)
|
)
|
||||||
config.file["deezer"]["arl"] = input(style("ARL: ", fg="green"))
|
config.file["deezer"]["arl"] = input(click.style("ARL: ", fg="green"))
|
||||||
config.save()
|
config.save()
|
||||||
|
|
||||||
|
|
||||||
|
@ -481,7 +498,7 @@ def convert(ctx, **kwargs):
|
||||||
elif os.path.isfile(kwargs["path"]):
|
elif os.path.isfile(kwargs["path"]):
|
||||||
codec_map[codec](filename=kwargs["path"], **converter_args).convert()
|
codec_map[codec](filename=kwargs["path"], **converter_args).convert()
|
||||||
else:
|
else:
|
||||||
secho(f"File {kwargs['path']} does not exist.", fg="red")
|
click.secho(f"File {kwargs['path']} does not exist.", fg="red")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@ -503,7 +520,7 @@ def repair(ctx, **kwargs):
|
||||||
|
|
||||||
def none_chosen():
|
def none_chosen():
|
||||||
"""Print message if nothing was chosen."""
|
"""Print message if nothing was chosen."""
|
||||||
secho("No items chosen, exiting.", fg="bright_red")
|
click.secho("No items chosen, exiting.", fg="bright_red")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
214
rip/cli_cleo.py
Normal file
214
rip/cli_cleo.py
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from cleo.application import Application as BaseApplication
|
||||||
|
from cleo.commands.command import Command
|
||||||
|
|
||||||
|
from streamrip import __version__
|
||||||
|
|
||||||
|
from .config import Config
|
||||||
|
from .core import RipCore
|
||||||
|
|
||||||
|
logging.basicConfig(level="WARNING")
|
||||||
|
logger = logging.getLogger("streamrip")
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadCommand(Command):
|
||||||
|
"""
|
||||||
|
Download items from a url
|
||||||
|
|
||||||
|
url
|
||||||
|
{--f|file=None : Path to a text file containing urls}
|
||||||
|
{urls?* : One or more Qobuz, Tidal, Deezer, or SoundCloud urls}
|
||||||
|
"""
|
||||||
|
|
||||||
|
help = (
|
||||||
|
'\nDownload "Dreams" by Fleetwood Mac:\n'
|
||||||
|
"$ <fg=magenta>rip url https://www.deezer.com/en/track/63480987</>\n\n"
|
||||||
|
"Batch download urls from a text file named urls.txt:\n"
|
||||||
|
"$ <fg=magenta>rip --file urls.txt</>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
config = Config()
|
||||||
|
core = RipCore(config)
|
||||||
|
|
||||||
|
urls = self.argument("urls")
|
||||||
|
path = self.option("file")
|
||||||
|
if path != "None":
|
||||||
|
if os.path.isfile(path):
|
||||||
|
core.handle_txt(path)
|
||||||
|
else:
|
||||||
|
self.line(
|
||||||
|
f"<error>File <comment>{path}</comment> does not exist.</error>"
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if urls:
|
||||||
|
core.handle_urls(";".join(urls))
|
||||||
|
|
||||||
|
if len(core) > 0:
|
||||||
|
core.download()
|
||||||
|
elif not urls and path == "None":
|
||||||
|
self.line("<error>Must pass arguments. See </><info>rip url -h</info>.")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class SearchCommand(Command):
|
||||||
|
"""
|
||||||
|
Search for and download items in interactive mode.
|
||||||
|
|
||||||
|
search
|
||||||
|
{query : The name to search for}
|
||||||
|
{--s|source=qobuz : Qobuz, Tidal, Soundcloud, Deezer, or Deezloader}
|
||||||
|
{--t|type=album : Album, Playlist, Track, or Artist}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
query = self.argument("query")
|
||||||
|
source, type = clean_options(self.option("source"), self.option("type"))
|
||||||
|
|
||||||
|
config = Config()
|
||||||
|
core = RipCore(config)
|
||||||
|
|
||||||
|
if core.interactive_search(query, source, type):
|
||||||
|
core.download()
|
||||||
|
else:
|
||||||
|
self.line("<error>No items chosen, exiting.</error>")
|
||||||
|
|
||||||
|
|
||||||
|
class DiscoverCommand(Command):
|
||||||
|
"""
|
||||||
|
Browse and download items in interactive mode.
|
||||||
|
|
||||||
|
discover
|
||||||
|
{--s|scrape : Download all of the items in the list}
|
||||||
|
{--m|max-items=50 : The number of items to fetch}
|
||||||
|
{list=ideal-discography : The list to fetch}
|
||||||
|
"""
|
||||||
|
|
||||||
|
help = (
|
||||||
|
"\nAvailable options for <info>list</info>:\n\n"
|
||||||
|
" • most-streamed\n"
|
||||||
|
" • recent-releases\n"
|
||||||
|
" • best-sellers\n"
|
||||||
|
" • press-awards\n"
|
||||||
|
" • ideal-discography\n"
|
||||||
|
" • editor-picks\n"
|
||||||
|
" • most-featured\n"
|
||||||
|
" • qobuzissims\n"
|
||||||
|
" • new-releases\n"
|
||||||
|
" • new-releases-full\n"
|
||||||
|
" • harmonia-mundi\n"
|
||||||
|
" • universal-classic\n"
|
||||||
|
" • universal-jazz\n"
|
||||||
|
" • universal-jeunesse\n"
|
||||||
|
" • universal-chanson\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
from streamrip.constants import QOBUZ_FEATURED_KEYS
|
||||||
|
|
||||||
|
chosen_list = self.argument("list")
|
||||||
|
scrape = self.option("scrape")
|
||||||
|
max_items = self.option("max-items")
|
||||||
|
|
||||||
|
if chosen_list not in QOBUZ_FEATURED_KEYS:
|
||||||
|
self.line(f'<error>Error: list "{chosen_list}" not available</error>')
|
||||||
|
self.line(self.help)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
config = Config()
|
||||||
|
core = RipCore(config)
|
||||||
|
|
||||||
|
if scrape:
|
||||||
|
core.scrape(chosen_list, max_items)
|
||||||
|
core.download()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if core.interactive_search(
|
||||||
|
chosen_list, "qobuz", "featured", limit=int(max_items)
|
||||||
|
):
|
||||||
|
core.download()
|
||||||
|
else:
|
||||||
|
self.line("<error>No items chosen, exiting.</error>")
|
||||||
|
|
||||||
|
|
||||||
|
class LastfmCommand(Command):
|
||||||
|
"""
|
||||||
|
Search for tracks from a list.fm playlist and download them.
|
||||||
|
|
||||||
|
lastfm
|
||||||
|
{--s|source=qobuz : The source to search for items on}
|
||||||
|
{urls* : Last.fm playlist urls}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
source = self.option("source")
|
||||||
|
urls = self.argument("urls")
|
||||||
|
|
||||||
|
config = Config()
|
||||||
|
core = RipCore(config)
|
||||||
|
config.session["lastfm"]["source"] = source
|
||||||
|
core.handle_lastfm_urls(";".join(urls))
|
||||||
|
core.download()
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigCommand(Command):
|
||||||
|
"""
|
||||||
|
Manage the configuration file
|
||||||
|
|
||||||
|
{--o|open : Open the config file in the default application}
|
||||||
|
{--ov|open-vim : Open the config file in (neo)vim}
|
||||||
|
{--d|directory : Open the directory that the config file is located in}
|
||||||
|
{--p|path : Show the config file's path}
|
||||||
|
{--q|qobuz : Set the credentials for Qobuz}
|
||||||
|
{--t|tidal : Log into Tidal}
|
||||||
|
{--dz|deezer : Set the Deezer ARL}
|
||||||
|
{--reset : Reset the config file}
|
||||||
|
{--update : Reset the config file, keeping the credentials}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Application(BaseApplication):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("rip", __version__)
|
||||||
|
|
||||||
|
def _run(self, io):
|
||||||
|
if io.is_debug():
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
super()._run(io)
|
||||||
|
|
||||||
|
# @property
|
||||||
|
# def _default_definition(self):
|
||||||
|
# default_globals = super()._default_definition
|
||||||
|
# default_globals.add_option(Option("convert", shortcut="c", flag=False))
|
||||||
|
# return default_globals
|
||||||
|
|
||||||
|
|
||||||
|
# class ConvertCommand(Command):
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
# class RepairCommand(Command):
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
def clean_options(*opts):
|
||||||
|
return tuple(o.replace("=", "").strip() for o in opts)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
application = Application()
|
||||||
|
application.add(DownloadCommand())
|
||||||
|
application.add(SearchCommand())
|
||||||
|
application.add(DiscoverCommand())
|
||||||
|
application.add(LastfmCommand())
|
||||||
|
# application.add(ConfigCommand())
|
||||||
|
application.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -7,7 +7,7 @@ import shutil
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
import click
|
from click import style, secho
|
||||||
import tomlkit
|
import tomlkit
|
||||||
|
|
||||||
from streamrip.exceptions import InvalidSourceError
|
from streamrip.exceptions import InvalidSourceError
|
||||||
|
|
|
@ -4,7 +4,7 @@ import os
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import click
|
from click import style, secho
|
||||||
|
|
||||||
APPNAME = "streamrip"
|
APPNAME = "streamrip"
|
||||||
APP_DIR = click.get_app_dir(APPNAME)
|
APP_DIR = click.get_app_dir(APPNAME)
|
||||||
|
|
76
rip/core.py
76
rip/core.py
|
@ -10,7 +10,7 @@ from hashlib import md5
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
from typing import Dict, Generator, List, Optional, Tuple, Type, Union
|
from typing import Dict, Generator, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
import click
|
from click import style, secho
|
||||||
import requests
|
import requests
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
@ -162,8 +162,8 @@ class RipCore(list):
|
||||||
if not parsed and len(self) == 0:
|
if not parsed and len(self) == 0:
|
||||||
if "last.fm" in url:
|
if "last.fm" in url:
|
||||||
message = (
|
message = (
|
||||||
f"For last.fm urls, use the {style('lastfm', fg='yellow')} "
|
f"For last.fm urls, use the {click.style('lastfm', fg='yellow')} "
|
||||||
f"command. See {style('rip lastfm --help', fg='yellow')}."
|
f"command. See {click.style('rip lastfm --help', fg='yellow')}."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
message = f"Cannot find urls in text: {url}"
|
message = f"Cannot find urls in text: {url}"
|
||||||
|
@ -175,7 +175,7 @@ class RipCore(list):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"ID {item_id} already downloaded, use --no-db to override."
|
f"ID {item_id} already downloaded, use --no-db to override."
|
||||||
)
|
)
|
||||||
secho(
|
click.secho(
|
||||||
f"ID {item_id} already downloaded, use --no-db to override.",
|
f"ID {item_id} already downloaded, use --no-db to override.",
|
||||||
fg="magenta",
|
fg="magenta",
|
||||||
)
|
)
|
||||||
|
@ -248,12 +248,12 @@ class RipCore(list):
|
||||||
max_items = float("inf")
|
max_items = float("inf")
|
||||||
|
|
||||||
if self.failed_db.is_dummy:
|
if self.failed_db.is_dummy:
|
||||||
secho(
|
click.secho(
|
||||||
"Failed downloads database must be enabled in the config file "
|
"Failed downloads database must be enabled in the config file "
|
||||||
"to repair!",
|
"to repair!",
|
||||||
fg="red",
|
fg="red",
|
||||||
)
|
)
|
||||||
raise click.Abort
|
exit()
|
||||||
|
|
||||||
for counter, (source, media_type, item_id) in enumerate(self.failed_db):
|
for counter, (source, media_type, item_id) in enumerate(self.failed_db):
|
||||||
if counter >= max_items:
|
if counter >= max_items:
|
||||||
|
@ -304,7 +304,7 @@ class RipCore(list):
|
||||||
item.load_meta(**arguments)
|
item.load_meta(**arguments)
|
||||||
except NonStreamable:
|
except NonStreamable:
|
||||||
self.failed_db.add((item.client.source, item.type, item.id))
|
self.failed_db.add((item.client.source, item.type, item.id))
|
||||||
secho(f"{item!s} is not available, skipping.", fg="red")
|
click.secho(f"{item!s} is not available, skipping.", fg="red")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -321,7 +321,7 @@ class RipCore(list):
|
||||||
self.failed_db.add(failed_item_info)
|
self.failed_db.add(failed_item_info)
|
||||||
continue
|
continue
|
||||||
except ItemExists as e:
|
except ItemExists as e:
|
||||||
secho(f'"{e!s}" already exists. Skipping.', fg="yellow")
|
click.secho(f'"{e!s}" already exists. Skipping.', fg="yellow")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if hasattr(item, "id"):
|
if hasattr(item, "id"):
|
||||||
|
@ -366,13 +366,13 @@ class RipCore(list):
|
||||||
creds = self.config.creds(client.source)
|
creds = self.config.creds(client.source)
|
||||||
if client.source == "deezer" and creds["arl"] == "":
|
if client.source == "deezer" and creds["arl"] == "":
|
||||||
if self.config.session["deezer"]["deezloader_warnings"]:
|
if self.config.session["deezer"]["deezloader_warnings"]:
|
||||||
secho(
|
click.secho(
|
||||||
"Falling back to Deezloader (max 320kbps MP3). If you have a subscription, run ",
|
"Falling back to Deezloader (max 320kbps MP3). If you have a subscription, run ",
|
||||||
nl=False,
|
nl=False,
|
||||||
fg="yellow",
|
fg="yellow",
|
||||||
)
|
)
|
||||||
secho("rip config --deezer ", nl=False, bold=True)
|
click.secho("rip config --deezer ", nl=False, bold=True)
|
||||||
secho("to download FLAC files.\n\n", fg="yellow")
|
click.secho("to download FLAC files.\n\n", fg="yellow")
|
||||||
raise DeezloaderFallback
|
raise DeezloaderFallback
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -380,7 +380,7 @@ class RipCore(list):
|
||||||
client.login(**creds)
|
client.login(**creds)
|
||||||
break
|
break
|
||||||
except AuthenticationError:
|
except AuthenticationError:
|
||||||
secho("Invalid credentials, try again.", fg="yellow")
|
click.secho("Invalid credentials, try again.", fg="yellow")
|
||||||
self.prompt_creds(client.source)
|
self.prompt_creds(client.source)
|
||||||
creds = self.config.creds(client.source)
|
creds = self.config.creds(client.source)
|
||||||
except MissingCredentials:
|
except MissingCredentials:
|
||||||
|
@ -419,7 +419,7 @@ class RipCore(list):
|
||||||
|
|
||||||
interpreter_urls = QOBUZ_INTERPRETER_URL_REGEX.findall(url)
|
interpreter_urls = QOBUZ_INTERPRETER_URL_REGEX.findall(url)
|
||||||
if interpreter_urls:
|
if interpreter_urls:
|
||||||
secho(
|
click.secho(
|
||||||
"Extracting IDs from Qobuz interpreter urls. Use urls "
|
"Extracting IDs from Qobuz interpreter urls. Use urls "
|
||||||
"that include the artist ID for faster preprocessing.",
|
"that include the artist ID for faster preprocessing.",
|
||||||
fg="yellow",
|
fg="yellow",
|
||||||
|
@ -432,7 +432,7 @@ class RipCore(list):
|
||||||
|
|
||||||
dynamic_urls = DEEZER_DYNAMIC_LINK_REGEX.findall(url)
|
dynamic_urls = DEEZER_DYNAMIC_LINK_REGEX.findall(url)
|
||||||
if dynamic_urls:
|
if dynamic_urls:
|
||||||
secho(
|
click.secho(
|
||||||
"Extracting IDs from Deezer dynamic link. Use urls "
|
"Extracting IDs from Deezer dynamic link. Use urls "
|
||||||
"of the form https://www.deezer.com/{country}/{type}/{id} for "
|
"of the form https://www.deezer.com/{country}/{type}/{id} for "
|
||||||
"faster processing.",
|
"faster processing.",
|
||||||
|
@ -486,7 +486,7 @@ class RipCore(list):
|
||||||
exit()
|
exit()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self._config_corrupted_message(err)
|
self._config_corrupted_message(err)
|
||||||
raise click.Abort
|
exit()
|
||||||
|
|
||||||
def search_query(title, artist, playlist) -> bool:
|
def search_query(title, artist, playlist) -> bool:
|
||||||
"""Search for a query and add the first result to playlist.
|
"""Search for a query and add the first result to playlist.
|
||||||
|
@ -523,8 +523,10 @@ class RipCore(list):
|
||||||
playlist.append(track)
|
playlist.append(track)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
from streamrip.utils import TQDM_BAR_FORMAT
|
||||||
|
|
||||||
for purl in lastfm_urls:
|
for purl in lastfm_urls:
|
||||||
secho(f"Fetching playlist at {purl}", fg="blue")
|
click.secho(f"Fetching playlist at {purl}", fg="blue")
|
||||||
title, queries = self.get_lastfm_playlist(purl)
|
title, queries = self.get_lastfm_playlist(purl)
|
||||||
|
|
||||||
pl = Playlist(client=self.get_client(lastfm_source), name=title)
|
pl = Playlist(client=self.get_client(lastfm_source), name=title)
|
||||||
|
@ -541,8 +543,11 @@ class RipCore(list):
|
||||||
# only for the progress bar
|
# only for the progress bar
|
||||||
for search_attempt in tqdm(
|
for search_attempt in tqdm(
|
||||||
concurrent.futures.as_completed(futures),
|
concurrent.futures.as_completed(futures),
|
||||||
|
unit="Tracks",
|
||||||
|
dynamic_ncols=True,
|
||||||
total=len(futures),
|
total=len(futures),
|
||||||
desc="Searching",
|
desc="Searching...",
|
||||||
|
bar_format=TQDM_BAR_FORMAT,
|
||||||
):
|
):
|
||||||
if not search_attempt.result():
|
if not search_attempt.result():
|
||||||
tracks_not_found += 1
|
tracks_not_found += 1
|
||||||
|
@ -550,7 +555,8 @@ class RipCore(list):
|
||||||
pl.loaded = True
|
pl.loaded = True
|
||||||
|
|
||||||
if tracks_not_found > 0:
|
if tracks_not_found > 0:
|
||||||
secho(f"{tracks_not_found} tracks not found.", fg="yellow")
|
click.secho(f"{tracks_not_found} tracks not found.", fg="yellow")
|
||||||
|
|
||||||
self.append(pl)
|
self.append(pl)
|
||||||
|
|
||||||
def handle_txt(self, filepath: Union[str, os.PathLike]):
|
def handle_txt(self, filepath: Union[str, os.PathLike]):
|
||||||
|
@ -602,7 +608,7 @@ class RipCore(list):
|
||||||
media_type if media_type != "featured" else "album"
|
media_type if media_type != "featured" else "album"
|
||||||
].from_api(item, client)
|
].from_api(item, client)
|
||||||
|
|
||||||
if i > limit:
|
if i >= limit - 1:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
logger.debug("Not generator")
|
logger.debug("Not generator")
|
||||||
|
@ -616,7 +622,7 @@ class RipCore(list):
|
||||||
for i, item in enumerate(items):
|
for i, item in enumerate(items):
|
||||||
logger.debug(item["title"])
|
logger.debug(item["title"])
|
||||||
yield MEDIA_CLASS[media_type].from_api(item, client) # type: ignore
|
yield MEDIA_CLASS[media_type].from_api(item, client) # type: ignore
|
||||||
if i > limit:
|
if i >= limit - 1:
|
||||||
return
|
return
|
||||||
|
|
||||||
def preview_media(self, media) -> str:
|
def preview_media(self, media) -> str:
|
||||||
|
@ -795,11 +801,7 @@ class RipCore(list):
|
||||||
for page in range(1, last_page + 1)
|
for page in range(1, last_page + 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
for future in tqdm(
|
for future in concurrent.futures.as_completed(futures):
|
||||||
concurrent.futures.as_completed(futures),
|
|
||||||
total=len(futures),
|
|
||||||
desc="Scraping playlist",
|
|
||||||
):
|
|
||||||
get_titles(future.result().text)
|
get_titles(future.result().text)
|
||||||
|
|
||||||
return playlist_title, info
|
return playlist_title, info
|
||||||
|
@ -815,9 +817,9 @@ class RipCore(list):
|
||||||
:type source: str
|
:type source: str
|
||||||
"""
|
"""
|
||||||
if source == "qobuz":
|
if source == "qobuz":
|
||||||
secho("Enter Qobuz email:", fg="green")
|
click.secho("Enter Qobuz email:", fg="green")
|
||||||
self.config.file[source]["email"] = input()
|
self.config.file[source]["email"] = input()
|
||||||
secho(
|
click.secho(
|
||||||
"Enter Qobuz password (will not show on screen):",
|
"Enter Qobuz password (will not show on screen):",
|
||||||
fg="green",
|
fg="green",
|
||||||
)
|
)
|
||||||
|
@ -826,27 +828,27 @@ class RipCore(list):
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
|
|
||||||
self.config.save()
|
self.config.save()
|
||||||
secho(
|
click.secho(
|
||||||
f'Credentials saved to config file at "{self.config._path}"',
|
f'Credentials saved to config file at "{self.config._path}"',
|
||||||
fg="green",
|
fg="green",
|
||||||
)
|
)
|
||||||
elif source == "deezer":
|
elif source == "deezer":
|
||||||
secho(
|
click.secho(
|
||||||
"If you're not sure how to find the ARL cookie, see the instructions at ",
|
"If you're not sure how to find the ARL cookie, see the instructions at ",
|
||||||
italic=True,
|
italic=True,
|
||||||
nl=False,
|
nl=False,
|
||||||
dim=True,
|
dim=True,
|
||||||
)
|
)
|
||||||
secho(
|
click.secho(
|
||||||
"https://github.com/nathom/streamrip/wiki/Finding-your-Deezer-ARL-Cookie",
|
"https://github.com/nathom/streamrip/wiki/Finding-your-Deezer-ARL-Cookie",
|
||||||
underline=True,
|
underline=True,
|
||||||
italic=True,
|
italic=True,
|
||||||
fg="blue",
|
fg="blue",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.config.file["deezer"]["arl"] = input(style("ARL: ", fg="green"))
|
self.config.file["deezer"]["arl"] = input(click.style("ARL: ", fg="green"))
|
||||||
self.config.save()
|
self.config.save()
|
||||||
secho(
|
click.secho(
|
||||||
f'Credentials saved to config file at "{self.config._path}"',
|
f'Credentials saved to config file at "{self.config._path}"',
|
||||||
fg="green",
|
fg="green",
|
||||||
)
|
)
|
||||||
|
@ -854,19 +856,19 @@ class RipCore(list):
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
def _config_updating_message(self):
|
def _config_updating_message(self):
|
||||||
secho(
|
click.secho(
|
||||||
"Updating config file... Some settings may be lost. Please run the "
|
"Updating config file... Some settings may be lost. Please run the "
|
||||||
"command again.",
|
"command again.",
|
||||||
fg="magenta",
|
fg="magenta",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _config_corrupted_message(self, err: Exception):
|
def _config_corrupted_message(self, err: Exception):
|
||||||
secho(
|
click.secho(
|
||||||
"There was a problem with your config file. This happens "
|
"There was a problem with your config file. This happens "
|
||||||
"sometimes after updates. Run ",
|
"sometimes after updates. Run ",
|
||||||
nl=False,
|
nl=False,
|
||||||
fg="red",
|
fg="red",
|
||||||
)
|
)
|
||||||
secho("rip config --reset ", fg="yellow", nl=False)
|
click.secho("rip config --reset ", fg="yellow", nl=False)
|
||||||
secho("to reset it. You will need to log in again.", fg="red")
|
click.secho("to reset it. You will need to log in again.", fg="red")
|
||||||
secho(str(err), fg="red")
|
click.secho(str(err), fg="red")
|
||||||
|
|
|
@ -11,8 +11,8 @@ from abc import ABC, abstractmethod
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Any, Dict, Generator, Optional, Sequence, Tuple, Union
|
from typing import Any, Dict, Generator, Optional, Sequence, Tuple, Union
|
||||||
|
|
||||||
import click # type: ignore
|
from click import style, secho
|
||||||
import deezer # type: ignore
|
import deezer
|
||||||
import requests
|
import requests
|
||||||
from Cryptodome.Cipher import AES, Blowfish # type: ignore
|
from Cryptodome.Cipher import AES, Blowfish # type: ignore
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Streamrip specific exceptions."""
|
"""Streamrip specific exceptions."""
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import click
|
from click import style, secho
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationError(Exception):
|
class AuthenticationError(Exception):
|
||||||
|
|
|
@ -15,12 +15,11 @@ import subprocess
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
from typing import Any, Dict, Generator, Iterable, List, Optional, Tuple, Union
|
from typing import Any, Dict, Generator, Iterable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import click
|
from click import style, secho
|
||||||
from mutagen.flac import FLAC, Picture
|
from mutagen.flac import FLAC, Picture
|
||||||
from mutagen.id3 import APIC, ID3, ID3NoHeaderError
|
from mutagen.id3 import APIC, ID3, ID3NoHeaderError
|
||||||
from mutagen.mp4 import MP4, MP4Cover
|
from mutagen.mp4 import MP4, MP4Cover
|
||||||
from pathvalidate import sanitize_filename, sanitize_filepath
|
from pathvalidate import sanitize_filename, sanitize_filepath
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
from . import converter
|
from . import converter
|
||||||
from .clients import Client, DeezloaderClient
|
from .clients import Client, DeezloaderClient
|
||||||
|
@ -50,7 +49,6 @@ from .utils import (
|
||||||
get_stats_from_quality,
|
get_stats_from_quality,
|
||||||
safe_get,
|
safe_get,
|
||||||
tidal_cover_url,
|
tidal_cover_url,
|
||||||
tqdm_download,
|
|
||||||
tqdm_stream,
|
tqdm_stream,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -272,7 +270,7 @@ class Track(Media):
|
||||||
:type progress_bar: bool
|
:type progress_bar: bool
|
||||||
"""
|
"""
|
||||||
if not self.part_of_tracklist:
|
if not self.part_of_tracklist:
|
||||||
secho(f"Downloading {self!s}\n", bold=True)
|
click.secho(f"Downloading {self!s}\n", bold=True)
|
||||||
|
|
||||||
self._prepare_download(
|
self._prepare_download(
|
||||||
quality=quality,
|
quality=quality,
|
||||||
|
@ -295,7 +293,6 @@ class Track(Media):
|
||||||
|
|
||||||
if self.client.source == "qobuz":
|
if self.client.source == "qobuz":
|
||||||
if not self.__validate_qobuz_dl_info(dl_info):
|
if not self.__validate_qobuz_dl_info(dl_info):
|
||||||
# secho("Track is not available for download", fg="red")
|
|
||||||
raise NonStreamable("Track is not available for download")
|
raise NonStreamable("Track is not available for download")
|
||||||
|
|
||||||
self.sampling_rate = dl_info.get("sampling_rate")
|
self.sampling_rate = dl_info.get("sampling_rate")
|
||||||
|
@ -314,7 +311,7 @@ class Track(Media):
|
||||||
words[0] + " " + " ".join(map(str.lower, words[1:])) + "."
|
words[0] + " " + " ".join(map(str.lower, words[1:])) + "."
|
||||||
)
|
)
|
||||||
|
|
||||||
secho(f"Panic: {e} dl_info = {dl_info}", fg="red")
|
click.secho(f"Panic: {e} dl_info = {dl_info}", fg="red")
|
||||||
raise NonStreamable
|
raise NonStreamable
|
||||||
|
|
||||||
_quick_download(download_url, self.path, desc=self._progress_desc)
|
_quick_download(download_url, self.path, desc=self._progress_desc)
|
||||||
|
@ -470,7 +467,6 @@ class Track(Media):
|
||||||
"""Download the cover art, if cover_url is given."""
|
"""Download the cover art, if cover_url is given."""
|
||||||
self.cover_path = os.path.join(gettempdir(), f"cover{hash(self.cover_url)}.jpg")
|
self.cover_path = os.path.join(gettempdir(), f"cover{hash(self.cover_url)}.jpg")
|
||||||
logger.debug(f"Downloading cover from {self.cover_url}")
|
logger.debug(f"Downloading cover from {self.cover_url}")
|
||||||
# secho(f"\nDownloading cover art for {self!s}", fg="blue")
|
|
||||||
|
|
||||||
if not os.path.exists(self.cover_path):
|
if not os.path.exists(self.cover_path):
|
||||||
_cover_download(self.cover_url, self.cover_path)
|
_cover_download(self.cover_url, self.cover_path)
|
||||||
|
@ -670,7 +666,7 @@ class Track(Media):
|
||||||
"""
|
"""
|
||||||
if not self.downloaded:
|
if not self.downloaded:
|
||||||
logger.debug("Track not downloaded, skipping conversion")
|
logger.debug("Track not downloaded, skipping conversion")
|
||||||
secho("Track not downloaded, skipping conversion", fg="magenta")
|
click.secho("Track not downloaded, skipping conversion", fg="magenta")
|
||||||
return
|
return
|
||||||
|
|
||||||
CONV_CLASS = {
|
CONV_CLASS = {
|
||||||
|
@ -687,15 +683,15 @@ class Track(Media):
|
||||||
try:
|
try:
|
||||||
self.container = codec.upper()
|
self.container = codec.upper()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
secho("Error: No audio codec chosen to convert to.", fg="red")
|
click.secho("Error: No audio codec chosen to convert to.", fg="red")
|
||||||
raise click.Abort
|
exit()
|
||||||
|
|
||||||
if not hasattr(self, "final_path"):
|
if not hasattr(self, "final_path"):
|
||||||
self.format_final_path()
|
self.format_final_path()
|
||||||
|
|
||||||
if not os.path.isfile(self.path):
|
if not os.path.isfile(self.path):
|
||||||
logger.info("File %s does not exist. Skipping conversion.", self.path)
|
logger.info("File %s does not exist. Skipping conversion.", self.path)
|
||||||
secho(f"{self!s} does not exist. Skipping conversion.", fg="red")
|
click.secho(f"{self!s} does not exist. Skipping conversion.", fg="red")
|
||||||
return
|
return
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
|
@ -707,7 +703,6 @@ class Track(Media):
|
||||||
sampling_rate=kwargs.get("sampling_rate"),
|
sampling_rate=kwargs.get("sampling_rate"),
|
||||||
remove_source=kwargs.get("remove_source", True),
|
remove_source=kwargs.get("remove_source", True),
|
||||||
)
|
)
|
||||||
# secho(f"Converting {self!s}", fg="blue")
|
|
||||||
engine.convert()
|
engine.convert()
|
||||||
self.path = engine.final_fn
|
self.path = engine.final_fn
|
||||||
self.final_path = self.final_path.replace(
|
self.final_path = self.final_path.replace(
|
||||||
|
@ -814,7 +809,7 @@ class Video(Media):
|
||||||
|
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
"""
|
"""
|
||||||
secho(
|
click.secho(
|
||||||
f"Downloading {self.title} (Video). This may take a while.",
|
f"Downloading {self.title} (Video). This may take a while.",
|
||||||
fg="blue",
|
fg="blue",
|
||||||
)
|
)
|
||||||
|
@ -949,7 +944,7 @@ class YoutubeVideo(Media):
|
||||||
:type youtube_video_downloads_folder: str
|
:type youtube_video_downloads_folder: str
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
"""
|
"""
|
||||||
secho(f"Downloading url {self.id}", fg="blue")
|
click.secho(f"Downloading url {self.id}", fg="blue")
|
||||||
filename_formatter = "%(track_number)s.%(track)s.%(container)s"
|
filename_formatter = "%(track_number)s.%(track)s.%(container)s"
|
||||||
filename = os.path.join(parent_folder, filename_formatter)
|
filename = os.path.join(parent_folder, filename_formatter)
|
||||||
|
|
||||||
|
@ -969,7 +964,7 @@ class YoutubeVideo(Media):
|
||||||
)
|
)
|
||||||
|
|
||||||
if download_youtube_videos:
|
if download_youtube_videos:
|
||||||
secho("Downloading video stream", fg="blue")
|
click.secho("Downloading video stream", fg="blue")
|
||||||
pv = subprocess.Popen(
|
pv = subprocess.Popen(
|
||||||
[
|
[
|
||||||
"youtube-dl",
|
"youtube-dl",
|
||||||
|
@ -1107,18 +1102,18 @@ class Tracklist(list):
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
executor.shutdown()
|
executor.shutdown()
|
||||||
click.echo("Aborted! May take some time to shutdown.")
|
click.echo("Aborted! May take some time to shutdown.")
|
||||||
raise click.Abort
|
exit()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for item in self:
|
for item in self:
|
||||||
if self.client.source != "soundcloud":
|
if self.client.source != "soundcloud":
|
||||||
# soundcloud only gets metadata after `target` is called
|
# soundcloud only gets metadata after `target` is called
|
||||||
# message will be printed in `target`
|
# message will be printed in `target`
|
||||||
secho(f'\nDownloading "{item!s}"', bold=True, fg="green")
|
click.secho(f'\nDownloading "{item!s}"', bold=True, fg="green")
|
||||||
try:
|
try:
|
||||||
target(item, **kwargs)
|
target(item, **kwargs)
|
||||||
except ItemExists:
|
except ItemExists:
|
||||||
secho(f"{item!s} exists. Skipping.", fg="yellow")
|
click.secho(f"{item!s} exists. Skipping.", fg="yellow")
|
||||||
except NonStreamable as e:
|
except NonStreamable as e:
|
||||||
e.print(item)
|
e.print(item)
|
||||||
failed_downloads.append((item.client.source, item.type, item.id))
|
failed_downloads.append((item.client.source, item.type, item.id))
|
||||||
|
@ -1264,7 +1259,7 @@ class Tracklist(list):
|
||||||
|
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
secho(
|
click.secho(
|
||||||
f"\n\nDownloading {self.title} ({self.__class__.__name__})\n",
|
f"\n\nDownloading {self.title} ({self.__class__.__name__})\n",
|
||||||
fg="magenta",
|
fg="magenta",
|
||||||
bold=True,
|
bold=True,
|
||||||
|
@ -1422,7 +1417,7 @@ class Album(Tracklist, Media):
|
||||||
self.download_message()
|
self.download_message()
|
||||||
|
|
||||||
# choose optimal cover size and download it
|
# choose optimal cover size and download it
|
||||||
secho("Downloading cover art", bold=True)
|
click.secho("Downloading cover art", bold=True)
|
||||||
cover_path = os.path.join(gettempdir(), f"cover_{hash(self)}.jpg")
|
cover_path = os.path.join(gettempdir(), f"cover_{hash(self)}.jpg")
|
||||||
embed_cover_size = kwargs.get("embed_cover_size", "large")
|
embed_cover_size = kwargs.get("embed_cover_size", "large")
|
||||||
|
|
||||||
|
@ -1446,7 +1441,7 @@ class Album(Tracklist, Media):
|
||||||
|
|
||||||
cover_size = os.path.getsize(cover_path)
|
cover_size = os.path.getsize(cover_path)
|
||||||
if cover_size > FLAC_MAX_BLOCKSIZE: # 16.77 MB
|
if cover_size > FLAC_MAX_BLOCKSIZE: # 16.77 MB
|
||||||
secho(
|
click.secho(
|
||||||
"Downgrading embedded cover size, too large ({cover_size}).",
|
"Downgrading embedded cover size, too large ({cover_size}).",
|
||||||
fg="bright_yellow",
|
fg="bright_yellow",
|
||||||
)
|
)
|
||||||
|
@ -1473,7 +1468,7 @@ class Album(Tracklist, Media):
|
||||||
and kwargs.get("download_booklets", True)
|
and kwargs.get("download_booklets", True)
|
||||||
and not any(f.endswith(".pdf") for f in os.listdir(self.folder))
|
and not any(f.endswith(".pdf") for f in os.listdir(self.folder))
|
||||||
):
|
):
|
||||||
secho("\nDownloading booklets", bold=True)
|
click.secho("\nDownloading booklets", bold=True)
|
||||||
for item in self.booklets:
|
for item in self.booklets:
|
||||||
Booklet(item).download(parent_folder=self.folder)
|
Booklet(item).download(parent_folder=self.folder)
|
||||||
|
|
||||||
|
@ -1783,9 +1778,9 @@ class Playlist(Tracklist, Media):
|
||||||
kwargs["parent_folder"] = self.folder
|
kwargs["parent_folder"] = self.folder
|
||||||
if self.client.source == "soundcloud":
|
if self.client.source == "soundcloud":
|
||||||
item.load_meta()
|
item.load_meta()
|
||||||
secho(f"Downloading {item!s}", fg="blue")
|
click.secho(f"Downloading {item!s}", fg="blue")
|
||||||
|
|
||||||
if playlist_to_album := kwargs.get("set_playlist_to_album", False):
|
if kwargs.get("set_playlist_to_album", False):
|
||||||
item.meta.album = self.name
|
item.meta.album = self.name
|
||||||
item.meta.albumartist = self.creator
|
item.meta.albumartist = self.creator
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,12 @@ import base64
|
||||||
import functools
|
import functools
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from json import JSONDecodeError
|
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
from typing import Dict, Hashable, Iterator, Optional, Tuple, Union
|
from typing import Dict, Hashable, Iterator, Optional, Tuple, Union
|
||||||
|
|
||||||
import click
|
from click import secho
|
||||||
import requests
|
import requests
|
||||||
from Cryptodome.Cipher import Blowfish
|
from Cryptodome.Cipher import Blowfish
|
||||||
from pathvalidate import sanitize_filename
|
from pathvalidate import sanitize_filename
|
||||||
|
@ -240,50 +238,6 @@ def get_stats_from_quality(
|
||||||
raise InvalidQuality(quality_id)
|
raise InvalidQuality(quality_id)
|
||||||
|
|
||||||
|
|
||||||
def tqdm_download(url: str, filepath: str, params: dict = None, desc: str = None):
|
|
||||||
"""Download a file with a progress bar.
|
|
||||||
|
|
||||||
:param url: url to direct download
|
|
||||||
:param filepath: file to write
|
|
||||||
:type url: str
|
|
||||||
:type filepath: str
|
|
||||||
"""
|
|
||||||
logger.debug(f"Downloading {url} to {filepath} with params {params}")
|
|
||||||
if params is None:
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
session = gen_threadsafe_session()
|
|
||||||
r = session.get(url, allow_redirects=True, stream=True, params=params)
|
|
||||||
total = int(r.headers.get("content-length", 0))
|
|
||||||
logger.debug("File size = %s", total)
|
|
||||||
if total < 1000 and not url.endswith("jpg") and not url.endswith("png"):
|
|
||||||
logger.debug("Response text: %s", r.text)
|
|
||||||
try:
|
|
||||||
raise NonStreamable(r.json()["error"])
|
|
||||||
except JSONDecodeError:
|
|
||||||
raise NonStreamable("Resource not found.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(filepath, "wb") as file, tqdm(
|
|
||||||
total=total,
|
|
||||||
unit="iB",
|
|
||||||
unit_scale=True,
|
|
||||||
unit_divisor=1024,
|
|
||||||
desc=desc,
|
|
||||||
dynamic_ncols=True,
|
|
||||||
# leave=False,
|
|
||||||
) as bar:
|
|
||||||
for data in r.iter_content(chunk_size=1024):
|
|
||||||
size = file.write(data)
|
|
||||||
bar.update(size)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
os.remove(filepath)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def clean_format(formatter: str, format_info):
|
def clean_format(formatter: str, format_info):
|
||||||
"""Format track or folder names sanitizing every formatter key.
|
"""Format track or folder names sanitizing every formatter key.
|
||||||
|
|
||||||
|
@ -345,14 +299,14 @@ def decrypt_mqa_file(in_path, out_path, encryption_key):
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
from Crypto.Util import Counter
|
from Crypto.Util import Counter
|
||||||
except (ImportError, ModuleNotFoundError):
|
except (ImportError, ModuleNotFoundError):
|
||||||
secho(
|
click.secho(
|
||||||
"To download this item in MQA, you need to run ",
|
"To download this item in MQA, you need to run ",
|
||||||
fg="yellow",
|
fg="yellow",
|
||||||
nl=False,
|
nl=False,
|
||||||
)
|
)
|
||||||
secho("pip3 install pycryptodome --upgrade", fg="blue", nl=False)
|
click.secho("pip3 install pycryptodome --upgrade", fg="blue", nl=False)
|
||||||
secho(".")
|
click.secho(".")
|
||||||
raise click.Abort
|
exit()
|
||||||
|
|
||||||
# Do not change this
|
# Do not change this
|
||||||
master_key = "UIlTTEMmmLfGowo/UC60x2H45W6MdGgTRfo/umg4754="
|
master_key = "UIlTTEMmmLfGowo/UC60x2H45W6MdGgTRfo/umg4754="
|
||||||
|
@ -431,7 +385,7 @@ def decho(message, fg=None):
|
||||||
:param fg: ANSI color with which to display the message on the
|
:param fg: ANSI color with which to display the message on the
|
||||||
screen
|
screen
|
||||||
"""
|
"""
|
||||||
secho(message, fg=fg)
|
click.secho(message, fg=fg)
|
||||||
logger.debug(message)
|
logger.debug(message)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import click
|
from click import style, secho, echo
|
||||||
|
|
||||||
test_urls = {
|
test_urls = {
|
||||||
"qobuz": "https://www.qobuz.com/us-en/album/blackest-blue-morcheeba/h4nngz0wgqesc",
|
"qobuz": "https://www.qobuz.com/us-en/album/blackest-blue-morcheeba/h4nngz0wgqesc",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue