Added add_batch endpoint.

This commit is contained in:
ArabCoders 2023-11-25 14:33:56 +03:00
parent b7bf9627a1
commit 83745c1f98
4 changed files with 78 additions and 20 deletions

View file

@ -12,8 +12,6 @@ import logging
import caribou import caribou
import sqlite3 import sqlite3
log = logging.getLogger('main')
class Main: class Main:
config: Config = None config: Config = None
@ -87,7 +85,6 @@ class Main:
self.start() self.start()
def start(self): def start(self):
log.info(f"Listening on {self.config.host}:{self.config.port}")
web.run_app( web.run_app(
self.app, self.app,
host=self.config.host, host=self.config.host,
@ -118,7 +115,7 @@ class Main:
if ytdlp_config is None: if ytdlp_config is None:
ytdlp_config = {} ytdlp_config = {}
status = await self.dqueue.add( status = await self.add(
url=url, url=url,
quality=quality, quality=quality,
format=format, format=format,
@ -130,6 +127,33 @@ class Main:
return web.Response(text=self.serializer.encode(status)) return web.Response(text=self.serializer.encode(status))
@self.routes.post(self.config.url_prefix + 'add_batch')
async def add_batch(request: Request) -> Response:
status = {}
post = await request.json()
if not isinstance(post, list):
raise web.HTTPBadRequest()
for item in post:
if not isinstance(item, dict):
raise web.HTTPBadRequest(
'Invalid request body expecting list with dicts.')
if not item.get('url'):
raise web.HTTPBadRequest(reason='url is required')
status[item.get('url')] = await self.add(
url=item.get('url'),
quality=item.get('quality'),
format=item.get('format'),
folder=item.get('folder'),
ytdlp_cookies=item.get('ytdlp_cookies'),
ytdlp_config=item.get('ytdlp_config'),
output_template=item.get('output_template')
)
return web.Response(text=self.serializer.encode(status))
@self.routes.delete(self.config.url_prefix + 'delete') @self.routes.delete(self.config.url_prefix + 'delete')
async def delete(request: Request) -> Response: async def delete(request: Request) -> Response:
post = await request.json() post = await request.json()
@ -208,6 +232,31 @@ class Main:
'Could not find the frontend UI static assets. Please run `node_modules/.bin/ng build` inside the frontend folder.') from e 'Could not find the frontend UI static assets. Please run `node_modules/.bin/ng build` inside the frontend folder.') from e
raise e raise e
async def add(
self,
url: str,
quality: str,
format: str,
folder: str,
ytdlp_cookies: str,
ytdlp_config: dict,
output_template: str
) -> dict[str, str]:
if ytdlp_config is None:
ytdlp_config = {}
status = await self.dqueue.add(
url=url,
quality=quality,
format=format,
folder=folder,
ytdlp_cookies=ytdlp_cookies,
ytdlp_config=ytdlp_config,
output_template=output_template
)
return status
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)

View file

@ -3,9 +3,7 @@ import logging
import os import os
import re import re
import sys import sys
import coloredlogs
log = logging.getLogger('config')
class Config: class Config:
config_path: str = '.' config_path: str = '.'
@ -32,6 +30,8 @@ class Config:
base_path: str = '' base_path: str = ''
logging_level: str = 'info'
_boolean_vars: tuple = ('keep_archive', 'ytdl_debug') _boolean_vars: tuple = ('keep_archive', 'ytdl_debug')
def __init__(self): def __init__(self):
@ -59,7 +59,7 @@ class Config:
for key in re.findall(r'\{.*?\}', v): for key in re.findall(r'\{.*?\}', v):
localKey: str = key[1:-1] localKey: str = key[1:-1]
if localKey not in self.__dict__: if localKey not in self.__dict__:
log.error( logging.error(
f'Config variable "{k}" had non exisitng config reference "{key}"') f'Config variable "{k}" had non exisitng config reference "{key}"')
sys.exit(1) sys.exit(1)
v = v.replace(key, getattr(self, localKey)) v = v.replace(key, getattr(self, localKey))
@ -68,27 +68,41 @@ class Config:
if k in self._boolean_vars: if k in self._boolean_vars:
if str(v).lower() not in (True, False, 'true', 'false', 'on', 'off', '1', '0'): if str(v).lower() not in (True, False, 'true', 'false', 'on', 'off', '1', '0'):
log.error( raise ValueError(
f'Config variable "{k}" is set to a non-boolean value "{v}".') f'Config variable "{k}" is set to a non-boolean value "{v}".')
sys.exit(1)
setattr(self, k, str(v).lower() in (True, 'true', 'on', '1')) setattr(self, k, str(v).lower() in (True, 'true', 'on', '1'))
if not self.url_prefix.endswith('/'): if not self.url_prefix.endswith('/'):
self.url_prefix += '/' self.url_prefix += '/'
numeric_level = getattr(
logging, self.logging_level.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError(f"Invalid log level: {self.logging_level}")
logging.basicConfig(
force=True,
level=numeric_level,
format="%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s"
)
coloredlogs.install()
if isinstance(self.ytdl_options, str): if isinstance(self.ytdl_options, str):
try: try:
self.ytdl_options = json.loads(self.ytdl_options) self.ytdl_options = json.loads(self.ytdl_options)
assert isinstance(self.ytdl_options, dict) assert isinstance(self.ytdl_options, dict)
except (json.decoder.JSONDecodeError, AssertionError) as e: except (json.decoder.JSONDecodeError, AssertionError) as e:
log.error(f'JSON error in "YTP_YTDL_OPTIONS": {e}') logging.error(f'JSON error in "YTP_YTDL_OPTIONS": {e}')
sys.exit(1) sys.exit(1)
if self.ytdl_options_file: if self.ytdl_options_file:
log.info( logging.info(
f'Loading yt-dlp custom options from "{self.ytdl_options_file}"') f'Loading yt-dlp custom options from "{self.ytdl_options_file}"')
if not os.path.exists(self.ytdl_options_file): if not os.path.exists(self.ytdl_options_file):
log.error( logging.error(
f'"YTP_YTDL_OPTIONS_FILE" ENV points to non-existent file: "{self.ytdl_options_file}"') f'"YTP_YTDL_OPTIONS_FILE" ENV points to non-existent file: "{self.ytdl_options_file}"')
else: else:
try: try:
@ -97,7 +111,8 @@ class Config:
assert isinstance(opts, dict) assert isinstance(opts, dict)
self.ytdl_options.update(opts) self.ytdl_options.update(opts)
except (json.decoder.JSONDecodeError, AssertionError) as e: except (json.decoder.JSONDecodeError, AssertionError) as e:
log.error(f'JSON error in "{self.ytdl_options_file}": {e}') logging.error(
f'JSON error in "{self.ytdl_options_file}": {e}')
sys.exit(1) sys.exit(1)
if self.keep_archive: if self.keep_archive:

View file

@ -1,6 +1,5 @@
from collections import OrderedDict from collections import OrderedDict
import json import json
import logging
import sqlite3 import sqlite3
from src.Utils import calcDownloadPath from src.Utils import calcDownloadPath
from src.Config import Config from src.Config import Config
@ -55,7 +54,6 @@ class DataStore:
) )
for row in cursor: for row in cursor:
logging.debug(row)
data: dict = json.loads(row['data']) data: dict = json.loads(row['data'])
key: str = data.pop('_id') key: str = data.pop('_id')
item: ItemDTO = ItemDTO(**data) item: ItemDTO = ItemDTO(**data)

View file

@ -77,7 +77,6 @@ def get_opts(format: str, quality: str, ytdl_opts: dict) -> dict:
Returns: Returns:
ytdl_opts: Extra options ytdl_opts: Extra options
""" """
logging.debug(f"get_opts: {format} {quality} {ytdl_opts}")
opts = copy.deepcopy(ytdl_opts) opts = copy.deepcopy(ytdl_opts)
if not opts: if not opts:
opts: dict = { opts: dict = {
@ -107,8 +106,6 @@ def get_opts(format: str, quality: str, ytdl_opts: dict) -> dict:
postprocessors.append( postprocessors.append(
{"key": "FFmpegThumbnailsConvertor", "format": "jpg", "when": "before_dl"}) {"key": "FFmpegThumbnailsConvertor", "format": "jpg", "when": "before_dl"})
logging.debug(
f"Postprocessors: {opts['postprocessors']} + {postprocessors}")
opts["postprocessors"] = postprocessors + \ opts["postprocessors"] = postprocessors + \
(opts["postprocessors"] if "postprocessors" in opts else []) (opts["postprocessors"] if "postprocessors" in opts else [])
return opts return opts
@ -119,7 +116,6 @@ def getVideoInfo(url: str, ytdlp_opts: dict = None) -> (Any | dict[str, Any] | N
'quiet': True, 'quiet': True,
'no_color': True, 'no_color': True,
'extract_flat': True, 'extract_flat': True,
} }
if ytdlp_opts: if ytdlp_opts: