diff --git a/backend/main.py b/backend/main.py index 10833da..1c39b93 100644 --- a/backend/main.py +++ b/backend/main.py @@ -13,7 +13,18 @@ type SocketT = websockets.WebSocketServerProtocol async def handler(socket: SocketT) -> None: - ydls = ydl_pool.Downloader(None, None) # type: ignore # TODO + async def ydl_log_handler(level: response.YdlLogLevel, msg: str) -> None: + try: + await socket.send(response.ydl_log(level, msg)) + except: + pass + + ydls = ydl_pool.Downloader( + ydl_pool.YdlLogger( + ydl_log_handler, + asyncio.get_event_loop(), + ) + ) async for message in socket: @@ -34,9 +45,7 @@ async def handler(socket: SocketT) -> None: data['url'], data.get('items'), ) - await socket.send(response.dl_end(ret)) - - # TODO: cancellation + await socket.send(response.ydl_end(ret)) case _: raise ValueError('invalid "action" field value') diff --git a/backend/response.py b/backend/response.py index dc9f997..47cc2f9 100644 --- a/backend/response.py +++ b/backend/response.py @@ -1,5 +1,7 @@ import json -import traceback +import logging +from typing import Literal + def playlist(items: list[str]) -> str: return json.dumps({ @@ -7,20 +9,25 @@ def playlist(items: list[str]) -> str: "data": items, }) -def dl_progress(msg: str) -> str: + +type YdlLogLevel = Literal['debug'] | Literal['warning'] | Literal['error'] + +def ydl_log(level: YdlLogLevel, msg: str) -> str: return json.dumps({ - "type": "dl_progress", + "type": "ydl_log", + "level": level, "data": msg, }) -def dl_end(ret: int) -> str: +def ydl_end(ret: int) -> str: return json.dumps({ - "type": "dl_end", + "type": "ydl_end", "data": ret, }) + def error(ex: Exception) -> str: - traceback.print_tb(ex.__traceback__) + logging.getLogger('musicdlp').exception(ex) return json.dumps({ "type": "error", "data": { diff --git a/backend/ydl_pool.py b/backend/ydl_pool.py index 7dd6511..6d85a30 100644 --- a/backend/ydl_pool.py +++ b/backend/ydl_pool.py @@ -1,10 +1,12 @@ import asyncio +import logging from typing import Callable, Awaitable, Iterable from yt_dlp import YoutubeDL from yt_dlp.postprocessor import FFmpegExtractAudioPP import config +import response import id3pp @@ -40,12 +42,33 @@ ydl_fn_keys = create_ydl_fn.keys() NP_YDLS = {'yandex'} -class Downloader: +class YdlLogger: def __init__( self, - progress_cb: Callable[[str], Awaitable], - lyrics_cb: Callable[[list[str]], Awaitable]) -> None: + log_cb: Callable[[response.YdlLogLevel, str], Awaitable], + loop: asyncio.AbstractEventLoop) -> None: + self.log_cb = log_cb + self.loop = loop + self.mdlp_logger = logging.getLogger('musicdlp') + + def debug(self, msg: str) -> None: + asyncio.run_coroutine_threadsafe(self.log_cb('debug', msg), self.loop) + + def info(self, _: str) -> None: # afaik not used in yt-dlp + pass + + def warning(self, msg: str) -> None: + asyncio.run_coroutine_threadsafe(self.log_cb('warning', msg), self.loop) + + def error(self, msg: str) -> None: + self.mdlp_logger.error(msg) + asyncio.run_coroutine_threadsafe(self.log_cb('error', msg), self.loop) + + +class Downloader: + + def __init__(self, logger: YdlLogger | None = None) -> None: self.ydls: dict[str, YoutubeDL | None] = { 'youtube': None, @@ -56,8 +79,9 @@ class Downloader: self.cur_ydl: YoutubeDL | None = None self.cur_site = '' - self.progress_cb = progress_cb - self.lyrics_cb = lyrics_cb + self.id3pp_obj = id3pp.ID3TagsPP() + + self.logger = logger def choose_ydl(self, site: str) -> None: @@ -67,8 +91,11 @@ class Downloader: if ydl is None: ydl = create_ydl_fn[site]() + if self.logger is not None: + ydl.params['logger'] = self.logger + ydl.params['outtmpl']['default'] = cfg.tmpl - ydl.add_post_processor(id3pp.ID3TagsPP(), when='post_process') + ydl.add_post_processor(self.id3pp_obj, when='post_process') cookies = cfg.cookies_dir / (site + '.txt') if cookies.exists():