From e1315743931f43f9649f4f46b54d54cd95e8f783 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 28 Apr 2022 11:47:38 +0200 Subject: [PATCH 1/5] add storage.py --- notes/Speicher.md | 7 +------ ucast/cover.py | 5 +++-- ucast/model.py | 3 +++ ucast/storage.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ ucast/types.py | 1 - 5 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 ucast/model.py create mode 100644 ucast/storage.py diff --git a/notes/Speicher.md b/notes/Speicher.md index 503587c..f2222cd 100644 --- a/notes/Speicher.md +++ b/notes/Speicher.md @@ -9,7 +9,7 @@ _ data |_ LinusTechTips |_ .ucast |_ videos.json # IDs und Metadaten aller heruntergeladenen Videos - |_ options.json # Kanalspezifische Optionen (ID, LastScan) + |_ options.json # Kanalspezifische Optionen (ID, enabled) |_ avatar.png # Profilbild des Kanals |_ feed.xml # RSS-Feed |_ covers # Cover-Bilder @@ -24,15 +24,10 @@ _ data ## Datenmodelle -### LastScan - -- LastScan: datetime - ### ChannelOptions - ID: str - Active: bool = True -- LastScan: datetime - SkipLivestreams: bool = True - SkipShorts: bool = True - KeepVideos: int = -1 diff --git a/ucast/cover.py b/ucast/cover.py index e4a4aa7..7dd523b 100644 --- a/ucast/cover.py +++ b/ucast/cover.py @@ -1,5 +1,6 @@ # coding=utf-8 import math +from pathlib import Path from typing import Tuple, List, Optional from PIL import Image, ImageDraw, ImageFont @@ -159,8 +160,8 @@ def _create_cover_image(thumbnail: Image.Image, avatar: Optional[Image.Image], t return cover -def create_cover_file(thumbnail_path: types.Path, avatar_path: Optional[types.Path], title: str, channel: str, - cover_path: types.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/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/storage.py b/ucast/storage.py new file mode 100644 index 0000000..266aca2 --- /dev/null +++ b/ucast/storage.py @@ -0,0 +1,44 @@ +# coding=utf-8 +import os +from pathlib import Path + +UCAST_DIRNAME = '.ucast' + + +class ChannelFolder: + def __init__(self, dir_root: Path): + 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' + + def does_exist(self) -> bool: + return os.path.isdir(self.dir_covers) + + def create(self): + os.makedirs(self.dir_covers, exist_ok=True) + + +class Storage: + def __init__(self, config_dir: Path, data_dir: Path): + self.dir_config = config_dir + self.dir_data = data_dir + + 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') + + 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') + + cf.create() + return cf diff --git a/ucast/types.py b/ucast/types.py index 2b54726..99876b6 100644 --- a/ucast/types.py +++ b/ucast/types.py @@ -3,4 +3,3 @@ from os import PathLike from typing import Tuple, Union Color = Tuple[int, int, int] -Path = Union[str, bytes, PathLike] From a4cb3440917dca3e1f770bf94ebd67d60aef9d98 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 30 Apr 2022 01:42:33 +0200 Subject: [PATCH 2/5] refactored Flask structure --- notes/Speicher.md | 61 +++++--- poetry.lock | 362 +++++++++++++++++++++++++++++++++++++--------- pyproject.toml | 7 +- ucast/__init__.py | 37 +---- ucast/app.py | 24 ++- ucast/views.py | 14 ++ ucast/wsgi.py | 4 + 7 files changed, 375 insertions(+), 134 deletions(-) create mode 100644 ucast/views.py create mode 100644 ucast/wsgi.py diff --git a/notes/Speicher.md b/notes/Speicher.md index f2222cd..7c80cb1 100644 --- a/notes/Speicher.md +++ b/notes/Speicher.md @@ -4,45 +4,64 @@ ```txt _ config - |_ config.toml + |_ config.py _ data |_ LinusTechTips - |_ .ucast - |_ videos.json # IDs und Metadaten aller heruntergeladenen Videos - |_ options.json # Kanalspezifische Optionen (ID, enabled) - |_ avatar.png # Profilbild des Kanals - |_ feed.xml # RSS-Feed - |_ covers # Cover-Bilder - |_ 220409_Building a _1_000_000 Computer.png - |_ 220410_Apple makes GREAT Gaming Computers.png - |_ 220409_Building a _1_000_000 Computer.mp3 - |_ 220410_Apple makes GREAT Gaming Computers.mp3 + | |_ .ucast + | | |_ videos.json # IDs und Metadaten aller heruntergeladenen Videos + | | |_ options.json # Kanalspezifische Optionen (ID, enabled) + | | |_ avatar.png # Profilbild des Kanals + | | |_ feed.xml # RSS-Feed + | | |_ covers # Cover-Bilder + | | |_ 220409_Building a _1_000_000 Computer.png + | | |_ 220410_Apple makes GREAT Gaming Computers.png + | |_ 220409_Building a _1_000_000 Computer.mp3 + | |_ 220410_Apple makes GREAT Gaming Computers.mp3 + | + |_ Andreas Spiess + |_ ... +``` +## Verzeichnisstruktur (mit Datenbank) + +```txt +_ config + |_ config.py +_ data + |_ ucast.db + | + |_ LinusTechTips + | |_ .ucast + | | |_ avatar.png # Profilbild des Kanals + | | |_ feed.xml # RSS-Feed + | | |_ covers # Cover-Bilder + | | |_ 220409_Building a _1_000_000 Computer.png + | | |_ 220410_Apple makes GREAT Gaming Computers.png + | |_ 220409_Building a _1_000_000 Computer.mp3 + | |_ 220410_Apple makes GREAT Gaming Computers.mp3 + | |_ Andreas Spiess |_ ... ``` ## Datenmodelle -### ChannelOptions +### Channel -- ID: str +- ID: str, VARCHAR(30), PKEY +- Name: str, VARCHAR(100) - Active: bool = True - SkipLivestreams: bool = True - SkipShorts: bool = True - KeepVideos: int = -1 -### Videos - -- Videos: dict[id: str -> Video] - ### Video -- ID: str -- Title: str -- Slug: str (YYMMDD_Title, used as filename) +- ID: str, VARCHAR(30), PKEY +- Title: str, VARCHAR(200) +- Slug: str (YYMMDD_Title, used as filename), VARCHAR(207) - Published: datetime -- Description: str +- Description: str, VARCHAR(1000) ### Config diff --git a/poetry.lock b/poetry.lock index 7909b86..17079c2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,18 @@ +[[package]] +name = "alembic" +version = "1.7.7" +description = "A database migration tool for SQLAlchemy." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" + +[package.extras] +tz = ["python-dateutil"] + [[package]] name = "atomicwrites" version = "1.4.0" @@ -71,7 +86,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.1.2" +version = "8.1.3" description = "Composable command line interface toolkit" category = "main" optional = false @@ -126,7 +141,7 @@ sgmllib3k = "*" [[package]] name = "flask" -version = "2.1.1" +version = "2.1.2" description = "A simple framework for building complex web applications." category = "main" optional = false @@ -142,6 +157,32 @@ Werkzeug = ">=2.0" async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] +[[package]] +name = "flask-alembic" +version = "2.0.1" +description = "Flask extension to integrate Alembic migrations" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +alembic = ">=0.8" +Flask = ">=0.10" +Flask-SQLAlchemy = "*" +SQLAlchemy = "*" + +[[package]] +name = "flask-sqlalchemy" +version = "2.5.1" +description = "Adds SQLAlchemy support to your Flask application." +category = "main" +optional = false +python-versions = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*" + +[package.dependencies] +Flask = ">=0.10" +SQLAlchemy = ">=0.8.0" + [[package]] name = "font-source-sans-pro" version = "0.0.1" @@ -158,6 +199,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "greenlet" +version = "1.1.2" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["sphinx"] + [[package]] name = "idna" version = "3.3" @@ -192,7 +244,7 @@ python-versions = ">=3.7" [[package]] name = "jinja2" -version = "3.1.1" +version = "3.1.2" description = "A very fast and expressive template engine." category = "main" optional = false @@ -204,6 +256,22 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "mako" +version = "1.2.0" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["babel"] +lingua = ["lingua"] +testing = ["pytest"] + [[package]] name = "markupsafe" version = "2.1.1" @@ -292,7 +360,7 @@ diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pytest" -version = "7.1.1" +version = "7.1.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -372,6 +440,38 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "sqlalchemy" +version = "1.4.36" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] +aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.3,!=0.2.4)"] +mariadb_connector = ["mariadb (>=1.0.1)"] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"] +mysql_connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"] +postgresql_pg8000 = ["pg8000 (>=1.16.6)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] +sqlcipher = ["sqlcipher3-binary"] + [[package]] name = "tomli" version = "2.0.1" @@ -382,11 +482,11 @@ python-versions = ">=3.7" [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "urllib3" @@ -401,6 +501,14 @@ brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "uwsgi" +version = "2.0.20" +description = "The uWSGI server" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "wcag-contrast-ratio" version = "0.9" @@ -411,7 +519,7 @@ python-versions = "*" [[package]] name = "websockets" -version = "10.2" +version = "10.3" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" category = "main" optional = false @@ -419,7 +527,7 @@ python-versions = ">=3.7" [[package]] name = "werkzeug" -version = "2.1.1" +version = "2.1.2" description = "The comprehensive WSGI web application library." category = "main" optional = false @@ -447,9 +555,13 @@ websockets = "*" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "cf8899258dac046f0ed3d0492161db330ab735dc8dcbe1c46d2c8d4e48b66342" +content-hash = "a87f6de89028dd491dd5c09ef629084997f799bd9f80d773d80ff2102f1dc0ea" [metadata.files] +alembic = [ + {file = "alembic-1.7.7-py3-none-any.whl", hash = "sha256:29be0856ec7591c39f4e1cb10f198045d890e6e2274cf8da80cb5e721a09642b"}, + {file = "alembic-1.7.7.tar.gz", hash = "sha256:4961248173ead7ce8a21efb3de378f13b8398e6630fab0eb258dc74a8af24c58"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -615,8 +727,8 @@ charset-normalizer = [ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, - {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -674,8 +786,16 @@ feedparser = [ {file = "feedparser-6.0.8.tar.gz", hash = "sha256:5ce0410a05ab248c8c7cfca3a0ea2203968ee9ff4486067379af4827a59f9661"}, ] flask = [ - {file = "Flask-2.1.1-py3-none-any.whl", hash = "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264"}, - {file = "Flask-2.1.1.tar.gz", hash = "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"}, + {file = "Flask-2.1.2-py3-none-any.whl", hash = "sha256:fad5b446feb0d6db6aec0c3184d16a8c1f6c3e464b511649c8918a9be100b4fe"}, + {file = "Flask-2.1.2.tar.gz", hash = "sha256:315ded2ddf8a6281567edb27393010fe3406188bafbfe65a3339d5787d89e477"}, +] +flask-alembic = [ + {file = "Flask-Alembic-2.0.1.tar.gz", hash = "sha256:05a1e6f4148dbfcc9280a393373bfbd250af6f9f4f0ca9f744ef8f7376a3deec"}, + {file = "Flask_Alembic-2.0.1-py2.py3-none-any.whl", hash = "sha256:7e67740b0b08d58dcae0c701d56b56e60f5fa4af907bb82b4cb0469229ba94ff"}, +] +flask-sqlalchemy = [ + {file = "Flask-SQLAlchemy-2.5.1.tar.gz", hash = "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912"}, + {file = "Flask_SQLAlchemy-2.5.1-py2.py3-none-any.whl", hash = "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390"}, ] font-source-sans-pro = [ {file = "font-source-sans-pro-0.0.1.tar.gz", hash = "sha256:3f81d8e52b0d7e930e2c867c0d3ee549312d03f97b71b664a8361006311f72e5"}, @@ -687,6 +807,63 @@ fonts = [ {file = "fonts-0.0.3-py3-none-any.whl", hash = "sha256:e5f551379088ab260c2537980c3ccdff8af93408d9d4fa3319388d2ee25b7b6d"}, {file = "fonts-0.0.3.tar.gz", hash = "sha256:c626655b75a60715e118e44e270656fd22fd8f54252901ff6ebf1308ad01c405"}, ] +greenlet = [ + {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, + {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, + {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, + {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, + {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"}, + {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, + {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, + {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, + {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, + {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, + {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"}, + {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, + {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, + {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, + {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"}, + {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, + {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, + {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, + {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"}, + {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, + {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, + {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, + {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"}, + {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, + {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, + {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, +] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -704,8 +881,12 @@ itsdangerous = [ {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, ] jinja2 = [ - {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, - {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] +mako = [ + {file = "Mako-1.2.0-py3-none-any.whl", hash = "sha256:23aab11fdbbb0f1051b93793a58323ff937e98e34aece1c4219675122e57e4ba"}, + {file = "Mako-1.2.0.tar.gz", hash = "sha256:9a7c7e922b87db3686210cf49d5d767033a41d4010b284e747682c92bddd8b39"}, ] markupsafe = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, @@ -843,8 +1024,8 @@ pyparsing = [ {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] pytest = [ - {file = "pytest-7.1.1-py3-none-any.whl", hash = "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea"}, - {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"}, + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] pytest-cov = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, @@ -863,74 +1044,115 @@ scrapetube = [ sgmllib3k = [ {file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"}, ] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.36-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:81e53bd383c2c33de9d578bfcc243f559bd3801a0e57f2bcc9a943c790662e0c"}, + {file = "SQLAlchemy-1.4.36-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e1fe00ee85c768807f2a139b83469c1e52a9ffd58a6eb51aa7aeb524325ab18"}, + {file = "SQLAlchemy-1.4.36-cp27-cp27m-win32.whl", hash = "sha256:d57ac32f8dc731fddeb6f5d1358b4ca5456e72594e664769f0a9163f13df2a31"}, + {file = "SQLAlchemy-1.4.36-cp27-cp27m-win_amd64.whl", hash = "sha256:fca8322e04b2dde722fcb0558682740eebd3bd239bea7a0d0febbc190e99dc15"}, + {file = "SQLAlchemy-1.4.36-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:53d2d9ee93970c969bc4e3c78b1277d7129554642f6ffea039c282c7dc4577bc"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:f0394a3acfb8925db178f7728adb38c027ed7e303665b225906bfa8099dc1ce8"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c606d8238feae2f360b8742ffbe67741937eb0a05b57f536948d198a3def96"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8d07fe2de0325d06e7e73281e9a9b5e259fbd7cbfbe398a0433cbb0082ad8fa7"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5041474dcab7973baa91ec1f3112049a9dd4652898d6a95a6a895ff5c58beb6b"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-win32.whl", hash = "sha256:be094460930087e50fd08297db9d7aadaed8408ad896baf758e9190c335632da"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-win_amd64.whl", hash = "sha256:64d796e9af522162f7f2bf7a3c5531a0a550764c426782797bbeed809d0646c5"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a0ae3aa2e86a4613f2d4c49eb7da23da536e6ce80b2bfd60bbb2f55fc02b0b32"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d50cb71c1dbed70646d521a0975fb0f92b7c3f84c61fa59e07be23a1aaeecfc"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:16abf35af37a3d5af92725fc9ec507dd9e9183d261c2069b6606d60981ed1c6e"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864a83bd345871ad9699ce466388f836db7572003d67d9392a71998092210e3"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-win32.whl", hash = "sha256:fbf8c09fe9728168f8cc1b40c239eab10baf9c422c18be7f53213d70434dea43"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-win_amd64.whl", hash = "sha256:6e859fa96605027bd50d8e966db1c4e1b03e7b3267abbc4b89ae658c99393c58"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:166a3887ec355f7d2f12738f7fa25dc8ac541867147a255f790f2f41f614cb44"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e885548da361aa3f8a9433db4cfb335b2107e533bf314359ae3952821d84b3e"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c90ef955d429966d84326d772eb34333178737ebb669845f1d529eb00c75e72"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a052bd9f53004f8993c624c452dfad8ec600f572dd0ed0445fbe64b22f5570e"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-win32.whl", hash = "sha256:dce3468bf1fc12374a1a732c9efd146ce034f91bb0482b602a9311cb6166a920"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-win_amd64.whl", hash = "sha256:6cb4c4f57a20710cea277edf720d249d514e587f796b75785ad2c25e1c0fed26"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e74ce103b81c375c3853b436297952ef8d7863d801dcffb6728d01544e5191b5"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b20c4178ead9bc398be479428568ff31b6c296eb22e75776273781a6551973f"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:af2587ae11400157753115612d6c6ad255143efba791406ad8a0cbcccf2edcb3"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83cf3077712be9f65c9aaa0b5bc47bc1a44789fd45053e2e3ecd59ff17c63fe9"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-win32.whl", hash = "sha256:ce20f5da141f8af26c123ebaa1b7771835ca6c161225ce728962a79054f528c3"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-win_amd64.whl", hash = "sha256:316c7e5304dda3e3ad711569ac5d02698bbc71299b168ac56a7076b86259f7ea"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:f522214f6749bc073262529c056f7dfd660f3b5ec4180c5354d985eb7219801e"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ecac4db8c1aa4a269f5829df7e706639a24b780d2ac46b3e485cbbd27ec0028"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3db741beaa983d4cbf9087558620e7787106319f7e63a066990a70657dd6b35"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ec89bf98cc6a0f5d1e28e3ad28e9be6f3b4bdbd521a4053c7ae8d5e1289a8a1"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-win32.whl", hash = "sha256:e12532c4d3f614678623da5d852f038ace1f01869b89f003ed6fe8c793f0c6a3"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-win_amd64.whl", hash = "sha256:cb441ca461bf97d00877b607f132772644b623518b39ced54da433215adce691"}, + {file = "SQLAlchemy-1.4.36.tar.gz", hash = "sha256:64678ac321d64a45901ef2e24725ec5e783f1f4a588305e196431447e7ace243"}, +] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] urllib3 = [ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] +uwsgi = [ + {file = "uwsgi-2.0.20.tar.gz", hash = "sha256:88ab9867d8973d8ae84719cf233b7dafc54326fcaec89683c3f9f77c002cdff9"}, +] wcag-contrast-ratio = [ {file = "wcag-contrast-ratio-0.9.tar.gz", hash = "sha256:69192b8e5c0a7d0dc5ff1187eeb3e398141633a4bde51c69c87f58fe87ed361c"}, ] websockets = [ - {file = "websockets-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5396710f86a306cf52f87fd8ea594a0e894ba0cc5a36059eaca3a477dc332aa"}, - {file = "websockets-10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b22bdc795e62e71118b63e14a08bacfa4f262fd2877de7e5b950f5ac16b0348f"}, - {file = "websockets-10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b04270b5613f245ec84bb2c6a482a9d009aefad37c0575f6cda8499125d5d5c"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5c335dc0e7dc271ef36df3f439868b3c790775f345338c2f61a562f1074187b"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a009eb551c46fd79737791c0c833fc0e5b56bcd1c3057498b262d660b92e9cd"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a10c0c1ee02164246f90053273a42d72a3b2452a7e7486fdae781138cf7fbe2d"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7b38a5c9112e3dbbe45540f7b60c5204f49b3cb501b40950d6ab34cd202ab1d0"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2aa9b91347ecd0412683f28aabe27f6bad502d89bd363b76e0a3508b1596402e"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b7fe45ae43ac814beb8ca09d6995b56800676f2cfa8e23f42839dc69bba34a42"}, - {file = "websockets-10.2-cp310-cp310-win32.whl", hash = "sha256:cef40a1b183dcf39d23b392e9dd1d9b07ab9c46aadf294fff1350fb79146e72b"}, - {file = "websockets-10.2-cp310-cp310-win_amd64.whl", hash = "sha256:c21a67ab9a94bd53e10bba21912556027fea944648a09e6508415ad14e37c325"}, - {file = "websockets-10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb316b87cbe3c0791c2ad92a5a36bf6adc87c457654335810b25048c1daa6fd5"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f14bd10e170abc01682a9f8b28b16e6f20acf6175945ef38db6ffe31b0c72c3f"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fa35c5d1830d0fb7b810324e9eeab9aa92e8f273f11fdbdc0741dcded6d72b9f"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:71a4491cfe7a9f18ee57d41163cb6a8a3fa591e0f0564ca8b0ed86b2a30cced4"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6193bbc1ee63aadeb9a4d81de0e19477401d150d506aee772d8380943f118186"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8beac786a388bb99a66c3be4ab0fb38273c0e3bc17f612a4e0a47c4fc8b9c045"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c67d9cacb3f6537ca21e9b224d4fd08481538e43bcac08b3d93181b0816def39"}, - {file = "websockets-10.2-cp37-cp37m-win32.whl", hash = "sha256:a03a25d95cc7400bd4d61a63460b5d85a7761c12075ee2f51de1ffe73aa593d3"}, - {file = "websockets-10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f8296b8408ec6853b26771599990721a26403e62b9de7e50ac0a056772ac0b5e"}, - {file = "websockets-10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7bb9d8a6beca478c7e9bdde0159bd810cc1006ad6a7cb460533bae39da692ca2"}, - {file = "websockets-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:05f6e9757017270e7a92a2975e2ae88a9a582ffc4629086fd6039aa80e99cd86"}, - {file = "websockets-10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1c9031e90ebfc486e9cdad532b94004ade3aa39a31d3c46c105bb0b579cd2490"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82bc33db6d8309dc27a3bee11f7da2288ad925fcbabc2a4bb78f7e9c56249baf"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:24b879ba7db12bb525d4e58089fcbe6a3df3ce4666523183654170e86d372cbe"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf931c33db9c87c53d009856045dd524e4a378445693382a920fa1e0eb77c36c"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:669e54228a4d9457abafed27cbf0e2b9f401445c4dfefc12bf8e4db9751703b8"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bffc65442dd35c473ca9790a3fa3ba06396102a950794f536783f4b8060af8dd"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4d110a84b63c5cfdd22485acc97b8b919aefeecd6300c0c9d551e055b9a88ea"}, - {file = "websockets-10.2-cp38-cp38-win32.whl", hash = "sha256:117383d0a17a0dda349f7a8790763dde75c1508ff8e4d6e8328b898b7df48397"}, - {file = "websockets-10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b66421f9f13d4df60cd48ab977ed2c2b6c9147ae1a33caf5a9f46294422fda1"}, - {file = "websockets-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac081aa0307f263d63c5ff0727935c736c8dad51ddf2dc9f5d0c4759842aefaa"}, - {file = "websockets-10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4059e2ccbe6587b6dc9a01db5fc49ead9a884faa4076eea96c5ec62cb32f42a"}, - {file = "websockets-10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9ca2ca05a4c29179f06cf6727b45dba5d228da62623ec9df4184413d8aae6cb9"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97950c7c844ec6f8d292440953ae18b99e3a6a09885e09d20d5e7ecd9b914cf8"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:98f57b3120f8331cd7440dbe0e776474f5e3632fdaa474af1f6b754955a47d71"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a72b92f96e5e540d5dda99ee3346e199ade8df63152fa3c737260da1730c411f"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:038afef2a05893578d10dadbdbb5f112bd115c46347e1efe99f6a356ff062138"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f09f46b1ff6d09b01c7816c50bd1903cf7d02ebbdb63726132717c2fcda835d5"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2349fa81b6b959484bb2bda556ccb9eb70ba68987646a0f8a537a1a18319fb03"}, - {file = "websockets-10.2-cp39-cp39-win32.whl", hash = "sha256:bef03a51f9657fb03d8da6ccd233fe96e04101a852f0ffd35f5b725b28221ff3"}, - {file = "websockets-10.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c1f3b18c8162e3b09761d0c6a0305fd642934202541cc511ef972cb9463261e"}, - {file = "websockets-10.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a38a0175ae82e4a8c4bac29fc01b9ee26d7d5a614e5ee11e7813c68a7d938ce"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e56606842bb24e16e36ae7eb308d866b4249cf0be8f63b212f287eeb76b124"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0f73cb2526d6da268e86977b2c4b58f2195994e53070fe567d5487c6436047e6"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cd02f36d37e503aca88ab23cc0a1a0e92a263d37acf6331521eb38040dcf77b"}, - {file = "websockets-10.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:56d48eebe9e39ce0d68701bce3b21df923aa05dcc00f9fd8300de1df31a7c07c"}, - {file = "websockets-10.2.tar.gz", hash = "sha256:8351c3c86b08156337b0e4ece0e3c5ec3e01fcd14e8950996832a23c99416098"}, + {file = "websockets-10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978"}, + {file = "websockets-10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500"}, + {file = "websockets-10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47"}, + {file = "websockets-10.3-cp310-cp310-win32.whl", hash = "sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae"}, + {file = "websockets-10.3-cp310-cp310-win_amd64.whl", hash = "sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079"}, + {file = "websockets-10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6"}, + {file = "websockets-10.3-cp37-cp37m-win32.whl", hash = "sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1"}, + {file = "websockets-10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4"}, + {file = "websockets-10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36"}, + {file = "websockets-10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69"}, + {file = "websockets-10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76"}, + {file = "websockets-10.3-cp38-cp38-win32.whl", hash = "sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559"}, + {file = "websockets-10.3-cp38-cp38-win_amd64.whl", hash = "sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d"}, + {file = "websockets-10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094"}, + {file = "websockets-10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667"}, + {file = "websockets-10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8"}, + {file = "websockets-10.3-cp39-cp39-win32.whl", hash = "sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582"}, + {file = "websockets-10.3-cp39-cp39-win_amd64.whl", hash = "sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02"}, + {file = "websockets-10.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755"}, + {file = "websockets-10.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55"}, + {file = "websockets-10.3.tar.gz", hash = "sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"}, ] werkzeug = [ - {file = "Werkzeug-2.1.1-py3-none-any.whl", hash = "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6"}, - {file = "Werkzeug-2.1.1.tar.gz", hash = "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"}, + {file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"}, + {file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"}, ] yt-dlp = [ {file = "yt-dlp-2022.4.8.tar.gz", hash = "sha256:8758d016509d4574b90fbde975aa70adaef71ed5e7a195141588f6d6945205ba"}, diff --git a/pyproject.toml b/pyproject.toml index ba48a57..c0e0b5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "ucast" version = "0.1.0" -description = "" +description = "YouTube to Podcast converter" authors = ["Theta-Dev "] [tool.poetry.dependencies] @@ -16,9 +16,12 @@ colorthief = "^0.2.1" wcag-contrast-ratio = "^0.9" font-source-sans-pro = "^0.0.1" fonts = "^0.0.3" +Flask-SQLAlchemy = "^2.5.1" +Flask-Alembic = "^2.0.1" +uWSGI = "^2.0.20" [tool.poetry.dev-dependencies] -pytest = "^7.1.1" +pytest = "^7.1.2" pytest-cov = "^3.0.0" invoke = "^1.7.0" diff --git a/ucast/__init__.py b/ucast/__init__.py index fa07f75..9bad579 100644 --- a/ucast/__init__.py +++ b/ucast/__init__.py @@ -1,36 +1 @@ -import os - -from flask import Flask - - -def create_app(test_config=None): - # create and configure the app - app = Flask(__name__, instance_relative_config=True) - app.config.from_mapping( - SECRET_KEY='dev', - DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), - ) - - if test_config is None: - # load the instance config, if it exists, when not testing - app.config.from_pyfile('config.py', silent=True) - else: - # load the test config if passed in - app.config.from_mapping(test_config) - - # ensure the instance folder exists - try: - os.makedirs(app.instance_path) - except OSError: - pass - - # a simple page that says hello - @app.route('/') - def hello(): - return 'Hello, World!' - - @app.route('/err') - def errtest(): - raise Exception('I f*cked up') - - return app +# coding=utf-8 diff --git a/ucast/app.py b/ucast/app.py index 9dc7df9..e324c0c 100644 --- a/ucast/app.py +++ b/ucast/app.py @@ -1,13 +1,27 @@ # coding=utf-8 from flask import Flask -app = Flask(__name__) +from ucast.views import view -@app.route('/') -def hello_world(): # put application's code here - return 'Hello World!' +def create_app(test_config=None): + # create and configure the app + app = Flask(__name__, instance_relative_config=True) + + if test_config is None: + # load the instance config, if it exists, when not testing + app.config.from_pyfile('config.py', silent=True) + else: + # load the test config if passed in + app.config.from_mapping(test_config) + + print(app.instance_path) + + app.register_blueprint(view) + + return app if __name__ == '__main__': - app.run() + application = create_app() + application.run() diff --git a/ucast/views.py b/ucast/views.py new file mode 100644 index 0000000..8382766 --- /dev/null +++ b/ucast/views.py @@ -0,0 +1,14 @@ +# coding=utf-8 +from flask import Blueprint + +view = Blueprint("view", __name__) + + +@view.route('/') +def hello(): + return 'Hello, World!' + + +@view.route('/err') +def errtest(): + raise Exception('This is an error') diff --git a/ucast/wsgi.py b/ucast/wsgi.py new file mode 100644 index 0000000..47e1839 --- /dev/null +++ b/ucast/wsgi.py @@ -0,0 +1,4 @@ +# coding=utf-8 +from ucast import app + +application = app.create_app() From 25fe3eb82430ca1405f462e2767b71b0a1905539 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 5 May 2022 01:25:49 +0200 Subject: [PATCH 3/5] setup django project --- .gitignore | 1 + .pre-commit-config.yaml | 21 ++ manage.py | 22 ++ poetry.lock | 310 +++++++++-------------- pyproject.toml | 16 +- tasks.py | 20 +- tests/__init__.py | 2 +- tests/test_cover.py | 105 +++++--- ucast/__init__.py | 37 +-- ucast/app.py | 13 - ucast/asgi.py | 16 ++ ucast/settings.py | 117 +++++++++ ucast/types.py | 6 - ucast/urls.py | 21 ++ ucast/util.py | 9 - ucast/wsgi.py | 16 ++ yt2podcast/__init__.py | 0 yt2podcast/admin.py | 3 + yt2podcast/apps.py | 6 + yt2podcast/migrations/__init__.py | 0 yt2podcast/models.py | 3 + yt2podcast/service/__init__.py | 1 + {ucast => yt2podcast/service}/cover.py | 97 ++++--- yt2podcast/service/typ.py | 4 + yt2podcast/service/util.py | 7 + {ucast => yt2podcast/service}/youtube.py | 53 ++-- yt2podcast/tests/__init__.py | 1 + yt2podcast/views.py | 3 + 28 files changed, 547 insertions(+), 363 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100755 manage.py delete mode 100644 ucast/app.py create mode 100644 ucast/asgi.py create mode 100644 ucast/settings.py delete mode 100644 ucast/types.py create mode 100644 ucast/urls.py delete mode 100644 ucast/util.py create mode 100644 ucast/wsgi.py create mode 100644 yt2podcast/__init__.py create mode 100644 yt2podcast/admin.py create mode 100644 yt2podcast/apps.py create mode 100644 yt2podcast/migrations/__init__.py create mode 100644 yt2podcast/models.py create mode 100644 yt2podcast/service/__init__.py rename {ucast => yt2podcast/service}/cover.py (68%) create mode 100644 yt2podcast/service/typ.py create mode 100644 yt2podcast/service/util.py rename {ucast => yt2podcast/service}/youtube.py (58%) create mode 100644 yt2podcast/tests/__init__.py create mode 100644 yt2podcast/views.py diff --git a/.gitignore b/.gitignore index d82c827..5479c4e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ __pycache__ # Application data /_run +.env diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1b99b3e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +# 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/manage.py b/manage.py new file mode 100755 index 0000000..4c6dfe3 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ucast.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/poetry.lock b/poetry.lock index 7909b86..7944970 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,14 @@ +[[package]] +name = "asgiref" +version = "3.5.1" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + [[package]] name = "atomicwrites" version = "1.4.0" @@ -69,22 +80,11 @@ python-versions = ">=3.5.0" [package.extras] unicode_backport = ["unicodedata2"] -[[package]] -name = "click" -version = "8.1.2" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -113,6 +113,23 @@ tomli = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["tomli"] +[[package]] +name = "django" +version = "4.0.4" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +category = "main" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +asgiref = ">=3.4.1,<4" +sqlparse = ">=0.2.2" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + [[package]] name = "feedparser" version = "6.0.8" @@ -124,24 +141,6 @@ python-versions = ">=3.6" [package.dependencies] sgmllib3k = "*" -[[package]] -name = "flask" -version = "2.1.1" -description = "A simple framework for building complex web applications." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -click = ">=8.0" -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.0" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - [[package]] name = "font-source-sans-pro" version = "0.0.1" @@ -182,36 +181,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "itsdangerous" -version = "2.1.2" -description = "Safely pass data to untrusted environments and back." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "jinja2" -version = "3.1.1" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" - [[package]] name = "mutagen" version = "1.45.1" @@ -292,7 +261,7 @@ diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pytest" -version = "7.1.1" +version = "7.1.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -372,6 +341,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "sqlparse" +version = "0.4.2" +description = "A non-validating SQL parser." +category = "main" +optional = false +python-versions = ">=3.5" + [[package]] name = "tomli" version = "2.0.1" @@ -382,11 +359,19 @@ python-versions = ">=3.7" [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" + +[[package]] +name = "tzdata" +version = "2022.1" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" [[package]] name = "urllib3" @@ -411,23 +396,12 @@ python-versions = "*" [[package]] name = "websockets" -version = "10.2" +version = "10.3" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" category = "main" optional = false python-versions = ">=3.7" -[[package]] -name = "werkzeug" -version = "2.1.1" -description = "The comprehensive WSGI web application library." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -watchdog = ["watchdog"] - [[package]] name = "yt-dlp" version = "2022.4.8" @@ -447,9 +421,13 @@ websockets = "*" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "cf8899258dac046f0ed3d0492161db330ab735dc8dcbe1c46d2c8d4e48b66342" +content-hash = "0da1cab2b3d09b68a9b2b8999e79108b6d7cae439e1ba3407f98ac83e497a9fe" [metadata.files] +asgiref = [ + {file = "asgiref-3.5.1-py3-none-any.whl", hash = "sha256:45a429524fba18aba9d512498b19d220c4d628e75b40cf5c627524dbaebc5cc1"}, + {file = "asgiref-3.5.1.tar.gz", hash = "sha256:fddeea3c53fa99d0cdb613c3941cc6e52d822491fc2753fba25768fb5bf4e865"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -614,10 +592,6 @@ 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"}, ] -click = [ - {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, - {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, -] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, @@ -669,14 +643,14 @@ 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"}, ] +django = [ + {file = "Django-4.0.4-py3-none-any.whl", hash = "sha256:07c8638e7a7f548dc0acaaa7825d84b7bd42b10e8d22268b3d572946f1e9b687"}, + {file = "Django-4.0.4.tar.gz", hash = "sha256:4e8177858524417563cc0430f29ea249946d831eacb0068a1455686587df40b5"}, +] feedparser = [ {file = "feedparser-6.0.8-py3-none-any.whl", hash = "sha256:1b7f57841d9cf85074deb316ed2c795091a238adb79846bc46dccdaf80f9c59a"}, {file = "feedparser-6.0.8.tar.gz", hash = "sha256:5ce0410a05ab248c8c7cfca3a0ea2203968ee9ff4486067379af4827a59f9661"}, ] -flask = [ - {file = "Flask-2.1.1-py3-none-any.whl", hash = "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264"}, - {file = "Flask-2.1.1.tar.gz", hash = "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"}, -] 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"}, @@ -699,56 +673,6 @@ invoke = [ {file = "invoke-1.7.0-py3-none-any.whl", hash = "sha256:a5159fc63dba6ca2a87a1e33d282b99cea69711b03c64a35bb4e1c53c6c4afa0"}, {file = "invoke-1.7.0.tar.gz", hash = "sha256:e332e49de40463f2016315f51df42313855772be86435686156bc18f45b5cc6c"}, ] -itsdangerous = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, -] -jinja2 = [ - {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, - {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, -] -markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] mutagen = [ {file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"}, {file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"}, @@ -843,8 +767,8 @@ pyparsing = [ {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] pytest = [ - {file = "pytest-7.1.1-py3-none-any.whl", hash = "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea"}, - {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"}, + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] pytest-cov = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, @@ -863,13 +787,21 @@ scrapetube = [ sgmllib3k = [ {file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"}, ] +sqlparse = [ + {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, + {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, +] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, +] +tzdata = [ + {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, + {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, ] urllib3 = [ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, @@ -879,58 +811,54 @@ wcag-contrast-ratio = [ {file = "wcag-contrast-ratio-0.9.tar.gz", hash = "sha256:69192b8e5c0a7d0dc5ff1187eeb3e398141633a4bde51c69c87f58fe87ed361c"}, ] websockets = [ - {file = "websockets-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5396710f86a306cf52f87fd8ea594a0e894ba0cc5a36059eaca3a477dc332aa"}, - {file = "websockets-10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b22bdc795e62e71118b63e14a08bacfa4f262fd2877de7e5b950f5ac16b0348f"}, - {file = "websockets-10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b04270b5613f245ec84bb2c6a482a9d009aefad37c0575f6cda8499125d5d5c"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5c335dc0e7dc271ef36df3f439868b3c790775f345338c2f61a562f1074187b"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a009eb551c46fd79737791c0c833fc0e5b56bcd1c3057498b262d660b92e9cd"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a10c0c1ee02164246f90053273a42d72a3b2452a7e7486fdae781138cf7fbe2d"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7b38a5c9112e3dbbe45540f7b60c5204f49b3cb501b40950d6ab34cd202ab1d0"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2aa9b91347ecd0412683f28aabe27f6bad502d89bd363b76e0a3508b1596402e"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b7fe45ae43ac814beb8ca09d6995b56800676f2cfa8e23f42839dc69bba34a42"}, - {file = "websockets-10.2-cp310-cp310-win32.whl", hash = "sha256:cef40a1b183dcf39d23b392e9dd1d9b07ab9c46aadf294fff1350fb79146e72b"}, - {file = "websockets-10.2-cp310-cp310-win_amd64.whl", hash = "sha256:c21a67ab9a94bd53e10bba21912556027fea944648a09e6508415ad14e37c325"}, - {file = "websockets-10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb316b87cbe3c0791c2ad92a5a36bf6adc87c457654335810b25048c1daa6fd5"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f14bd10e170abc01682a9f8b28b16e6f20acf6175945ef38db6ffe31b0c72c3f"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fa35c5d1830d0fb7b810324e9eeab9aa92e8f273f11fdbdc0741dcded6d72b9f"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:71a4491cfe7a9f18ee57d41163cb6a8a3fa591e0f0564ca8b0ed86b2a30cced4"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6193bbc1ee63aadeb9a4d81de0e19477401d150d506aee772d8380943f118186"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8beac786a388bb99a66c3be4ab0fb38273c0e3bc17f612a4e0a47c4fc8b9c045"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c67d9cacb3f6537ca21e9b224d4fd08481538e43bcac08b3d93181b0816def39"}, - {file = "websockets-10.2-cp37-cp37m-win32.whl", hash = "sha256:a03a25d95cc7400bd4d61a63460b5d85a7761c12075ee2f51de1ffe73aa593d3"}, - {file = "websockets-10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f8296b8408ec6853b26771599990721a26403e62b9de7e50ac0a056772ac0b5e"}, - {file = "websockets-10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7bb9d8a6beca478c7e9bdde0159bd810cc1006ad6a7cb460533bae39da692ca2"}, - {file = "websockets-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:05f6e9757017270e7a92a2975e2ae88a9a582ffc4629086fd6039aa80e99cd86"}, - {file = "websockets-10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1c9031e90ebfc486e9cdad532b94004ade3aa39a31d3c46c105bb0b579cd2490"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82bc33db6d8309dc27a3bee11f7da2288ad925fcbabc2a4bb78f7e9c56249baf"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:24b879ba7db12bb525d4e58089fcbe6a3df3ce4666523183654170e86d372cbe"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf931c33db9c87c53d009856045dd524e4a378445693382a920fa1e0eb77c36c"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:669e54228a4d9457abafed27cbf0e2b9f401445c4dfefc12bf8e4db9751703b8"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bffc65442dd35c473ca9790a3fa3ba06396102a950794f536783f4b8060af8dd"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4d110a84b63c5cfdd22485acc97b8b919aefeecd6300c0c9d551e055b9a88ea"}, - {file = "websockets-10.2-cp38-cp38-win32.whl", hash = "sha256:117383d0a17a0dda349f7a8790763dde75c1508ff8e4d6e8328b898b7df48397"}, - {file = "websockets-10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b66421f9f13d4df60cd48ab977ed2c2b6c9147ae1a33caf5a9f46294422fda1"}, - {file = "websockets-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac081aa0307f263d63c5ff0727935c736c8dad51ddf2dc9f5d0c4759842aefaa"}, - {file = "websockets-10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4059e2ccbe6587b6dc9a01db5fc49ead9a884faa4076eea96c5ec62cb32f42a"}, - {file = "websockets-10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9ca2ca05a4c29179f06cf6727b45dba5d228da62623ec9df4184413d8aae6cb9"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97950c7c844ec6f8d292440953ae18b99e3a6a09885e09d20d5e7ecd9b914cf8"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:98f57b3120f8331cd7440dbe0e776474f5e3632fdaa474af1f6b754955a47d71"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a72b92f96e5e540d5dda99ee3346e199ade8df63152fa3c737260da1730c411f"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:038afef2a05893578d10dadbdbb5f112bd115c46347e1efe99f6a356ff062138"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f09f46b1ff6d09b01c7816c50bd1903cf7d02ebbdb63726132717c2fcda835d5"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2349fa81b6b959484bb2bda556ccb9eb70ba68987646a0f8a537a1a18319fb03"}, - {file = "websockets-10.2-cp39-cp39-win32.whl", hash = "sha256:bef03a51f9657fb03d8da6ccd233fe96e04101a852f0ffd35f5b725b28221ff3"}, - {file = "websockets-10.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c1f3b18c8162e3b09761d0c6a0305fd642934202541cc511ef972cb9463261e"}, - {file = "websockets-10.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a38a0175ae82e4a8c4bac29fc01b9ee26d7d5a614e5ee11e7813c68a7d938ce"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e56606842bb24e16e36ae7eb308d866b4249cf0be8f63b212f287eeb76b124"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0f73cb2526d6da268e86977b2c4b58f2195994e53070fe567d5487c6436047e6"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cd02f36d37e503aca88ab23cc0a1a0e92a263d37acf6331521eb38040dcf77b"}, - {file = "websockets-10.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:56d48eebe9e39ce0d68701bce3b21df923aa05dcc00f9fd8300de1df31a7c07c"}, - {file = "websockets-10.2.tar.gz", hash = "sha256:8351c3c86b08156337b0e4ece0e3c5ec3e01fcd14e8950996832a23c99416098"}, -] -werkzeug = [ - {file = "Werkzeug-2.1.1-py3-none-any.whl", hash = "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6"}, - {file = "Werkzeug-2.1.1.tar.gz", hash = "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"}, + {file = "websockets-10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978"}, + {file = "websockets-10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500"}, + {file = "websockets-10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47"}, + {file = "websockets-10.3-cp310-cp310-win32.whl", hash = "sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae"}, + {file = "websockets-10.3-cp310-cp310-win_amd64.whl", hash = "sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079"}, + {file = "websockets-10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6"}, + {file = "websockets-10.3-cp37-cp37m-win32.whl", hash = "sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1"}, + {file = "websockets-10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4"}, + {file = "websockets-10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36"}, + {file = "websockets-10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69"}, + {file = "websockets-10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76"}, + {file = "websockets-10.3-cp38-cp38-win32.whl", hash = "sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559"}, + {file = "websockets-10.3-cp38-cp38-win_amd64.whl", hash = "sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d"}, + {file = "websockets-10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094"}, + {file = "websockets-10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667"}, + {file = "websockets-10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8"}, + {file = "websockets-10.3-cp39-cp39-win32.whl", hash = "sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582"}, + {file = "websockets-10.3-cp39-cp39-win_amd64.whl", hash = "sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02"}, + {file = "websockets-10.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755"}, + {file = "websockets-10.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55"}, + {file = "websockets-10.3.tar.gz", hash = "sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"}, ] yt-dlp = [ {file = "yt-dlp-2022.4.8.tar.gz", hash = "sha256:8758d016509d4574b90fbde975aa70adaef71ed5e7a195141588f6d6945205ba"}, diff --git a/pyproject.toml b/pyproject.toml index ba48a57..ab28bd5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [tool.poetry] name = "ucast" -version = "0.1.0" -description = "" +version = "0.0.1" +description = "YouTube to Podcast converter" authors = ["Theta-Dev "] [tool.poetry.dependencies] python = "^3.10" -Flask = "^2.1.1" +Django = "^4.0.4" yt-dlp = "^2022.3.8" scrapetube = "^2.2.2" rfeed = "^1.1.1" @@ -25,3 +25,13 @@ invoke = "^1.7.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[tool.flake8] +max-line-length = 88 +per-file-ignores = [ + "settings.py:E501", +] + +[tool.black] +line-length = 88 +target-version = ['py310'] diff --git a/tasks.py b/tasks.py index cafe839..83bfdb5 100644 --- a/tasks.py +++ b/tasks.py @@ -2,31 +2,31 @@ import os from invoke import task -from ucast import youtube, util, cover import tests +from yt2podcast.service import cover, util, youtube @task def test(c): - c.run('pytest tests', pty=True) + c.run("pytest tests", pty=True) @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) diff --git a/tests/__init__.py b/tests/__init__.py index 3a4d86b..8b21d67 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 745c2e6..cac2bbe 100644 --- a/tests/test_cover.py +++ b/tests/test_cover.py @@ -1,60 +1,89 @@ # 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, types +from yt2podcast.service 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)), -]) -def test_get_dominant_color(file_name: str, color: types.Color): - img = Image.open(tests.DIR_TESTFILES / 'thumbnail' / file_name) +@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) 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)), -]) -def test_get_text_color(bg_color: types.Color, text_color: types.Color): +@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) @@ -67,15 +96,17 @@ 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/ucast/__init__.py b/ucast/__init__.py index fa07f75..9bad579 100644 --- a/ucast/__init__.py +++ b/ucast/__init__.py @@ -1,36 +1 @@ -import os - -from flask import Flask - - -def create_app(test_config=None): - # create and configure the app - app = Flask(__name__, instance_relative_config=True) - app.config.from_mapping( - SECRET_KEY='dev', - DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), - ) - - if test_config is None: - # load the instance config, if it exists, when not testing - app.config.from_pyfile('config.py', silent=True) - else: - # load the test config if passed in - app.config.from_mapping(test_config) - - # ensure the instance folder exists - try: - os.makedirs(app.instance_path) - except OSError: - pass - - # a simple page that says hello - @app.route('/') - def hello(): - return 'Hello, World!' - - @app.route('/err') - def errtest(): - raise Exception('I f*cked up') - - return app +# coding=utf-8 diff --git a/ucast/app.py b/ucast/app.py deleted file mode 100644 index 9dc7df9..0000000 --- a/ucast/app.py +++ /dev/null @@ -1,13 +0,0 @@ -# coding=utf-8 -from flask import Flask - -app = Flask(__name__) - - -@app.route('/') -def hello_world(): # put application's code here - return 'Hello World!' - - -if __name__ == '__main__': - app.run() diff --git a/ucast/asgi.py b/ucast/asgi.py new file mode 100644 index 0000000..be266bd --- /dev/null +++ b/ucast/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for ucast project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ucast.settings") + +application = get_asgi_application() diff --git a/ucast/settings.py b/ucast/settings.py new file mode 100644 index 0000000..7616f3b --- /dev/null +++ b/ucast/settings.py @@ -0,0 +1,117 @@ +""" +Django settings for ucast project. + +Generated by 'django-admin startproject' using Django 4.0.4. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-$b25pq-s+(_zx2!2$+i+^0$kft0&y3kwmj7j5a#d_jop)$d061" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "ucast.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "ucast.wsgi.application" + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + +# Password validation +# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/4.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/ucast/types.py b/ucast/types.py deleted file mode 100644 index 2b54726..0000000 --- a/ucast/types.py +++ /dev/null @@ -1,6 +0,0 @@ -# coding=utf-8 -from os import PathLike -from typing import Tuple, Union - -Color = Tuple[int, int, int] -Path = Union[str, bytes, PathLike] diff --git a/ucast/urls.py b/ucast/urls.py new file mode 100644 index 0000000..7d590bd --- /dev/null +++ b/ucast/urls.py @@ -0,0 +1,21 @@ +"""ucast URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path("admin/", admin.site.urls), +] diff --git a/ucast/util.py b/ucast/util.py deleted file mode 100644 index 15cb146..0000000 --- a/ucast/util.py +++ /dev/null @@ -1,9 +0,0 @@ -# coding=utf-8 -import requests - -from ucast import types - - -def download_file(url: str, download_path: types.Path): - r = requests.get(url, allow_redirects=True) - open(download_path, 'wb').write(r.content) diff --git a/ucast/wsgi.py b/ucast/wsgi.py new file mode 100644 index 0000000..06375f4 --- /dev/null +++ b/ucast/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for ucast project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ucast.settings") + +application = get_wsgi_application() diff --git a/yt2podcast/__init__.py b/yt2podcast/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yt2podcast/admin.py b/yt2podcast/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/yt2podcast/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/yt2podcast/apps.py b/yt2podcast/apps.py new file mode 100644 index 0000000..563d9a1 --- /dev/null +++ b/yt2podcast/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class Yt2PodcastConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "yt2podcast" diff --git a/yt2podcast/migrations/__init__.py b/yt2podcast/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yt2podcast/models.py b/yt2podcast/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/yt2podcast/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/yt2podcast/service/__init__.py b/yt2podcast/service/__init__.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/yt2podcast/service/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/ucast/cover.py b/yt2podcast/service/cover.py similarity index 68% rename from ucast/cover.py rename to yt2podcast/service/cover.py index e4a4aa7..38db4da 100644 --- a/ucast/cover.py +++ b/yt2podcast/service/cover.py @@ -1,40 +1,43 @@ # coding=utf-8 import math -from typing import Tuple, List, Optional +from pathlib import Path +from typing import List, Optional, Tuple -from PIL import Image, ImageDraw, ImageFont -from colorthief import ColorThief import wcag_contrast_ratio +from colorthief import ColorThief from fonts.ttf import SourceSansPro +from PIL import Image, ImageDraw, ImageFont -from ucast import types +from yt2podcast.service 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: @@ -46,14 +49,14 @@ def _split_text(height: int, width: int, text: str, font: ImageFont.FreeTypeFont 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) @@ -61,8 +64,15 @@ def _split_text(height: int, width: int, text: str, font: ImageFont.FreeTypeFont return lines -def _draw_text_box(draw: ImageDraw.ImageDraw, box: Tuple[int, int, int, int], text: str, font: ImageFont.FreeTypeFont, - color: types.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 @@ -85,13 +95,13 @@ def _get_dominant_color(img: Image.Image): return thief.get_color() -def _interpolate_color(color_from: types.Color, color_to: types.Color, interval: int): +def _interpolate_color(color_from: typ.Color, color_to: typ.Color, interval: int): det_co = [(t - f) / interval for f, t in zip(color_from, color_to)] for i in range(interval): yield [round(f + det * i) for f, det in zip(color_from, det_co)] -def _get_text_color(bg_color) -> types.Color: +def _get_text_color(bg_color) -> typ.Color: color_decimal = tuple([c / 255 for c in bg_color]) c_blk = wcag_contrast_ratio.rgb((0, 0, 0), color_decimal) c_wht = wcag_contrast_ratio.rgb((1, 1, 1), color_decimal) @@ -100,7 +110,9 @@ def _get_text_color(bg_color) -> types.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) @@ -112,11 +124,13 @@ def _create_cover_image(thumbnail: Image.Image, avatar: Optional[Image.Image], t 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 @@ -133,7 +147,7 @@ def _create_cover_image(thumbnail: Image.Image, avatar: Optional[Image.Image], t 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) @@ -149,18 +163,43 @@ def _create_cover_image(thumbnail: Image.Image, avatar: Optional[Image.Image], t 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: types.Path, avatar_path: Optional[types.Path], title: str, channel: str, - cover_path: types.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/yt2podcast/service/typ.py b/yt2podcast/service/typ.py new file mode 100644 index 0000000..dbf0d5a --- /dev/null +++ b/yt2podcast/service/typ.py @@ -0,0 +1,4 @@ +# coding=utf-8 +from typing import Tuple + +Color = Tuple[int, int, int] diff --git a/yt2podcast/service/util.py b/yt2podcast/service/util.py new file mode 100644 index 0000000..a65bc97 --- /dev/null +++ b/yt2podcast/service/util.py @@ -0,0 +1,7 @@ +# coding=utf-8 +import requests + + +def download_file(url: str, download_path): + r = requests.get(url, allow_redirects=True) + open(download_path, "wb").write(r.content) diff --git a/ucast/youtube.py b/yt2podcast/service/youtube.py similarity index 58% rename from ucast/youtube.py rename to yt2podcast/service/youtube.py index ac68a6b..8e56d79 100644 --- a/ucast/youtube.py +++ b/yt2podcast/service/youtube.py @@ -1,16 +1,16 @@ # coding=utf-8 -from operator import itemgetter import json from dataclasses import dataclass +from operator import itemgetter -from yt_dlp import YoutubeDL -from scrapetube import scrapetube import requests +from scrapetube import scrapetube +from yt_dlp import YoutubeDL 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,29 +20,25 @@ 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 @@ -61,7 +57,8 @@ 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" @@ -69,11 +66,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) diff --git a/yt2podcast/tests/__init__.py b/yt2podcast/tests/__init__.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/yt2podcast/tests/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/yt2podcast/views.py b/yt2podcast/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/yt2podcast/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From cd0bcccc62eb053bd0e6fc6f59dea22cd4e4c0d7 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 6 May 2022 12:13:44 +0200 Subject: [PATCH 4/5] add manage.py to package --- .gitignore | 2 ++ manage.py | 23 +---------------------- pyproject.toml | 7 +++++++ ucast/manage.py | 22 ++++++++++++++++++++++ 4 files changed, 32 insertions(+), 22 deletions(-) mode change 100755 => 120000 manage.py create mode 100755 ucast/manage.py diff --git a/.gitignore b/.gitignore index 5479c4e..1ddb610 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Python venv +dist .tox __pycache__ *.egg-info @@ -18,3 +19,4 @@ __pycache__ # Application data /_run .env +*.sqlite3 diff --git a/manage.py b/manage.py deleted file mode 100755 index 4c6dfe3..0000000 --- a/manage.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" -import os -import sys - - -def main(): - """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ucast.settings") - try: - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) - - -if __name__ == "__main__": - main() diff --git a/manage.py b/manage.py new file mode 120000 index 0000000..9986394 --- /dev/null +++ b/manage.py @@ -0,0 +1 @@ +ucast/manage.py \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ab28bd5..bb2feb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,10 @@ name = "ucast" version = "0.0.1" description = "YouTube to Podcast converter" authors = ["Theta-Dev "] +packages = [ + { include = "ucast" }, + { include = "yt2podcast" }, +] [tool.poetry.dependencies] python = "^3.10" @@ -22,6 +26,9 @@ pytest = "^7.1.1" pytest-cov = "^3.0.0" invoke = "^1.7.0" +[tool.poetry.scripts] +"ucast-manage" = "ucast.manage:main" + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/ucast/manage.py b/ucast/manage.py new file mode 100755 index 0000000..4c6dfe3 --- /dev/null +++ b/ucast/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ucast.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() From 5def0f3c61958d1c67ad898547e62d5ce35342a9 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 8 May 2022 23:12:44 +0200 Subject: [PATCH 5/5] add basic models --- poetry.lock | 21 ++++++++- pyproject.toml | 1 + tasks.py | 10 ++++- tests/__init__.py | 4 -- ucast/settings.py | 6 ++- yt2podcast/migrations/0001_initial.py | 41 ++++++++++++++++++ yt2podcast/models.py | 18 +++++++- yt2podcast/tests/__init__.py | 3 ++ {tests => yt2podcast/tests}/test_cover.py | 2 +- .../tests}/testfiles/avatar/a1.jpg | Bin .../tests}/testfiles/avatar/a2.jpg | Bin .../tests}/testfiles/avatar/a3.jpg | Bin .../tests}/testfiles/cover/c1.png | Bin .../tests}/testfiles/cover/c2.png | Bin .../tests}/testfiles/cover/c3.png | Bin .../tests}/testfiles/sources.md | 0 .../tests}/testfiles/thumbnail/t1.webp | Bin .../tests}/testfiles/thumbnail/t2.webp | Bin .../tests}/testfiles/thumbnail/t3.webp | Bin 19 files changed, 97 insertions(+), 9 deletions(-) delete mode 100644 tests/__init__.py create mode 100644 yt2podcast/migrations/0001_initial.py rename {tests => yt2podcast/tests}/test_cover.py (99%) rename {tests => yt2podcast/tests}/testfiles/avatar/a1.jpg (100%) rename {tests => yt2podcast/tests}/testfiles/avatar/a2.jpg (100%) rename {tests => yt2podcast/tests}/testfiles/avatar/a3.jpg (100%) rename {tests => yt2podcast/tests}/testfiles/cover/c1.png (100%) rename {tests => yt2podcast/tests}/testfiles/cover/c2.png (100%) rename {tests => yt2podcast/tests}/testfiles/cover/c3.png (100%) rename {tests => yt2podcast/tests}/testfiles/sources.md (100%) rename {tests => yt2podcast/tests}/testfiles/thumbnail/t1.webp (100%) rename {tests => yt2podcast/tests}/testfiles/thumbnail/t2.webp (100%) rename {tests => yt2podcast/tests}/testfiles/thumbnail/t3.webp (100%) diff --git a/poetry.lock b/poetry.lock index 7944970..025af55 100644 --- a/poetry.lock +++ b/poetry.lock @@ -295,6 +295,21 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-django" +version = "4.5.2" +description = "A Django plugin for pytest." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["django", "django-configurations (>=2.0)"] + [[package]] name = "requests" version = "2.27.1" @@ -421,7 +436,7 @@ websockets = "*" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "0da1cab2b3d09b68a9b2b8999e79108b6d7cae439e1ba3407f98ac83e497a9fe" +content-hash = "0288eb9eca14b78ee0cfac28cef0dcf17701165b6aa778255bfa73b94d5b2c4f" [metadata.files] asgiref = [ @@ -774,6 +789,10 @@ pytest-cov = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] +pytest-django = [ + {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, + {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, +] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, diff --git a/pyproject.toml b/pyproject.toml index bb2feb2..ba41970 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ fonts = "^0.0.3" pytest = "^7.1.1" pytest-cov = "^3.0.0" invoke = "^1.7.0" +pytest-django = "^4.5.2" [tool.poetry.scripts] "ucast-manage" = "ucast.manage:main" diff --git a/tasks.py b/tasks.py index 83bfdb5..d803b2d 100644 --- a/tasks.py +++ b/tasks.py @@ -1,16 +1,24 @@ import os +from pathlib import Path from invoke import task -import tests +from yt2podcast import tests from yt2podcast.service import cover, util, youtube +os.chdir(Path(__file__).absolute().parent) + @task def test(c): c.run("pytest tests", pty=True) +@task +def makemigrations(c): + c.run("python manage.py makemigrations yt2podcast") + + @task def get_cover(c, vid=""): vinfo = youtube.get_video_info(vid) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 8b21d67..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# coding=utf-8 -from importlib.resources import files - -DIR_TESTFILES = files("tests.testfiles") diff --git a/ucast/settings.py b/ucast/settings.py index 7616f3b..ffcdcd0 100644 --- a/ucast/settings.py +++ b/ucast/settings.py @@ -13,7 +13,7 @@ https://docs.djangoproject.com/en/4.0/ref/settings/ from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent +BASE_DIR = Path(__file__).resolve().parent.parent / "_run" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ @@ -29,6 +29,7 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ + "yt2podcast.apps.Yt2PodcastConfig", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -110,6 +111,9 @@ USE_TZ = True # https://docs.djangoproject.com/en/4.0/howto/static-files/ STATIC_URL = "static/" +STATIC_ROOT = BASE_DIR / "static" + +DATA_ROOT = BASE_DIR / "data" # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field diff --git a/yt2podcast/migrations/0001_initial.py b/yt2podcast/migrations/0001_initial.py new file mode 100644 index 0000000..fed1734 --- /dev/null +++ b/yt2podcast/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# Generated by Django 4.0.4 on 2022-05-05 00:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Channel", + fields=[ + ( + "id", + models.CharField(max_length=30, primary_key=True, serialize=False), + ), + ("name", models.CharField(max_length=100)), + ("active", models.BooleanField(default=True)), + ("skip_livestreams", models.BooleanField(default=True)), + ("skip_shorts", models.BooleanField(default=True)), + ("keep_videos", models.IntegerField(default=None, null=True)), + ], + ), + migrations.CreateModel( + name="Video", + fields=[ + ( + "id", + models.CharField(max_length=30, primary_key=True, serialize=False), + ), + ("title", models.CharField(max_length=200)), + ("slug", models.CharField(max_length=209)), + ("published", models.DateTimeField()), + ("downloaded", models.DateTimeField()), + ("description", models.TextField()), + ], + ), + ] diff --git a/yt2podcast/models.py b/yt2podcast/models.py index 71a8362..486abad 100644 --- a/yt2podcast/models.py +++ b/yt2podcast/models.py @@ -1,3 +1,19 @@ from django.db import models -# Create your models here. + +class Channel(models.Model): + id = models.CharField(max_length=30, primary_key=True) + name = models.CharField(max_length=100) + active = models.BooleanField(default=True) + skip_livestreams = models.BooleanField(default=True) + skip_shorts = models.BooleanField(default=True) + keep_videos = models.IntegerField(null=True, default=None) + + +class Video(models.Model): + id = models.CharField(max_length=30, primary_key=True) + title = models.CharField(max_length=200) + slug = models.CharField(max_length=209) + published = models.DateTimeField() + downloaded = models.DateTimeField() + description = models.TextField() diff --git a/yt2podcast/tests/__init__.py b/yt2podcast/tests/__init__.py index 9bad579..b383d20 100644 --- a/yt2podcast/tests/__init__.py +++ b/yt2podcast/tests/__init__.py @@ -1 +1,4 @@ # coding=utf-8 +from importlib.resources import files + +DIR_TESTFILES = files("yt2podcast.tests.testfiles") diff --git a/tests/test_cover.py b/yt2podcast/tests/test_cover.py similarity index 99% rename from tests/test_cover.py rename to yt2podcast/tests/test_cover.py index cac2bbe..518ac25 100644 --- a/tests/test_cover.py +++ b/yt2podcast/tests/test_cover.py @@ -7,7 +7,7 @@ import pytest from fonts.ttf import SourceSansPro from PIL import Image, ImageChops, ImageFont -import tests +from yt2podcast import tests from yt2podcast.service import cover, typ diff --git a/tests/testfiles/avatar/a1.jpg b/yt2podcast/tests/testfiles/avatar/a1.jpg similarity index 100% rename from tests/testfiles/avatar/a1.jpg rename to yt2podcast/tests/testfiles/avatar/a1.jpg diff --git a/tests/testfiles/avatar/a2.jpg b/yt2podcast/tests/testfiles/avatar/a2.jpg similarity index 100% rename from tests/testfiles/avatar/a2.jpg rename to yt2podcast/tests/testfiles/avatar/a2.jpg diff --git a/tests/testfiles/avatar/a3.jpg b/yt2podcast/tests/testfiles/avatar/a3.jpg similarity index 100% rename from tests/testfiles/avatar/a3.jpg rename to yt2podcast/tests/testfiles/avatar/a3.jpg diff --git a/tests/testfiles/cover/c1.png b/yt2podcast/tests/testfiles/cover/c1.png similarity index 100% rename from tests/testfiles/cover/c1.png rename to yt2podcast/tests/testfiles/cover/c1.png diff --git a/tests/testfiles/cover/c2.png b/yt2podcast/tests/testfiles/cover/c2.png similarity index 100% rename from tests/testfiles/cover/c2.png rename to yt2podcast/tests/testfiles/cover/c2.png diff --git a/tests/testfiles/cover/c3.png b/yt2podcast/tests/testfiles/cover/c3.png similarity index 100% rename from tests/testfiles/cover/c3.png rename to yt2podcast/tests/testfiles/cover/c3.png diff --git a/tests/testfiles/sources.md b/yt2podcast/tests/testfiles/sources.md similarity index 100% rename from tests/testfiles/sources.md rename to yt2podcast/tests/testfiles/sources.md diff --git a/tests/testfiles/thumbnail/t1.webp b/yt2podcast/tests/testfiles/thumbnail/t1.webp similarity index 100% rename from tests/testfiles/thumbnail/t1.webp rename to yt2podcast/tests/testfiles/thumbnail/t1.webp diff --git a/tests/testfiles/thumbnail/t2.webp b/yt2podcast/tests/testfiles/thumbnail/t2.webp similarity index 100% rename from tests/testfiles/thumbnail/t2.webp rename to yt2podcast/tests/testfiles/thumbnail/t2.webp diff --git a/tests/testfiles/thumbnail/t3.webp b/yt2podcast/tests/testfiles/thumbnail/t3.webp similarity index 100% rename from tests/testfiles/thumbnail/t3.webp rename to yt2podcast/tests/testfiles/thumbnail/t3.webp