diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 1b99b3e..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# See https://pre-commit.com for more information -# See https://pre-commit.com/hooks.html for more hooks -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 - hooks: - - id: check-added-large-files - - repo: https://github.com/pycqa/isort - rev: 5.10.1 - hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - - repo: https://github.com/pycqa/flake8 - rev: 4.0.1 - hooks: - - id: flake8 - additional_dependencies: [ Flake8-pyproject ] - entry: flake8p diff --git a/notes/Speicher.md b/notes/Speicher.md index 2702592..307b9e2 100644 --- a/notes/Speicher.md +++ b/notes/Speicher.md @@ -62,7 +62,6 @@ _ data - Title: str, VARCHAR(200) - Slug: str (YYYYMMDD_Title, used as filename), VARCHAR(209) - Published: datetime -- Downloaded: datetime - Description: str, VARCHAR(1000) ### Config diff --git a/poetry.lock b/poetry.lock index d531411..a04e799 100644 --- a/poetry.lock +++ b/poetry.lock @@ -101,14 +101,6 @@ python-versions = "*" [package.dependencies] pycparser = "*" -[[package]] -name = "cfgv" -version = "3.3.1" -description = "Validate configuration and produce human readable error messages." -category = "dev" -optional = false -python-versions = ">=3.6.1" - [[package]] name = "charset-normalizer" version = "2.0.12" @@ -164,14 +156,6 @@ tomli = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["tomli"] -[[package]] -name = "distlib" -version = "0.3.4" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "feedparser" version = "6.0.8" @@ -183,18 +167,6 @@ python-versions = ">=3.6" [package.dependencies] sgmllib3k = "*" -[[package]] -name = "filelock" -version = "3.6.0" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] - [[package]] name = "font-source-sans-pro" version = "0.0.1" @@ -230,17 +202,6 @@ category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "identify" -version = "2.5.0" -description = "File identification library for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -license = ["ukkonen"] - [[package]] name = "idna" version = "3.3" @@ -319,22 +280,6 @@ category = "main" optional = false python-versions = ">=3.5, <4" -[[package]] -name = "mysqlclient" -version = "2.1.0" -description = "Python interface to MySQL" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "nodeenv" -version = "1.6.0" -description = "Node.js virtual environment builder" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "packaging" version = "21.3" @@ -358,18 +303,6 @@ python-versions = ">=3.7" docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -[[package]] -name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] - [[package]] name = "pluggy" version = "1.0.0" @@ -382,30 +315,6 @@ python-versions = ">=3.6" dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "pre-commit" -version = "2.18.1" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" - -[[package]] -name = "psycopg2" -version = "2.9.3" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "py" version = "1.11.0" @@ -686,14 +595,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - [[package]] name = "tomli" version = "2.0.1" @@ -747,24 +648,6 @@ h11 = ">=0.8" [package.extras] standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] -[[package]] -name = "virtualenv" -version = "20.14.1" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -platformdirs = ">=2,<3" -six = ">=1.9.0,<2" - -[package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] - [[package]] name = "wcag-contrast-ratio" version = "0.9" @@ -814,7 +697,7 @@ websockets = "*" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "ff7d3c1941f088222a945efb832db370bad4daeee7298d08752aa4061f7c4846" +content-hash = "3e8f9ea28feca928ee5e81f883e97e3d4390c859dc6a7850544003aff517dade" [metadata.files] alembic = [ @@ -989,10 +872,6 @@ cffi = [ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, @@ -1052,18 +931,10 @@ coverage = [ {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] -distlib = [ - {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, - {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, -] feedparser = [ {file = "feedparser-6.0.8-py3-none-any.whl", hash = "sha256:1b7f57841d9cf85074deb316ed2c795091a238adb79846bc46dccdaf80f9c59a"}, {file = "feedparser-6.0.8.tar.gz", hash = "sha256:5ce0410a05ab248c8c7cfca3a0ea2203968ee9ff4486067379af4827a59f9661"}, ] -filelock = [ - {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, - {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, -] font-source-sans-pro = [ {file = "font-source-sans-pro-0.0.1.tar.gz", hash = "sha256:3f81d8e52b0d7e930e2c867c0d3ee549312d03f97b71b664a8361006311f72e5"}, {file = "font_source_sans_pro-0.0.1-py2-none-any.whl", hash = "sha256:685c8813d59941e84ea326f46d638871adbc825a0aa5205a72ee9ed9c5fbb471"}, @@ -1135,10 +1006,6 @@ h11 = [ {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, ] -identify = [ - {file = "identify-2.5.0-py2.py3-none-any.whl", hash = "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f"}, - {file = "identify-2.5.0.tar.gz", hash = "sha256:c83af514ea50bf2be2c4a3f2fb349442b59dc87284558ae9ff54191bff3541d2"}, -] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -1209,17 +1076,6 @@ mutagen = [ {file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"}, {file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"}, ] -mysqlclient = [ - {file = "mysqlclient-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:02c8826e6add9b20f4cb12dcf016485f7b1d6e30356a1204d05431867a1b3947"}, - {file = "mysqlclient-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b62d23c11c516cedb887377c8807628c1c65d57593b57853186a6ee18b0c6a5b"}, - {file = "mysqlclient-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2c8410f54492a3d2488a6a53e2d85b7e016751a1e7d116e7aea9c763f59f5e8c"}, - {file = "mysqlclient-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6279263d5a9feca3e0edbc2b2a52c057375bf301d47da2089c075ff76331d14"}, - {file = "mysqlclient-2.1.0.tar.gz", hash = "sha256:973235686f1b720536d417bf0a0d39b4ab3d5086b2b6ad5e6752393428c02b12"}, -] -nodeenv = [ - {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, - {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, -] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -1264,31 +1120,10 @@ pillow = [ {file = "Pillow-9.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458"}, {file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"}, ] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -pre-commit = [ - {file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"}, - {file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"}, -] -psycopg2 = [ - {file = "psycopg2-2.9.3-cp310-cp310-win32.whl", hash = "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362"}, - {file = "psycopg2-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"}, - {file = "psycopg2-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56"}, - {file = "psycopg2-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305"}, - {file = "psycopg2-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2"}, - {file = "psycopg2-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461"}, - {file = "psycopg2-2.9.3-cp38-cp38-win32.whl", hash = "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7"}, - {file = "psycopg2-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf"}, - {file = "psycopg2-2.9.3-cp39-cp39-win32.whl", hash = "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126"}, - {file = "psycopg2-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c"}, - {file = "psycopg2-2.9.3.tar.gz", hash = "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981"}, -] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -1459,10 +1294,6 @@ text-unidecode = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, ] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -1483,10 +1314,6 @@ uvicorn = [ {file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"}, {file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"}, ] -virtualenv = [ - {file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"}, - {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, -] wcag-contrast-ratio = [ {file = "wcag-contrast-ratio-0.9.tar.gz", hash = "sha256:69192b8e5c0a7d0dc5ff1187eeb3e398141633a4bde51c69c87f58fe87ed361c"}, ] diff --git a/pyproject.toml b/pyproject.toml index caeb5da..094413b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,15 +22,11 @@ python-slugify = "^6.1.2" starlette-core = "^0.0.1" click = "^8.1.3" python-dotenv = "^0.20.0" -mysqlclient = "^2.1.0" -psycopg2 = "^2.9.3" [tool.poetry.dev-dependencies] pytest = "^7.1.2" pytest-cov = "^3.0.0" invoke = "^1.7.0" -pre-commit = "^2.18.1" -virtualenv = "20.14.1" [tool.poetry.scripts] ucast = "ucast.__main__:cli" @@ -38,10 +34,3 @@ ucast = "ucast.__main__:cli" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" - -[tool.flake8] -max-line-length = 88 - -[tool.black] -line-length = 88 -target-version = ['py310'] diff --git a/tasks.py b/tasks.py index 287db3c..732e629 100644 --- a/tasks.py +++ b/tasks.py @@ -4,8 +4,8 @@ from tempfile import TemporaryDirectory from invoke import task +from ucast import youtube, util, cover import tests -from ucast import cover, util, youtube os.chdir(Path(__file__).absolute().parent) db_file = Path("_run/ucast.db").absolute() @@ -18,7 +18,7 @@ os.environ["DATABASE_URL"] = f"sqlite:///{db_file}" @task def test(c): - c.run("pytest tests", pty=True) + c.run('pytest tests', pty=True) @task @@ -29,21 +29,21 @@ def run(c): @task -def get_cover(c, vid=""): +def get_cover(c, vid=''): vinfo = youtube.get_video_info(vid) - title = vinfo["fulltitle"] - channel_name = vinfo["uploader"] + title = vinfo['fulltitle'] + channel_name = vinfo['uploader'] thumbnail_url = youtube.get_thumbnail_url(vinfo) - channel_url = vinfo["channel_url"] + channel_url = vinfo['channel_url'] channel_metadata = youtube.get_channel_metadata(channel_url) ti = 1 - while os.path.exists(tests.DIR_TESTFILES / "cover" / f"c{ti}.png"): + while os.path.exists(tests.DIR_TESTFILES / 'cover' / f'c{ti}.png'): ti += 1 - tn_file = tests.DIR_TESTFILES / "thumbnail" / f"t{ti}.webp" - av_file = tests.DIR_TESTFILES / "avatar" / f"a{ti}.jpg" - cv_file = tests.DIR_TESTFILES / "cover" / f"c{ti}.png" + tn_file = tests.DIR_TESTFILES / 'thumbnail' / f't{ti}.webp' + av_file = tests.DIR_TESTFILES / 'avatar' / f'a{ti}.jpg' + cv_file = tests.DIR_TESTFILES / 'cover' / f'c{ti}.png' util.download_file(thumbnail_url, tn_file) util.download_file(channel_metadata.avatar_url, av_file) @@ -52,7 +52,7 @@ def get_cover(c, vid=""): @task -def add_migration(c, m=""): +def add_migration(c, m=''): if not m: raise Exception("please input migration name") @@ -65,4 +65,4 @@ def add_migration(c, m=""): os.chdir("ucast") c.run("alembic upgrade head") - c.run(f"alembic revision --autogenerate -m '{m}'") + c.run(f"alembic revision --autogenerate -m {m}") diff --git a/tests/__init__.py b/tests/__init__.py index 8b21d67..3a4d86b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ # coding=utf-8 from importlib.resources import files -DIR_TESTFILES = files("tests.testfiles") +DIR_TESTFILES = files('tests.testfiles') diff --git a/tests/test_cover.py b/tests/test_cover.py index 386e4bf..508f152 100644 --- a/tests/test_cover.py +++ b/tests/test_cover.py @@ -1,89 +1,60 @@ # coding=utf-8 +from typing import List import tempfile from pathlib import Path -from typing import List import pytest +from PIL import Image, ImageFont, ImageChops from fonts.ttf import SourceSansPro -from PIL import Image, ImageChops, ImageFont import tests from ucast import cover, typ -@pytest.mark.parametrize( - "height,width,text,expect", - [ - (40, 300, "Hello", ["Hello"]), - (40, 300, "Hello World, this is me", ["Hello World,…"]), - (90, 300, "Hello World, this is me", ["Hello World, this", "is me"]), - ( - 90, - 300, - "Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz", - ["Rindfleischettik…"], - ), - ( - 1000, - 300, - "Ha! du wärst Obrigkeit von Gott? Gott spendet Segen aus; du raubst! \ -Du nicht von Gott, Tyrann!", - [ - "Ha! du wärst", - "Obrigkeit von", - "Gott? Gott", - "spendet Segen", - "aus; du raubst!", - "Du nicht von Gott,", - "Tyrann!", - ], - ), - ], -) +@pytest.mark.parametrize('height,width,text,expect', [ + (40, 300, 'Hello', ['Hello']), + (40, 300, 'Hello World, this is me', ['Hello World,…']), + (90, 300, 'Hello World, this is me', ['Hello World, this', 'is me']), + (90, 300, 'Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz', ['Rindfleischettik…']), + (1000, 300, 'Ha! du wärst Obrigkeit von Gott? Gott spendet Segen aus; du raubst! Du nicht von Gott, Tyrann!', + ['Ha! du wärst', 'Obrigkeit von', 'Gott? Gott', 'spendet Segen', 'aus; du raubst!', 'Du nicht von Gott,', + 'Tyrann!']), +]) def test_split_text(height: int, width: int, text: str, expect: List[str]): font = ImageFont.truetype(SourceSansPro, 40) lines = cover._split_text(height, width, text, font, 8) assert lines == expect -@pytest.mark.parametrize( - "file_name,color", - [ - ("t1.webp", (63, 63, 62)), - ("t2.webp", (74, 45, 37)), - ("t3.webp", (54, 24, 28)), - ], -) +@pytest.mark.parametrize('file_name,color', [ + ('t1.webp', (63, 63, 62)), + ('t2.webp', (74, 45, 37)), + ('t3.webp', (54, 24, 28)), +]) def test_get_dominant_color(file_name: str, color: typ.Color): - img = Image.open(tests.DIR_TESTFILES / "thumbnail" / file_name) + img = Image.open(tests.DIR_TESTFILES / 'thumbnail' / file_name) c = cover._get_dominant_color(img) assert c == color -@pytest.mark.parametrize( - "bg_color,text_color", - [ - ((100, 0, 0), (255, 255, 255)), - ((200, 200, 0), (0, 0, 0)), - ], -) +@pytest.mark.parametrize('bg_color,text_color', [ + ((100, 0, 0), (255, 255, 255)), + ((200, 200, 0), (0, 0, 0)), +]) def test_get_text_color(bg_color: typ.Color, text_color: typ.Color): c = cover._get_text_color(bg_color) assert c == text_color -@pytest.mark.parametrize( - "n_image,title,channel", - [ - (1, "ThetaDev @ Embedded World 2019", "ThetaDev"), - (2, "Sintel - Open Movie by Blender Foundation", "Blender"), - (3, "Systemabsturz Teaser zur DiVOC bb3", "media.ccc.de"), - ], -) +@pytest.mark.parametrize('n_image,title,channel', [ + (1, 'ThetaDev @ Embedded World 2019', 'ThetaDev'), + (2, 'Sintel - Open Movie by Blender Foundation', 'Blender'), + (3, 'Systemabsturz Teaser zur DiVOC bb3', 'media.ccc.de'), +]) def test_create_cover_image(n_image: int, title: str, channel: str): - tn_file = tests.DIR_TESTFILES / "thumbnail" / f"t{n_image}.webp" - av_file = tests.DIR_TESTFILES / "avatar" / f"a{n_image}.jpg" - expected_cv_file = tests.DIR_TESTFILES / "cover" / f"c{n_image}.png" + tn_file = tests.DIR_TESTFILES / 'thumbnail' / f't{n_image}.webp' + av_file = tests.DIR_TESTFILES / 'avatar' / f'a{n_image}.jpg' + expected_cv_file = tests.DIR_TESTFILES / 'cover' / f'c{n_image}.png' tn_image = Image.open(tn_file) av_image = Image.open(av_file) @@ -96,17 +67,15 @@ def test_create_cover_image(n_image: int, title: str, channel: str): def test_create_cover_file(): - tn_file = tests.DIR_TESTFILES / "thumbnail" / "t1.webp" - av_file = tests.DIR_TESTFILES / "avatar" / "a1.jpg" - expected_cv_file = tests.DIR_TESTFILES / "cover" / "c1.png" + tn_file = tests.DIR_TESTFILES / 'thumbnail' / 't1.webp' + av_file = tests.DIR_TESTFILES / 'avatar' / 'a1.jpg' + expected_cv_file = tests.DIR_TESTFILES / 'cover' / 'c1.png' tmpdir_o = tempfile.TemporaryDirectory() tmpdir = Path(tmpdir_o.name) - cv_file = tmpdir / "cover.png" + cv_file = tmpdir / 'cover.png' - cover.create_cover_file( - tn_file, av_file, "ThetaDev @ Embedded World 2019", "ThetaDev", cv_file - ) + cover.create_cover_file(tn_file, av_file, 'ThetaDev @ Embedded World 2019', 'ThetaDev', cv_file) cv_image = Image.open(cv_file) expected_cv_image = Image.open(expected_cv_file) diff --git a/tests/test_database.py b/tests/test_database.py deleted file mode 100644 index d8977c8..0000000 --- a/tests/test_database.py +++ /dev/null @@ -1,86 +0,0 @@ -# coding=utf-8 -import os -from datetime import datetime - -import pytest -import sqlalchemy -from sqlalchemy import orm - -# from ucast import models -from ucast import db - - -def test_insert_channel(testdb): - c1 = db.models.Channel(id="UCE1PLliRk3urTjDG6kGByiA", name="Natalie Gold") - session.add(c1) - session.commit() - - c2 = db.models.Channel(id="UCGiJh0NZ52wRhYKYnuZI08Q", name="ThetaDev") - - session.add(c2) - session.commit() - - # stmt = sqlalchemy.select(models.Channel).where(models.Channel.name == "LinusTechTips") - # stmt = sqlalchemy.select(models.Channel) - # res = testdb.execute(stmt) - - res = session.query(models.Channel).all() - assert len(res) == 2 - assert res[0].name == "Natalie Gold" - assert res[1].name == "ThetaDev" - - -""" -@pytest.fixture(scope="session", autouse=True) -def testdb(): - try: - os.remove("test.db") - except: - pass - # url = "sqlite:///:memory:" - url = "sqlite:///test.db" - engine = sqlalchemy.create_engine(url) - models.metadata.create_all(engine) # Create the tables. - return engine - - -def test_insert_channel(testdb): - session_maker = orm.sessionmaker(bind=testdb) - session = session_maker() - c1 = models.Channel(id="UCE1PLliRk3urTjDG6kGByiA", name="Natalie Gold") - session.add(c1) - session.commit() - - c2 = models.Channel(id="UCGiJh0NZ52wRhYKYnuZI08Q", name="ThetaDev") - - session.add(c2) - session.commit() - - # stmt = sqlalchemy.select(models.Channel).where(models.Channel.name == "LinusTechTips") - # stmt = sqlalchemy.select(models.Channel) - # res = testdb.execute(stmt) - - res = session.query(models.Channel).all() - assert len(res) == 2 - assert res[0].name == "Natalie Gold" - assert res[1].name == "ThetaDev" - - -def test_insert_video(testdb): - session_maker = orm.sessionmaker(bind=testdb) - session = session_maker() - - c1 = models.Channel(id="UC0QEucPrn0-Ddi3JBTcs5Kw", name="Saria Delaney") - session.add(c1) - session.commit() - - v1 = models.Video(id="Bxhxzj8R_i0", - channel=c1, - title="Verschwiegen. Verraten. Verstummt. [Reupload: 10.10.2018]", - published=datetime(2020, 7, 4, 12, 21, 30), - description="") - v1.slug = v1.get_slug() - - session.add(v1) - session.commit() -""" diff --git a/ucast/__init__.py b/ucast/__init__.py index 464fe19..5a388d7 100644 --- a/ucast/__init__.py +++ b/ucast/__init__.py @@ -3,5 +3,5 @@ __version__ = "0.0.1" UCAST_BANNER = """\ ┬ ┬┌─┐┌─┐┌─┐┌┬┐ - │ ││ ├─┤└─┐ │ + │ ││ ├─┤└─┐ │ └─┘└─┘┴ ┴└─┘ ┴ """ diff --git a/ucast/__main__.py b/ucast/__main__.py index 302942c..2afbf88 100644 --- a/ucast/__main__.py +++ b/ucast/__main__.py @@ -1,11 +1,11 @@ import os import sys -from importlib import resources -from pathlib import Path - -import dotenv -import uvicorn from alembic import config as alembic_cmd +from pathlib import Path +from importlib import resources + +import uvicorn +import dotenv import ucast @@ -24,36 +24,29 @@ def print_banner(): def print_help(): print_banner() - print( - """ + print(""" Available commands: run: start the server migrate: apply database migrations alembic: run the alembic migrator Configuration is read from the .env file or environment variables. -Refer to the project page for more information: https://code.thetadev.de/HSA/Ucast""" - ) +Refer to the project page for more information: https://code.thetadev.de/HSA/Ucast""") def run(): print_banner() load_dotenv() from ucast import config - - uvicorn.run( - "ucast.app:create_app", - host="0.0.0.0", - port=config.HTTP_PORT, - factory=True, - reload=config.DEBUG, - ) + uvicorn.run('ucast.app:create_app', + host="0.0.0.0", port=config.HTTP_PORT, factory=True, reload=config.DEBUG) def alembic(args): load_dotenv() alembic_ini_path = resources.path("ucast", "alembic.ini") os.environ["ALEMBIC_CONFIG"] = str(alembic_ini_path) + os.chdir(alembic_ini_path.parent) alembic_cmd.main(args, f"{sys.argv[0]} alembic") diff --git a/ucast/alembic.ini b/ucast/alembic.ini index fabed10..ae6b667 100644 --- a/ucast/alembic.ini +++ b/ucast/alembic.ini @@ -2,7 +2,7 @@ [alembic] # path to migration scripts -script_location = ucast:migrations +script_location = migrations # template used to generate migration files file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s diff --git a/ucast/app.py b/ucast/app.py index 17ac6c1..2bd1b40 100644 --- a/ucast/app.py +++ b/ucast/app.py @@ -2,17 +2,14 @@ from starlette.applications import Starlette from starlette.routing import Route -from ucast import config, views +from ucast import views, config def create_app(): - app = Starlette( - config.DEBUG, - routes=[ - Route("/", views.homepage), - Route("/err", views.error), - ], - ) + app = Starlette(config.DEBUG, routes=[ + Route("/", views.homepage), + Route("/err", views.error), + ]) if app.debug: print("Debug mode enabled.") diff --git a/ucast/config.py b/ucast/config.py index bbc434a..900bcb2 100644 --- a/ucast/config.py +++ b/ucast/config.py @@ -6,7 +6,7 @@ from starlette_core.database import DatabaseURL config = Config() # Basic configuration -DEBUG = config("DEBUG", cast=bool, default=False) -DATABASE_URL = config("DATABASE_URL", cast=DatabaseURL) -SECRET_KEY = config("SECRET_KEY", cast=Secret) -HTTP_PORT = config("HTTP_PORT", cast=int, default=8000) +DEBUG = config('DEBUG', cast=bool, default=False) +DATABASE_URL = config('DATABASE_URL', cast=DatabaseURL) +SECRET_KEY = config('SECRET_KEY', cast=Secret) +HTTP_PORT = config('HTTP_PORT', cast=int, default=8000) diff --git a/ucast/cover.py b/ucast/cover.py index 7bd0e2f..dbe6e83 100644 --- a/ucast/cover.py +++ b/ucast/cover.py @@ -1,43 +1,41 @@ # coding=utf-8 import math from pathlib import Path -from typing import List, Optional, Tuple +from typing import Tuple, List, Optional -import wcag_contrast_ratio -from colorthief import ColorThief -from fonts.ttf import SourceSansPro from PIL import Image, ImageDraw, ImageFont +from colorthief import ColorThief +import wcag_contrast_ratio +from fonts.ttf import SourceSansPro from ucast import typ -CHAR_ELLIPSIS = "…" +CHAR_ELLIPSIS = '…' COVER_WIDTH = 500 -def _split_text( - height: int, width: int, text: str, font: ImageFont.FreeTypeFont, line_spacing=0 -) -> List[str]: +def _split_text(height: int, width: int, text: str, font: ImageFont.FreeTypeFont, line_spacing=0) -> List[str]: if height < font.size: return [] max_lines = math.floor((height - font.size) / (font.size + line_spacing)) + 1 lines = [] - line = "" + line = '' - for word in text.split(" "): + for word in text.split(' '): if len(lines) >= max_lines: line = word break - if line == "": + if line == '': nline = word else: - nline = line + " " + word + nline = line + ' ' + word if font.getsize(nline)[0] <= width: line = nline - elif line != "": + elif line != '': lines.append(line) line = word else: @@ -49,14 +47,14 @@ def _split_text( lines.append(nline_e) break - if line != "": + if line != '': if len(lines) >= max_lines: # Drop the last line and add ... to the end lastline = lines[-1] + CHAR_ELLIPSIS if font.getsize(lastline)[0] <= width: lines[-1] = lastline else: - i_last_space = lines[-1].rfind(" ") + i_last_space = lines[-1].rfind(' ') lines[-1] = lines[-1][:i_last_space] + CHAR_ELLIPSIS else: lines.append(line) @@ -64,15 +62,8 @@ def _split_text( return lines -def _draw_text_box( - draw: ImageDraw.ImageDraw, - box: Tuple[int, int, int, int], - text: str, - font: ImageFont.FreeTypeFont, - color: typ.Color = (0, 0, 0), - line_spacing=0, - vertical_center=True, -): +def _draw_text_box(draw: ImageDraw.ImageDraw, box: Tuple[int, int, int, int], text: str, font: ImageFont.FreeTypeFont, + color: typ.Color = (0, 0, 0), line_spacing=0, vertical_center=True): x_tl, y_tl, x_br, y_br = box height = y_br - y_tl width = x_br - x_tl @@ -110,9 +101,7 @@ def _get_text_color(bg_color) -> typ.Color: return 0, 0, 0 -def _create_cover_image( - thumbnail: Image.Image, avatar: Optional[Image.Image], title: str, channel: str -) -> Image.Image: +def _create_cover_image(thumbnail: Image.Image, avatar: Optional[Image.Image], title: str, channel: str) -> Image.Image: # Scale the thumbnail image down to cover size tn_height = int(COVER_WIDTH / thumbnail.width * thumbnail.height) tn = thumbnail.resize((COVER_WIDTH, tn_height), Image.Resampling.LANCZOS) @@ -124,13 +113,11 @@ def _create_cover_image( bottom_color = _get_dominant_color(bottom_part) # Create new cover image - cover = Image.new("RGB", (COVER_WIDTH, COVER_WIDTH)) + cover = Image.new('RGB', (COVER_WIDTH, COVER_WIDTH)) cover_draw = ImageDraw.Draw(cover) # Draw background gradient - for i, color in enumerate( - _interpolate_color(top_color, bottom_color, cover.height) - ): + for i, color in enumerate(_interpolate_color(top_color, bottom_color, cover.height)): cover_draw.line(((0, i), (cover.width, i)), tuple(color), 1) # Insert thumbnail image in the middle @@ -147,7 +134,7 @@ def _create_cover_image( avt = avatar.resize((avt_size, avt_size), Image.Resampling.LANCZOS) - circle_mask = Image.new("L", (avt_size, avt_size)) + circle_mask = Image.new('L', (avt_size, avt_size)) circle_mask_draw = ImageDraw.Draw(circle_mask) circle_mask_draw.ellipse((0, 0, avt_size, avt_size), 255) @@ -163,43 +150,18 @@ def _create_cover_image( top_text_color = _get_text_color(top_color) bottom_text_color = _get_text_color(bottom_color) - _draw_text_box( - cover_draw, - ( - text_margin_topleft, - text_vertical_offset, - COVER_WIDTH - text_margin_x, - tn_margin, - ), - channel, - fnt, - top_text_color, - text_line_space, - ) - _draw_text_box( - cover_draw, - ( - text_margin_x, - COVER_WIDTH - tn_margin + text_vertical_offset, - COVER_WIDTH - text_margin_x, - COVER_WIDTH, - ), - title, - fnt, - bottom_text_color, - text_line_space, - ) + _draw_text_box(cover_draw, (text_margin_topleft, text_vertical_offset, COVER_WIDTH - text_margin_x, tn_margin), + channel, + fnt, top_text_color, text_line_space) + _draw_text_box(cover_draw, + (text_margin_x, COVER_WIDTH - tn_margin + text_vertical_offset, + COVER_WIDTH - text_margin_x, COVER_WIDTH), title, fnt, bottom_text_color, text_line_space) return cover -def create_cover_file( - thumbnail_path: Path, - avatar_path: Optional[Path], - title: str, - channel: str, - cover_path: Path, -): +def create_cover_file(thumbnail_path: Path, avatar_path: Optional[Path], title: str, channel: str, + cover_path: Path): thumbnail = Image.open(thumbnail_path) avatar = None diff --git a/ucast/db.py b/ucast/db.py index 59165a6..4a86b93 100644 --- a/ucast/db.py +++ b/ucast/db.py @@ -1,8 +1,8 @@ # coding=utf-8 -from starlette_core.database import Database, metadata # noqa: F401 +from starlette_core.database import Database, metadata -from ucast import models # noqa: F401 from ucast.config import DATABASE_URL +from ucast import models # set db config options if DATABASE_URL.driver == "psycopg2": diff --git a/ucast/migrations/env.py b/ucast/migrations/env.py index 70b6d51..114f14f 100644 --- a/ucast/migrations/env.py +++ b/ucast/migrations/env.py @@ -1,7 +1,8 @@ from logging.config import fileConfig +from sqlalchemy import engine_from_config +from sqlalchemy import pool from alembic import context -from sqlalchemy import engine_from_config, pool from ucast import db @@ -9,7 +10,7 @@ from ucast import db # access to the values within the .ini file in use. config = context.config -config.set_main_option("sqlalchemy.url", str(db.DATABASE_URL)) +config.set_main_option('sqlalchemy.url', str(db.DATABASE_URL)) target_metadata = db.metadata # Interpret the config file for Python logging. @@ -62,7 +63,9 @@ def run_migrations_online(): ) with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) + context.configure( + connection=connection, target_metadata=target_metadata + ) with context.begin_transaction(): context.run_migrations() diff --git a/ucast/migrations/versions/2022-05-03_0ae786127cd8_initial_revision.py b/ucast/migrations/versions/2022-05-03_0ae786127cd8_initial_revision.py index 5e7aa7f..ef2e078 100644 --- a/ucast/migrations/versions/2022-05-03_0ae786127cd8_initial_revision.py +++ b/ucast/migrations/versions/2022-05-03_0ae786127cd8_initial_revision.py @@ -1,15 +1,16 @@ """Initial revision Revision ID: 0ae786127cd8 -Revises: +Revises: Create Date: 2022-05-03 10:03:42.224721 """ -import sqlalchemy as sa from alembic import op +import sqlalchemy as sa + # revision identifiers, used by Alembic. -revision = "0ae786127cd8" +revision = '0ae786127cd8' down_revision = None branch_labels = None depends_on = None @@ -17,36 +18,30 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "channels", - sa.Column("id", sa.String(length=30), nullable=False), - sa.Column("name", sa.Unicode(length=100), nullable=False), - sa.Column("active", sa.Boolean(), nullable=False), - sa.Column("skip_livestreams", sa.Boolean(), nullable=False), - sa.Column("skip_shorts", sa.Boolean(), nullable=False), - sa.Column("keep_videos", sa.Integer(), nullable=True), - sa.PrimaryKeyConstraint("id"), + op.create_table('channels', + sa.Column('id', sa.String(length=30), nullable=False), + sa.Column('name', sa.Unicode(length=100), nullable=False), + sa.Column('active', sa.Boolean(), nullable=False), + sa.Column('skip_livestreams', sa.Boolean(), nullable=False), + sa.Column('skip_shorts', sa.Boolean(), nullable=False), + sa.Column('keep_videos', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('id') ) - op.create_table( - "videos", - sa.Column("id", sa.String(length=30), nullable=False), - sa.Column("channel_id", sa.String(length=30), nullable=False), - sa.Column("title", sa.Unicode(length=200), nullable=False), - sa.Column("slug", sa.String(length=209), nullable=False), - sa.Column("published", sa.DateTime(), nullable=False), - sa.Column("downloaded", sa.DateTime(), nullable=True), - sa.Column("description", sa.UnicodeText(), nullable=False), - sa.ForeignKeyConstraint( - ["channel_id"], - ["channels.id"], - ), - sa.PrimaryKeyConstraint("id"), + op.create_table('videos', + sa.Column('id', sa.String(length=30), nullable=False), + sa.Column('channel_id', sa.String(length=30), nullable=False), + sa.Column('title', sa.Unicode(length=200), nullable=False), + sa.Column('slug', sa.String(length=209), nullable=False), + sa.Column('published', sa.DateTime(), nullable=False), + sa.Column('description', sa.UnicodeText(), nullable=False), + sa.ForeignKeyConstraint(['channel_id'], ['channels.id'], ), + sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("videos") - op.drop_table("channels") + op.drop_table('videos') + op.drop_table('channels') # ### end Alembic commands ### diff --git a/ucast/model.py b/ucast/model.py new file mode 100644 index 0000000..87062c3 --- /dev/null +++ b/ucast/model.py @@ -0,0 +1,3 @@ +# coding=utf-8 + + diff --git a/ucast/models.py b/ucast/models.py index 3921d64..b118067 100644 --- a/ucast/models.py +++ b/ucast/models.py @@ -1,8 +1,9 @@ # coding=utf-8 -import slugify import sqlalchemy as sa from sqlalchemy import orm from starlette_core.database import Base +import slugify + # metadata = sa.MetaData() # Base = declarative_base(metadata=metadata) @@ -29,7 +30,6 @@ class Video(Base): title = sa.Column(sa.Unicode(200), nullable=False) slug = sa.Column(sa.String(209), nullable=False) published = sa.Column(sa.DateTime, nullable=False) - downloaded = sa.Column(sa.DateTime, nullable=True) description = sa.Column(sa.UnicodeText(), nullable=False, default="") def get_slug(self) -> str: diff --git a/ucast/storage.py b/ucast/storage.py index ccdb2af..266aca2 100644 --- a/ucast/storage.py +++ b/ucast/storage.py @@ -2,7 +2,7 @@ import os from pathlib import Path -UCAST_DIRNAME = ".ucast" +UCAST_DIRNAME = '.ucast' class ChannelFolder: @@ -10,11 +10,11 @@ class ChannelFolder: self.dir_root = dir_root dir_ucast = self.dir_root / UCAST_DIRNAME - self.file_videos = dir_ucast / "videos.json" - self.file_options = dir_ucast / "options.json" - self.file_avatar = dir_ucast / "avatar.png" - self.file_feed = dir_ucast / "feed.xml" - self.dir_covers = dir_ucast / "covers" + self.file_videos = dir_ucast / 'videos.json' + self.file_options = dir_ucast / 'options.json' + self.file_avatar = dir_ucast / 'avatar.png' + self.file_feed = dir_ucast / 'feed.xml' + self.dir_covers = dir_ucast / 'covers' def does_exist(self) -> bool: return os.path.isdir(self.dir_covers) @@ -31,14 +31,14 @@ class Storage: def get_channel_folder(self, channel_name: str): cf = ChannelFolder(self.dir_data / channel_name) if not cf.does_exist(): - raise FileNotFoundError("channel folder does not exist") + raise FileNotFoundError('channel folder does not exist') return cf def create_channel_folder(self, channel_name: str): cf = ChannelFolder(self.dir_data / channel_name) if cf.does_exist(): - raise FileExistsError("channel folder already exists") + raise FileExistsError('channel folder already exists') cf.create() return cf diff --git a/ucast/util.py b/ucast/util.py index aef2c75..1654094 100644 --- a/ucast/util.py +++ b/ucast/util.py @@ -1,9 +1,8 @@ # coding=utf-8 -from pathlib import Path - import requests +from pathlib import Path def download_file(url: str, download_path: Path): r = requests.get(url, allow_redirects=True) - open(download_path, "wb").write(r.content) + open(download_path, 'wb').write(r.content) diff --git a/ucast/views.py b/ucast/views.py index c018e9f..c892687 100644 --- a/ucast/views.py +++ b/ucast/views.py @@ -2,7 +2,7 @@ from starlette.requests import Request from starlette.responses import Response -# from ucast import db +from ucast import db async def homepage(request: Request) -> Response: diff --git a/ucast/youtube.py b/ucast/youtube.py index 8e56d79..ac68a6b 100644 --- a/ucast/youtube.py +++ b/ucast/youtube.py @@ -1,16 +1,16 @@ # coding=utf-8 +from operator import itemgetter import json from dataclasses import dataclass -from operator import itemgetter -import requests -from scrapetube import scrapetube from yt_dlp import YoutubeDL +from scrapetube import scrapetube +import requests def get_thumbnail_url(vinfo): """Get the best quality thumbnail""" - return max(vinfo["thumbnails"], key=itemgetter("preference"))["url"] + return max(vinfo['thumbnails'], key=itemgetter('preference'))['url'] def get_video_info(video_id): @@ -20,25 +20,29 @@ def get_video_info(video_id): def download_video(video_id, download_path, sponsorblock=False): ydl_params = { - "format": "bestaudio", - "postprocessors": [ - {"key": "FFmpegExtractAudio", "preferredcodec": "mp3"}, + 'format': 'bestaudio', + 'postprocessors': [ + { + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3' + }, ], - "outtmpl": download_path, + 'outtmpl': download_path, } if sponsorblock: # noinspection PyTypeChecker - ydl_params["postprocessors"].extend( - [ - { - "key": "SponsorBlock", - "categories": ["sponsor"], - "when": "after_filter", - }, - {"key": "ModifyChapters", "remove_sponsor_segments": ["sponsor"]}, - ] - ) + ydl_params['postprocessors'].extend([ + { + 'key': 'SponsorBlock', + 'categories': ['sponsor'], + 'when': 'after_filter' + }, + { + 'key': 'ModifyChapters', + 'remove_sponsor_segments': ['sponsor'] + } + ]) with YoutubeDL(ydl_params) as ydl: # extract_info downloads the video and returns its metadata @@ -57,8 +61,7 @@ def get_channel_metadata(channel_url): session = requests.Session() session.headers[ "User-Agent" - ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ -(KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36" + ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36" url = f"{channel_url}/videos?view=0&flow=grid" @@ -66,11 +69,11 @@ def get_channel_metadata(channel_url): data = json.loads( scrapetube.get_json_from_html(html, "var ytInitialData = ", 0, "};") + "}" ) - metadata = data["metadata"]["channelMetadataRenderer"] + metadata = data['metadata']['channelMetadataRenderer'] - channel_id = metadata["externalId"] - name = metadata["title"] - description = metadata["description"] - avatar = metadata["avatar"]["thumbnails"][0]["url"] + channel_id = metadata['externalId'] + name = metadata['title'] + description = metadata['description'] + avatar = metadata['avatar']['thumbnails'][0]['url'] return ChannelMetadata(channel_id, name, description, avatar)