This commit is contained in:
ArabCoders 2024-12-16 22:06:54 +03:00
parent 4787579d67
commit f614dbc217
6 changed files with 152 additions and 149 deletions

76
Pipfile.lock generated
View file

@ -117,11 +117,11 @@
},
"aiosignal": {
"hashes": [
"sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc",
"sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"
"sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5",
"sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.1"
"markers": "python_version >= '3.9'",
"version": "==1.3.2"
},
"anyio": {
"hashes": [
@ -142,11 +142,11 @@
},
"attrs": {
"hashes": [
"sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346",
"sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"
"sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff",
"sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"
],
"markers": "python_version >= '3.7'",
"version": "==24.2.0"
"markers": "python_version >= '3.8'",
"version": "==24.3.0"
},
"bidict": {
"hashes": [
@ -166,11 +166,11 @@
},
"certifi": {
"hashes": [
"sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8",
"sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"
"sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56",
"sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"
],
"markers": "python_version >= '3.6'",
"version": "==2024.8.30"
"version": "==2024.12.14"
},
"cffi": {
"hashes": [
@ -281,36 +281,36 @@
},
"debugpy": {
"hashes": [
"sha256:1339e14c7d980407248f09824d1b25ff5c5616651689f1e0f0e51bdead3ea13e",
"sha256:17c5e0297678442511cf00a745c9709e928ea4ca263d764e90d233208889a19e",
"sha256:1efbb3ff61487e2c16b3e033bc8595aea578222c08aaf3c4bf0f93fadbd662ee",
"sha256:365e556a4772d7d0d151d7eb0e77ec4db03bcd95f26b67b15742b88cacff88e9",
"sha256:3d9755e77a2d680ce3d2c5394a444cf42be4a592caaf246dbfbdd100ffcf7ae5",
"sha256:3e59842d6c4569c65ceb3751075ff8d7e6a6ada209ceca6308c9bde932bcef11",
"sha256:472a3994999fe6c0756945ffa359e9e7e2d690fb55d251639d07208dbc37caea",
"sha256:54a7e6d3014c408eb37b0b06021366ee985f1539e12fe49ca2ee0d392d9ceca5",
"sha256:5e565fc54b680292b418bb809f1386f17081d1346dca9a871bf69a8ac4071afe",
"sha256:62d22dacdb0e296966d7d74a7141aaab4bec123fa43d1a35ddcb39bf9fd29d70",
"sha256:66eeae42f3137eb428ea3a86d4a55f28da9bd5a4a3d369ba95ecc3a92c1bba53",
"sha256:6953b335b804a41f16a192fa2e7851bdcfd92173cbb2f9f777bb934f49baab65",
"sha256:7c4d65d03bee875bcb211c76c1d8f10f600c305dbd734beaed4077e902606fee",
"sha256:7e646e62d4602bb8956db88b1e72fe63172148c1e25c041e03b103a25f36673c",
"sha256:7e8b079323a56f719977fde9d8115590cb5e7a1cba2fcee0986ef8817116e7c1",
"sha256:8138efff315cd09b8dcd14226a21afda4ca582284bf4215126d87342bba1cc66",
"sha256:8e99c0b1cc7bf86d83fb95d5ccdc4ad0586d4432d489d1f54e4055bcc795f693",
"sha256:957363d9a7a6612a37458d9a15e72d03a635047f946e5fceee74b50d52a9c8e2",
"sha256:957ecffff80d47cafa9b6545de9e016ae8c9547c98a538ee96ab5947115fb3dd",
"sha256:ada7fb65102a4d2c9ab62e8908e9e9f12aed9d76ef44880367bc9308ebe49a0f",
"sha256:b74a49753e21e33e7cf030883a92fa607bddc4ede1aa4145172debc637780040",
"sha256:c36856343cbaa448171cba62a721531e10e7ffb0abff838004701454149bc037",
"sha256:cc37a6c9987ad743d9c3a14fa1b1a14b7e4e6041f9dd0c8abf8895fe7a97b899",
"sha256:cfe1e6c6ad7178265f74981edf1154ffce97b69005212fbc90ca22ddfe3d017e",
"sha256:e46b420dc1bea64e5bbedd678148be512442bc589b0111bd799367cde051e71a",
"sha256:ff54ef77ad9f5c425398efb150239f6fe8e20c53ae2f68367eba7ece1e96226d"
"sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920",
"sha256:116bf8342062246ca749013df4f6ea106f23bc159305843491f64672a55af2e5",
"sha256:189058d03a40103a57144752652b3ab08ff02b7595d0ce1f651b9acc3a3a35a0",
"sha256:23dc34c5e03b0212fa3c49a874df2b8b1b8fda95160bd79c01eb3ab51ea8d851",
"sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b",
"sha256:2b26fefc4e31ff85593d68b9022e35e8925714a10ab4858fb1b577a8a48cb8cd",
"sha256:32db46ba45849daed7ccf3f2e26f7a386867b077f39b2a974bb5c4c2c3b0a280",
"sha256:40499a9979c55f72f4eb2fc38695419546b62594f8af194b879d2a18439c97a9",
"sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1",
"sha256:52c3cf9ecda273a19cc092961ee34eb9ba8687d67ba34cc7b79a521c1c64c4c0",
"sha256:52d8a3166c9f2815bfae05f386114b0b2d274456980d41f320299a8d9a5615a7",
"sha256:61bc8b3b265e6949855300e84dc93d02d7a3a637f2aec6d382afd4ceb9120c9f",
"sha256:654130ca6ad5de73d978057eaf9e582244ff72d4574b3e106fb8d3d2a0d32458",
"sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57",
"sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e",
"sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308",
"sha256:85de8474ad53ad546ff1c7c7c89230db215b9b8a02754d41cb5a76f70d0be296",
"sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3",
"sha256:8da1db4ca4f22583e834dcabdc7832e56fe16275253ee53ba66627b86e304da1",
"sha256:8ffc382e4afa4aee367bf413f55ed17bd91b191dcaf979890af239dda435f2a1",
"sha256:987bce16e86efa86f747d5151c54e91b3c1e36acc03ce1ddb50f9d09d16ded0e",
"sha256:ad7efe588c8f5cf940f40c3de0cd683cc5b76819446abaa50dc0829a30c094db",
"sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28",
"sha256:c928bbf47f65288574b78518449edaa46c82572d340e2750889bbf8cd92f3737",
"sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768",
"sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.8.9"
"version": "==1.8.11"
},
"frozenlist": {
"hashes": [

View file

@ -45,14 +45,20 @@ class Emitter:
tasks = []
for emitter in self.emitters:
_ret = emitter(event, data, **kwargs)
if _ret:
if isinstance(_ret, list):
tasks.extend(_ret)
else:
tasks.append(_ret)
try:
_ret = emitter(event, data, **kwargs)
if _ret:
if isinstance(_ret, list):
tasks.extend(_ret)
else:
tasks.append(_ret)
except Exception as e:
LOG.error(f"Emitter '{emitter}' failed with error '{e}'.")
if len(tasks) < 1:
return
try:
await asyncio.wait_for(asyncio.gather(*tasks), timeout=60)
except asyncio.TimeoutError:
LOG.error(f"Timed out sending event {event}.")
LOG.error(f"Timed out sending event '{event}'.")

View file

@ -109,7 +109,7 @@ class HttpAPI(common):
contentType = self.extToMime.get(os.path.splitext(file)[1], MIME.from_file(file))
self.staticHolder[urlPath] = {'content': content, 'content_type': contentType}
LOG.debug(f'Preloading: [{urlPath}].')
LOG.debug(f"Preloading '{urlPath}'.")
app.router.add_get(urlPath, self.staticFile)
if urlPath.endswith('/index.html') and urlPath != '/index.html':
@ -148,7 +148,7 @@ class HttpAPI(common):
self.routes.get(self.config.url_prefix[:-1])(lambda _: web.HTTPFound(self.config.url_prefix))
# add static files.
self.routes.static(f'{self.config.url_prefix}download/', self.config.download_path)
self.routes.static(f"{self.config.url_prefix}download/", self.config.download_path)
self.preloadStatic(app)
try:
@ -304,7 +304,7 @@ class HttpAPI(common):
updated = True
setattr(item.info, k, v)
LOG.info(f'Updated [{k}] to [{v}] for [{item.info.id}]')
LOG.info(f"Updated '{k}' to '{v}' for '{item.info.id}'")
status = 200 if updated else 304
if updated:
@ -478,7 +478,7 @@ class HttpAPI(common):
'X-Accel-Buffering': 'no',
'Access-Control-Allow-Origin': '*',
'Pragma': 'public',
'Cache-Control': f'public, max-age={time.time() + 31536000}',
'Cache-Control': f"public, max-age={time.time() + 31536000}",
'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(os.path.getmtime(file_path)).timetuple()),
'Expires': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(time.time() + 31536000).timetuple()),
})
@ -504,7 +504,7 @@ class HttpAPI(common):
'X-Accel-Buffering': 'no',
'Access-Control-Allow-Origin': '*',
'Pragma': 'public',
'Cache-Control': f'public, max-age={time.time() + 31536000}',
'Cache-Control': f"public, max-age={time.time() + 31536000}",
'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(os.path.getmtime(file_path)).timetuple()),
'Expires': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(time.time() + 31536000).timetuple()),
})

View file

@ -13,7 +13,7 @@ from .config import Config
from .DownloadQueue import DownloadQueue
from .common import common
from .encoder import Encoder
from .Utils import isDownloaded, load_file
from .Utils import isDownloaded
from .Emitter import Emitter
LOG = logging.getLogger('socket')
@ -65,7 +65,7 @@ class HttpSocket(common):
return
try:
LOG.info(f'Cli command from {sid=}: {data}')
LOG.info(f"Cli command from client '{sid}'. '{data}'")
args = ['yt-dlp'] + shlex.split(data)
_env = os.environ.copy()
@ -93,7 +93,7 @@ class HttpSocket(common):
try:
os.close(slave_fd)
except Exception as e:
LOG.error(f'Error closing PTY: {str(e)}')
LOG.error(f"Error closing PTY. '{str(e)}'.")
async def read_pty():
loop = asyncio.get_running_loop()
@ -126,7 +126,7 @@ class HttpSocket(common):
try:
os.close(master_fd)
except Exception as e:
LOG.error(f'Error closing PTY: {str(e)}')
LOG.error(f"Error closing PTY. '{str(e)}'.")
# Start reading output from PTY
read_task = asyncio.create_task(read_pty())
@ -139,7 +139,7 @@ class HttpSocket(common):
await self.emitter.emit('cli_close', {'exitcode': returncode}, to=sid)
except Exception as e:
LOG.error(f'CLI Error for {sid=}: {str(e)}')
LOG.error(f"CLI execute exception was thrown for client '{sid}'. {str(e)}")
await self.emitter.emit('cli_out1put', {
'type': 'stderr',
'line': str(e),
@ -152,7 +152,7 @@ class HttpSocket(common):
quality: str = data.get('quality')
if not url:
self.emitter.warning('No URL provided.')
self.emitter.warning('No URL provided.', to=sid)
return
format: str = data.get('format')
@ -178,7 +178,7 @@ class HttpSocket(common):
@ws_event
async def item_cancel(self, sid: str, id: str):
if not id:
await self.emitter.warning('Invalid request.')
await self.emitter.warning('Invalid request.', to=sid)
return
status: dict[str, str] = {}
@ -190,7 +190,7 @@ class HttpSocket(common):
@ws_event
async def item_delete(self, sid: str, id: str):
if not id:
await self.emitter.warning('Invalid request.')
await self.emitter.warning('Invalid request.', to=sid)
return
status: dict[str, str] = {}
@ -230,30 +230,22 @@ class HttpSocket(common):
with open(manual_archive, 'a') as f:
f.write(f"{idDict['archive_id']} - at: {datetime.now().isoformat()}\n")
LOG.info(f'Archiving item: {data["url"]=}')
LOG.info(f"Archiving url '{data['url']}' with id '{idDict['archive_id']}'.")
@ws_event
async def connect(self, sid: str, _=None):
data: dict = {
**self.queue.get(),
"config": self.config.frontend(),
"tasks": [],
"tasks": self.config.tasks,
"presets": [],
"directories": [],
}
if os.path.exists(os.path.join(self.config.config_path, 'tasks.json')):
try:
(tasks, status, error) = load_file(os.path.join(self.config.config_path, 'tasks.json'), list)
if status is False:
LOG.error(f"Could not load tasks file. Error message '{error}'.")
else:
data['tasks'] = tasks
except Exception as e:
pass
# get directory listing
dlDir: str = self.config.download_path
data['directories'] = [name for name in os.listdir(dlDir) if os.path.isdir(os.path.join(dlDir, name))]
data['directories'] = [
name for name in os.listdir(dlDir) if os.path.isdir(os.path.join(dlDir, name))
]
await self.emitter.emit('initial_data', data, to=sid)

View file

@ -64,9 +64,13 @@ class Config:
auth_password: str = None
ytdlp_version: str = YTDLP_VERSION
tasks: list = []
_manual_vars: tuple = ('temp_path', 'config_path', 'download_path',)
_immutable: tuple = ('version', '__instance', 'ytdl_options', 'new_version_available', 'ytdlp_version', 'started')
_immutable: tuple = (
'version', '__instance', 'ytdl_options', 'tasks',
'new_version_available', 'ytdlp_version', 'started',
)
_int_vars: tuple = ('port', 'max_workers', 'socket_timeout', 'extract_info_timeout', 'debugpy_port',)
_boolean_vars: tuple = ('keep_archive', 'ytdl_debug', 'debug', 'temp_keep', 'allow_manifestless',)
@ -122,7 +126,7 @@ class Config:
for key in re.findall(r'\{.*?\}', v):
localKey: str = key[1:-1]
if localKey not in self.__dict__:
logging.error(f'Config variable "{k}" had non existing config reference "{key}"')
logging.error(f"Config variable '{k}' had non-existing config reference '{key}'.")
sys.exit(1)
v = v.replace(key, getattr(self, localKey))
@ -131,7 +135,7 @@ class Config:
if k in self._boolean_vars:
if str(v).lower() not in (True, False, 'true', 'false', 'on', 'off', '1', '0'):
raise ValueError(f'Config variable "{k}" is set to a non-boolean value "{v}".')
raise ValueError(f"Config variable '{k}' is set to a non-boolean value '{v}'.")
setattr(self, k, str(v).lower() in (True, 'true', 'on', '1'))
@ -143,7 +147,7 @@ class Config:
numeric_level = getattr(logging, self.log_level.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError(f"Invalid log level: {self.log_level}")
raise ValueError(f"Invalid log level '{self.log_level}' specified.")
coloredlogs.install(
level=numeric_level,
@ -157,36 +161,48 @@ class Config:
try:
import debugpy
debugpy.listen(("0.0.0.0", self.debugpy_port))
LOG.info(f"starting debugpy server on [0.0.0.0:{self.debugpy_port}]")
LOG.info(f"starting debugpy server on '0.0.0.0:{self.debugpy_port}'.")
except ImportError:
LOG.error("debugpy not found, please install it with 'pip install debugpy'")
LOG.error("debugpy package not found, please install it with 'pip install debugpy'.")
except Exception as e:
LOG.error(f"Error starting debugpy server at [0.0.0.0:{self.debugpy_port}]: {e}")
LOG.error(f"Error starting debugpy server at '0.0.0.0:{self.debugpy_port}'. {e}")
optsFile: str = os.path.join(self.config_path, 'ytdlp.json')
if os.path.exists(optsFile) and os.path.getsize(optsFile) > 0:
LOG.info(f'Loading yt-dlp custom options from "{optsFile}"')
LOG.info(f"Loading yt-dlp custom options from '{optsFile}'.")
(opts, status, error) = load_file(optsFile, dict)
if not status:
LOG.error(f'Error loading "{optsFile}": {error}')
LOG.error(f"Could not load yt-dlp custom options from '{optsFile}'. {error}")
sys.exit(1)
self.ytdl_options.update(opts)
else:
LOG.info(f'No custom yt-dlp options found in "{self.config_path}"')
LOG.info(f"No yt-dlp custom options found at '{optsFile}'.")
tasksFile = os.path.join(self.config_path, 'tasks.json')
if os.path.exists(tasksFile) and os.path.getsize(tasksFile) > 0:
LOG.info(f"Loading tasks from '{tasksFile}'.")
try:
(tasks, status, error) = load_file(tasksFile, list)
if not status:
LOG.error(f"Could not load tasks file from '{tasksFile}'. '{error}'.")
sys.exit(1)
self.tasks.extend(tasks)
except Exception as e:
pass
self.ytdl_options['socket_timeout'] = self.socket_timeout
if self.keep_archive:
LOG.info(f'keep archive: {self.keep_archive}')
self.ytdl_options['download_archive'] = os.path.join(
self.config_path, 'archive.log')
LOG.info(f"keep archive option is enabled.")
self.ytdl_options['download_archive'] = os.path.join(self.config_path, 'archive.log')
LOG.info(f'Keep temp: {self.temp_keep}')
if self.temp_keep:
LOG.info(f'Keep temp files option is enabled.')
if self.auth_password and self.auth_username:
LOG.warn(f"Basic authentication enabled with username: '{self.auth_username}'.")
LOG.warning(f"Basic authentication enabled with username '{self.auth_username}'.")
self.started = time.time()

View file

@ -3,7 +3,6 @@
import asyncio
import os
import random
import socketio
import logging
import caribou
import sqlite3
@ -11,7 +10,6 @@ import magic
from datetime import datetime
from aiohttp import web
from pathlib import Path
from library.Utils import load_file
from library.Emitter import Emitter
from library.config import Config
from library.encoder import Encoder
@ -21,17 +19,13 @@ from library.HttpSocket import HttpSocket
from library.HttpAPI import HttpAPI
from aiocron import crontab
LOG = logging.getLogger('app')
LOG = logging.getLogger("app")
MIME = magic.Magic(mime=True)
class main:
config: Config = None
app: web.Application = None
sio: socketio.AsyncServer = None
routes: web.RouteTableDef = None
loop: asyncio.AbstractEventLoop = None
appLoader: str = None
http: HttpAPI = None
socket: HttpSocket = None
@ -42,11 +36,11 @@ class main:
self.encoder = Encoder()
self.checkDirectories()
caribou.upgrade(self.config.db_file, os.path.join(self.rootPath, 'migrations'))
caribou.upgrade(self.config.db_file, os.path.join(self.rootPath, "migrations"))
connection = sqlite3.connect(database=self.config.db_file, isolation_level=None)
connection.row_factory = sqlite3.Row
connection.execute('PRAGMA journal_mode=wal')
connection.execute("PRAGMA journal_mode=wal")
emitter = Emitter()
@ -56,96 +50,91 @@ class main:
self.http = HttpAPI(queue=queue, emitter=emitter, encoder=self.encoder)
self.socket = HttpSocket(queue=queue, emitter=emitter, encoder=self.encoder)
WebhookFile = os.path.join(self.config.config_path, 'webhooks.json')
WebhookFile = os.path.join(self.config.config_path, "webhooks.json")
if os.path.exists(WebhookFile):
emitter.add_emitter(Webhooks(WebhookFile).emit)
def checkDirectories(self) -> None:
try:
LOG.debug(f'Checking download folder at [{self.config.download_path}]')
LOG.debug(f"Checking download folder at '{self.config.download_path}'.")
if not os.path.exists(self.config.download_path):
LOG.info(f'Creating download folder at [{self.config.download_path}]')
LOG.info(f"Creating download folder at '{self.config.download_path}'.")
os.makedirs(self.config.download_path, exist_ok=True)
except OSError as e:
LOG.error(f'Could not create download folder at [{self.config.download_path}]')
LOG.error(
f"Could not create download folder at '{self.config.download_path}'."
)
raise e
try:
LOG.debug(f'Checking temp folder at [{self.config.temp_path}]')
LOG.debug(f"Checking temp folder at '{self.config.temp_path}'.")
if not os.path.exists(self.config.temp_path):
LOG.info(f'Creating temp folder at [{self.config.temp_path}]')
LOG.info(f"Creating temp folder at '{self.config.temp_path}'.")
os.makedirs(self.config.temp_path, exist_ok=True)
except OSError as e:
LOG.error(f'Could not create temp folder at [{self.config.temp_path}]')
LOG.error(f"Could not create temp folder at '{self.config.temp_path}'.")
raise e
try:
LOG.debug(f'Checking config folder at [{self.config.config_path}]')
LOG.debug(f"Checking config folder at '{self.config.config_path}'.")
if not os.path.exists(self.config.config_path):
LOG.info(f'Creating config folder at [{self.config.config_path}]')
LOG.info(f"Creating config folder at '{self.config.config_path}'.")
os.makedirs(self.config.config_path, exist_ok=True)
except OSError as e:
LOG.error(f'Could not create config folder at [{self.config.config_path}]')
LOG.error(f"Could not create config folder at '{self.config.config_path}'.")
raise e
try:
LOG.debug(f'Checking database file at [{self.config.db_file}]')
LOG.debug(f"Checking database file at '{self.config.db_file}'.")
if not os.path.exists(self.config.db_file):
LOG.info(f'Creating database file at [{self.config.db_file}]')
with open(self.config.db_file, 'w') as _:
LOG.info(f"Creating database file at '{self.config.db_file}'.")
with open(self.config.db_file, "w") as _:
pass
except OSError as e:
LOG.error(f'Could not create database file at [{self.config.db_file}]')
LOG.error(f"Could not create database file at '{self.config.db_file}'.")
raise e
def load_tasks(self):
tasks_file: str = os.path.join(self.config.config_path, 'tasks.json')
if not os.path.exists(tasks_file):
LOG.info(f'No tasks file found at {tasks_file}. Skipping Tasks.')
return
async def cron_runner(self, task: dict):
try:
(tasks, status, error) = load_file(tasks_file, list)
if status is False:
raise Exception(error)
taskName = task.get("name", task.get("url"))
timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
LOG.info(f"Started 'Task: {taskName}' at '{timeNow}'.")
await self.socket.add(
url=task.get("url"),
quality=task.get("quality", "best"),
format=task.get("format", "any"),
folder=task.get("folder"),
ytdlp_cookies=task.get("ytdlp_cookies"),
ytdlp_config=task.get("ytdlp_config"),
output_template=task.get("output_template"),
)
timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
LOG.info(f"Completed 'Task: {taskName}' at '{timeNow}'.")
except Exception as e:
LOG.error(f"Could not load tasks file '{tasks_file}'. Error message '{str(e)}'. Skipping Tasks.")
return
timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
LOG.error(
f"Failed 'Task: {taskName}' at '{timeNow}'. Error message '{str(e)}'."
)
for task in tasks:
if not task.get('url'):
LOG.warning(f'Invalid task {task}.')
def load_tasks(self):
for task in self.config.tasks:
if not task.get("url"):
LOG.warning(f"Invalid task '{task}'. No URL found.")
continue
cron_timer: str = task.get('timer', f'{random.randint(1,59)} */1 * * *')
async def cron_runner(task: dict):
taskName = task.get("name", task.get("url"))
timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
LOG.info(f'Started [Task: {taskName}] at [{timeNow}].')
await self.add(
url=task.get('url'),
quality=task.get('quality', 'best'),
format=task.get('format', 'any'),
folder=task.get('folder'),
ytdlp_cookies=task.get('ytdlp_cookies'),
ytdlp_config=task.get('ytdlp_config'),
output_template=task.get('output_template')
)
timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
LOG.info(f'Completed [Task: {taskName}] at [{timeNow}].')
cron_timer: str = task.get("timer", f"{random.randint(1,59)} */1 * * *")
crontab(
spec=cron_timer,
func=cron_runner,
func=self.cron_runner,
args=(task,),
start=True,
loop=self.loop
loop=asyncio.get_event_loop(),
)
LOG.info(f'Added [Task: {task.get("name", task.get("url"))}] executed every [{cron_timer}].')
LOG.info(
f"Added 'Task: {task.get('name', task.get('url'))}' to be executed every '{cron_timer}'."
)
def start(self):
self.socket.attach(self.app)
@ -153,7 +142,7 @@ class main:
self.load_tasks()
start: str = f'YTPTube v{self.config.version} - listening on http://{self.config.host}:{self.config.port}'
start: str = f"YTPTube v{self.config.version} - started on http://{self.config.host}:{self.config.port}"
web.run_app(
self.app,
host=self.config.host,
@ -161,10 +150,10 @@ class main:
reuse_port=True,
loop=asyncio.get_event_loop(),
access_log=None,
print=lambda _: LOG.info(start)
print=lambda _: LOG.info(start),
)
if __name__ == '__main__':
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
main().start()