Begin move to cleo

This commit is contained in:
nathom 2021-07-30 11:20:36 -07:00
parent 54f4ab99af
commit 9970ac548f
13 changed files with 528 additions and 150 deletions

View file

@ -22,6 +22,9 @@ ignore_missing_imports = True
[mypy-tomlkit.*]
ignore_missing_imports = True
[mypy-Crypto.*]
ignore_missing_imports = True
[mypy-Cryptodome.*]
ignore_missing_imports = True
@ -30,3 +33,9 @@ ignore_missing_imports = True
[mypy-PIL.*]
ignore_missing_imports = True
[mypy-cleo.*]
ignore_missing_imports = True
[mypy-deezer.*]
ignore_missing_imports = True

185
poetry.lock generated
View file

@ -6,6 +6,14 @@ category = "dev"
optional = false
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]]
name = "autodoc"
version = "0.5.0"
@ -44,6 +52,28 @@ soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""}
html5lib = ["html5lib"]
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]]
name = "certifi"
version = "2021.5.30"
@ -63,6 +93,18 @@ python-versions = ">=3.5.0"
[package.extras]
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]]
name = "click"
version = "8.0.1"
@ -82,6 +124,14 @@ category = "main"
optional = false
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]]
name = "decorator"
version = "5.0.9"
@ -125,6 +175,20 @@ category = "dev"
optional = false
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]]
name = "jinja2"
version = "3.0.1"
@ -155,6 +219,14 @@ category = "main"
optional = false
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]]
name = "packaging"
version = "21.0"
@ -166,6 +238,14 @@ python-versions = ">=3.6"
[package.dependencies]
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]]
name = "pathvalidate"
version = "2.4.1"
@ -212,6 +292,14 @@ category = "dev"
optional = false
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]]
name = "pyparsing"
version = "2.4.7"
@ -228,6 +316,14 @@ category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "regex"
version = "2021.7.6"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "requests"
version = "2.26.0"
@ -380,6 +476,14 @@ python-versions = ">=3.5"
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"]
[[package]]
name = "tomli"
version = "1.1.0"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "tomlkit"
version = "0.7.2"
@ -486,13 +590,17 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "8128cd9440b4931b23509f72a3952e1d4115a3783ded70c3b458de921ed30c56"
content-hash = "3e7ca36060a8049300d1408d6b7595c1fdf27cd5b4ed9cfab68af94d083d7935"
[metadata.files]
alabaster = [
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
{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 = [
{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.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 = [
{file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
{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-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 = [
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
{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.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 = [
{file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"},
{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.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 = [
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
{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.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 = [
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
{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 = [
{file = "pathvalidate-2.4.1-py3-none-any.whl", hash = "sha256:f5dde7efeeb4262784c5e1331e02752d07c1ec3ee5ea42683fe211155652b808"},
{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.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 = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{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.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 = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{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-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 = [
{file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"},
{file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"},

View file

@ -19,7 +19,7 @@ classifiers = [
]
[tool.poetry.scripts]
rip = "rip.cli:main"
rip = "rip.cli_cleo:main"
[tool.poetry.dependencies]
python = "^3.8"
@ -35,6 +35,8 @@ windows-curses = {version = "^2.2.0", platform = 'win32 or cygwin'}
Pillow = "^8.3.0"
deezer-py = "^1.0.4"
pycryptodomex = "^3.10.1"
cleo = {version = "1.0.0a3", allow-prereleases = true}
appdirs = "^1.4.4"
[tool.poetry.urls]
"Bug Reports" = "https://github.com/nathom/streamrip/issues"
@ -44,6 +46,8 @@ Sphinx = "^4.1.1"
autodoc = "^0.5.0"
types-click = "^7.1.2"
types-Pillow = "^8.3.1"
black = "^21.7b0"
isort = "^5.9.3"
[build-system]
requires = ["poetry-core>=1.0.0"]

View file

@ -9,15 +9,31 @@ logging.basicConfig(level="WARNING")
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(
"-u",
"--urls",
metavar="URLS",
help="Url from Qobuz, Tidal, SoundCloud, or Deezer",
multiple=True,
)
@click.option(
"-q",
"--quality",
@ -27,6 +43,7 @@ logger = logging.getLogger("streamrip")
@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("--debug", is_flag=True, help="Show debugging logs.")
@click.argument("URLS", nargs=1)
@click.version_option(prog_name="rip", version=__version__)
@click.pass_context
def cli(ctx, **kwargs):
@ -54,9 +71,7 @@ def cli(ctx, **kwargs):
from .constants import CONFIG_DIR
from .core import RipCore
logging.basicConfig(level="WARNING")
logger = logging.getLogger("streamrip")
print(kwargs)
if not os.path.isdir(CONFIG_DIR):
os.makedirs(CONFIG_DIR, exist_ok=True)
@ -68,7 +83,8 @@ def cli(ctx, **kwargs):
logger.debug("Starting debug log")
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 {
None,
@ -90,13 +106,13 @@ def cli(ctx, **kwargs):
r = requests.get("https://pypi.org/pypi/streamrip/json").json()
newest = r["info"]["version"]
if __version__ != newest:
secho(
click.secho(
"A new version of streamrip is available! "
"Run `pip3 install streamrip --upgrade` to update.",
fg="yellow",
)
else:
secho("streamrip is up-to-date!", fg="green")
click.secho("streamrip is up-to-date!", fg="green")
if kwargs["no_db"]:
config.session["database"]["enabled"] = False
@ -108,7 +124,7 @@ def cli(ctx, **kwargs):
if kwargs["quality"] is not None:
quality = int(kwargs["quality"])
if quality not in range(5):
secho("Invalid quality", fg="red")
click.secho("Invalid quality", fg="red")
return
config.session["qobuz"]["quality"] = quality
@ -126,7 +142,7 @@ def cli(ctx, **kwargs):
logger.debug(f"Handling {kwargs['text']}")
core.handle_txt(kwargs["text"])
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:
core.download()
@ -150,6 +166,7 @@ def filter_discography(ctx, **kwargs):
For basic filtering, use the `--repeats` and `--features` filters.
"""
raise Exception
filters = kwargs.copy()
filters.pop("urls")
config.session["filters"] = filters
@ -206,7 +223,7 @@ def search(ctx, **kwargs):
if core.interactive_search(query, kwargs["source"], kwargs["type"]):
core.download()
else:
secho("No items chosen, exiting.", fg="bright_red")
click.secho("No items chosen, exiting.", fg="bright_red")
@cli.command()
@ -333,10 +350,10 @@ def config(ctx, **kwargs):
config.update()
if kwargs["path"]:
echo(CONFIG_PATH)
click.echo(CONFIG_PATH)
if kwargs["open"]:
secho(f"Opening {CONFIG_PATH}", fg="green")
click.secho(f"Opening {CONFIG_PATH}", fg="green")
click.launch(CONFIG_PATH)
if kwargs["open_vim"]:
@ -347,41 +364,41 @@ def config(ctx, **kwargs):
if kwargs["directory"]:
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)
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(
getpass(prompt="").encode("utf-8")
).hexdigest()
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"]:
client = TidalClient()
client.login()
config.file["tidal"].update(client.get_tokens())
config.save()
secho("Credentials saved to config.", fg="green")
click.secho("Credentials saved to config.", fg="green")
if kwargs["deezer"]:
secho(
click.secho(
"If you're not sure how to find the ARL cookie, see the instructions at ",
italic=True,
nl=False,
dim=True,
)
secho(
click.secho(
"https://github.com/nathom/streamrip/wiki/Finding-your-Deezer-ARL-Cookie",
underline=True,
italic=True,
fg="blue",
)
config.file["deezer"]["arl"] = input(style("ARL: ", fg="green"))
config.file["deezer"]["arl"] = input(click.style("ARL: ", fg="green"))
config.save()
@ -481,7 +498,7 @@ def convert(ctx, **kwargs):
elif os.path.isfile(kwargs["path"]):
codec_map[codec](filename=kwargs["path"], **converter_args).convert()
else:
secho(f"File {kwargs['path']} does not exist.", fg="red")
click.secho(f"File {kwargs['path']} does not exist.", fg="red")
@cli.command()
@ -503,7 +520,7 @@ def repair(ctx, **kwargs):
def none_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():

214
rip/cli_cleo.py Normal file
View 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()

View file

@ -7,7 +7,7 @@ import shutil
from pprint import pformat
from typing import Any, Dict
import click
from click import style, secho
import tomlkit
from streamrip.exceptions import InvalidSourceError

View file

@ -4,7 +4,7 @@ import os
import re
from pathlib import Path
import click
from click import style, secho
APPNAME = "streamrip"
APP_DIR = click.get_app_dir(APPNAME)

View file

@ -10,7 +10,7 @@ from hashlib import md5
from string import Formatter
from typing import Dict, Generator, List, Optional, Tuple, Type, Union
import click
from click import style, secho
import requests
from tqdm import tqdm
@ -162,8 +162,8 @@ class RipCore(list):
if not parsed and len(self) == 0:
if "last.fm" in url:
message = (
f"For last.fm urls, use the {style('lastfm', fg='yellow')} "
f"command. See {style('rip lastfm --help', fg='yellow')}."
f"For last.fm urls, use the {click.style('lastfm', fg='yellow')} "
f"command. See {click.style('rip lastfm --help', fg='yellow')}."
)
else:
message = f"Cannot find urls in text: {url}"
@ -175,7 +175,7 @@ class RipCore(list):
logger.info(
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.",
fg="magenta",
)
@ -248,12 +248,12 @@ class RipCore(list):
max_items = float("inf")
if self.failed_db.is_dummy:
secho(
click.secho(
"Failed downloads database must be enabled in the config file "
"to repair!",
fg="red",
)
raise click.Abort
exit()
for counter, (source, media_type, item_id) in enumerate(self.failed_db):
if counter >= max_items:
@ -304,7 +304,7 @@ class RipCore(list):
item.load_meta(**arguments)
except NonStreamable:
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
try:
@ -321,7 +321,7 @@ class RipCore(list):
self.failed_db.add(failed_item_info)
continue
except ItemExists as e:
secho(f'"{e!s}" already exists. Skipping.', fg="yellow")
click.secho(f'"{e!s}" already exists. Skipping.', fg="yellow")
continue
if hasattr(item, "id"):
@ -366,13 +366,13 @@ class RipCore(list):
creds = self.config.creds(client.source)
if client.source == "deezer" and creds["arl"] == "":
if self.config.session["deezer"]["deezloader_warnings"]:
secho(
click.secho(
"Falling back to Deezloader (max 320kbps MP3). If you have a subscription, run ",
nl=False,
fg="yellow",
)
secho("rip config --deezer ", nl=False, bold=True)
secho("to download FLAC files.\n\n", fg="yellow")
click.secho("rip config --deezer ", nl=False, bold=True)
click.secho("to download FLAC files.\n\n", fg="yellow")
raise DeezloaderFallback
while True:
@ -380,7 +380,7 @@ class RipCore(list):
client.login(**creds)
break
except AuthenticationError:
secho("Invalid credentials, try again.", fg="yellow")
click.secho("Invalid credentials, try again.", fg="yellow")
self.prompt_creds(client.source)
creds = self.config.creds(client.source)
except MissingCredentials:
@ -419,7 +419,7 @@ class RipCore(list):
interpreter_urls = QOBUZ_INTERPRETER_URL_REGEX.findall(url)
if interpreter_urls:
secho(
click.secho(
"Extracting IDs from Qobuz interpreter urls. Use urls "
"that include the artist ID for faster preprocessing.",
fg="yellow",
@ -432,7 +432,7 @@ class RipCore(list):
dynamic_urls = DEEZER_DYNAMIC_LINK_REGEX.findall(url)
if dynamic_urls:
secho(
click.secho(
"Extracting IDs from Deezer dynamic link. Use urls "
"of the form https://www.deezer.com/{country}/{type}/{id} for "
"faster processing.",
@ -486,7 +486,7 @@ class RipCore(list):
exit()
except Exception as err:
self._config_corrupted_message(err)
raise click.Abort
exit()
def search_query(title, artist, playlist) -> bool:
"""Search for a query and add the first result to playlist.
@ -523,8 +523,10 @@ class RipCore(list):
playlist.append(track)
return True
from streamrip.utils import TQDM_BAR_FORMAT
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)
pl = Playlist(client=self.get_client(lastfm_source), name=title)
@ -541,8 +543,11 @@ class RipCore(list):
# only for the progress bar
for search_attempt in tqdm(
concurrent.futures.as_completed(futures),
unit="Tracks",
dynamic_ncols=True,
total=len(futures),
desc="Searching",
desc="Searching...",
bar_format=TQDM_BAR_FORMAT,
):
if not search_attempt.result():
tracks_not_found += 1
@ -550,7 +555,8 @@ class RipCore(list):
pl.loaded = True
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)
def handle_txt(self, filepath: Union[str, os.PathLike]):
@ -602,7 +608,7 @@ class RipCore(list):
media_type if media_type != "featured" else "album"
].from_api(item, client)
if i > limit:
if i >= limit - 1:
return
else:
logger.debug("Not generator")
@ -616,7 +622,7 @@ class RipCore(list):
for i, item in enumerate(items):
logger.debug(item["title"])
yield MEDIA_CLASS[media_type].from_api(item, client) # type: ignore
if i > limit:
if i >= limit - 1:
return
def preview_media(self, media) -> str:
@ -795,11 +801,7 @@ class RipCore(list):
for page in range(1, last_page + 1)
]
for future in tqdm(
concurrent.futures.as_completed(futures),
total=len(futures),
desc="Scraping playlist",
):
for future in concurrent.futures.as_completed(futures):
get_titles(future.result().text)
return playlist_title, info
@ -815,9 +817,9 @@ class RipCore(list):
:type source: str
"""
if source == "qobuz":
secho("Enter Qobuz email:", fg="green")
click.secho("Enter Qobuz email:", fg="green")
self.config.file[source]["email"] = input()
secho(
click.secho(
"Enter Qobuz password (will not show on screen):",
fg="green",
)
@ -826,27 +828,27 @@ class RipCore(list):
).hexdigest()
self.config.save()
secho(
click.secho(
f'Credentials saved to config file at "{self.config._path}"',
fg="green",
)
elif source == "deezer":
secho(
click.secho(
"If you're not sure how to find the ARL cookie, see the instructions at ",
italic=True,
nl=False,
dim=True,
)
secho(
click.secho(
"https://github.com/nathom/streamrip/wiki/Finding-your-Deezer-ARL-Cookie",
underline=True,
italic=True,
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()
secho(
click.secho(
f'Credentials saved to config file at "{self.config._path}"',
fg="green",
)
@ -854,19 +856,19 @@ class RipCore(list):
raise Exception
def _config_updating_message(self):
secho(
click.secho(
"Updating config file... Some settings may be lost. Please run the "
"command again.",
fg="magenta",
)
def _config_corrupted_message(self, err: Exception):
secho(
click.secho(
"There was a problem with your config file. This happens "
"sometimes after updates. Run ",
nl=False,
fg="red",
)
secho("rip config --reset ", fg="yellow", nl=False)
secho("to reset it. You will need to log in again.", fg="red")
secho(str(err), fg="red")
click.secho("rip config --reset ", fg="yellow", nl=False)
click.secho("to reset it. You will need to log in again.", fg="red")
click.secho(str(err), fg="red")

View file

@ -11,8 +11,8 @@ from abc import ABC, abstractmethod
from pprint import pformat
from typing import Any, Dict, Generator, Optional, Sequence, Tuple, Union
import click # type: ignore
import deezer # type: ignore
from click import style, secho
import deezer
import requests
from Cryptodome.Cipher import AES, Blowfish # type: ignore

View file

@ -1,7 +1,7 @@
"""Streamrip specific exceptions."""
from typing import List
import click
from click import style, secho
class AuthenticationError(Exception):

View file

@ -15,12 +15,11 @@ import subprocess
from tempfile import gettempdir
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.id3 import APIC, ID3, ID3NoHeaderError
from mutagen.mp4 import MP4, MP4Cover
from pathvalidate import sanitize_filename, sanitize_filepath
from tqdm import tqdm
from . import converter
from .clients import Client, DeezloaderClient
@ -50,7 +49,6 @@ from .utils import (
get_stats_from_quality,
safe_get,
tidal_cover_url,
tqdm_download,
tqdm_stream,
)
@ -272,7 +270,7 @@ class Track(Media):
:type progress_bar: bool
"""
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(
quality=quality,
@ -295,7 +293,6 @@ class Track(Media):
if self.client.source == "qobuz":
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")
self.sampling_rate = dl_info.get("sampling_rate")
@ -314,7 +311,7 @@ class Track(Media):
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
_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."""
self.cover_path = os.path.join(gettempdir(), f"cover{hash(self.cover_url)}.jpg")
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):
_cover_download(self.cover_url, self.cover_path)
@ -670,7 +666,7 @@ class Track(Media):
"""
if not self.downloaded:
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
CONV_CLASS = {
@ -687,15 +683,15 @@ class Track(Media):
try:
self.container = codec.upper()
except AttributeError:
secho("Error: No audio codec chosen to convert to.", fg="red")
raise click.Abort
click.secho("Error: No audio codec chosen to convert to.", fg="red")
exit()
if not hasattr(self, "final_path"):
self.format_final_path()
if not os.path.isfile(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
assert (
@ -707,7 +703,6 @@ class Track(Media):
sampling_rate=kwargs.get("sampling_rate"),
remove_source=kwargs.get("remove_source", True),
)
# secho(f"Converting {self!s}", fg="blue")
engine.convert()
self.path = engine.final_fn
self.final_path = self.final_path.replace(
@ -814,7 +809,7 @@ class Video(Media):
:param kwargs:
"""
secho(
click.secho(
f"Downloading {self.title} (Video). This may take a while.",
fg="blue",
)
@ -949,7 +944,7 @@ class YoutubeVideo(Media):
:type youtube_video_downloads_folder: str
: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 = os.path.join(parent_folder, filename_formatter)
@ -969,7 +964,7 @@ class YoutubeVideo(Media):
)
if download_youtube_videos:
secho("Downloading video stream", fg="blue")
click.secho("Downloading video stream", fg="blue")
pv = subprocess.Popen(
[
"youtube-dl",
@ -1107,18 +1102,18 @@ class Tracklist(list):
except (KeyboardInterrupt, SystemExit):
executor.shutdown()
click.echo("Aborted! May take some time to shutdown.")
raise click.Abort
exit()
else:
for item in self:
if self.client.source != "soundcloud":
# soundcloud only gets metadata after `target` is called
# 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:
target(item, **kwargs)
except ItemExists:
secho(f"{item!s} exists. Skipping.", fg="yellow")
click.secho(f"{item!s} exists. Skipping.", fg="yellow")
except NonStreamable as e:
e.print(item)
failed_downloads.append((item.client.source, item.type, item.id))
@ -1264,7 +1259,7 @@ class Tracklist(list):
:rtype: str
"""
secho(
click.secho(
f"\n\nDownloading {self.title} ({self.__class__.__name__})\n",
fg="magenta",
bold=True,
@ -1422,7 +1417,7 @@ class Album(Tracklist, Media):
self.download_message()
# 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")
embed_cover_size = kwargs.get("embed_cover_size", "large")
@ -1446,7 +1441,7 @@ class Album(Tracklist, Media):
cover_size = os.path.getsize(cover_path)
if cover_size > FLAC_MAX_BLOCKSIZE: # 16.77 MB
secho(
click.secho(
"Downgrading embedded cover size, too large ({cover_size}).",
fg="bright_yellow",
)
@ -1473,7 +1468,7 @@ class Album(Tracklist, Media):
and kwargs.get("download_booklets", True)
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:
Booklet(item).download(parent_folder=self.folder)
@ -1783,9 +1778,9 @@ class Playlist(Tracklist, Media):
kwargs["parent_folder"] = self.folder
if self.client.source == "soundcloud":
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.albumartist = self.creator

View file

@ -6,14 +6,12 @@ import base64
import functools
import hashlib
import logging
import os
import re
from collections import OrderedDict
from json import JSONDecodeError
from string import Formatter
from typing import Dict, Hashable, Iterator, Optional, Tuple, Union
import click
from click import secho
import requests
from Cryptodome.Cipher import Blowfish
from pathvalidate import sanitize_filename
@ -240,50 +238,6 @@ def get_stats_from_quality(
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):
"""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.Util import Counter
except (ImportError, ModuleNotFoundError):
secho(
click.secho(
"To download this item in MQA, you need to run ",
fg="yellow",
nl=False,
)
secho("pip3 install pycryptodome --upgrade", fg="blue", nl=False)
secho(".")
raise click.Abort
click.secho("pip3 install pycryptodome --upgrade", fg="blue", nl=False)
click.secho(".")
exit()
# Do not change this
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
screen
"""
secho(message, fg=fg)
click.secho(message, fg=fg)
logger.debug(message)

View file

@ -2,7 +2,7 @@ import os
import shutil
import subprocess
import click
from click import style, secho, echo
test_urls = {
"qobuz": "https://www.qobuz.com/us-en/album/blackest-blue-morcheeba/h4nngz0wgqesc",