diff --git a/pyproject.toml b/pyproject.toml index 8938fe2..9717fac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,8 @@ dependencies = [ "click>=8.1.7", "mutagen>=1.47.0", "ffmpeg-python>=0.2.0", - "m3u8>=6.0.0" + "m3u8>=6.0.0", + "rich>=13.9.4" ] [project.urls] diff --git a/tiddl/cli/__init__.py b/tiddl/cli/__init__.py index f222dd5..cab82cc 100644 --- a/tiddl/cli/__init__.py +++ b/tiddl/cli/__init__.py @@ -1,6 +1,8 @@ import click import logging +from rich.logging import RichHandler + from .ctx import ContextObj, passContext, Context from .auth import AuthGroup from .download import UrlGroup, FavGroup, SearchGroup, FileGroup @@ -12,28 +14,36 @@ from tiddl.config import HOME_PATH @click.group() @passContext @click.option("--verbose", "-v", is_flag=True, help="Show debug logs") -def cli(ctx: Context, verbose: bool): +@click.option("--quiet", "-q", is_flag=True, help="Suppress logs") +def cli(ctx: Context, verbose: bool, quiet: bool): """TIDDL - Download Tidal tracks \u266b""" ctx.obj = ContextObj() - # TODO: add rich console to ctx.obj, edit logging config, - # add more verbosity options (silent, info, debug), - # maybe logging format configuration - # latest logs - file_handler = logging.FileHandler(HOME_PATH / "tiddl.log", mode="w", encoding="utf-8") - file_handler.setLevel(logging.DEBUG) + file_handler = logging.FileHandler( + HOME_PATH / "tiddl.log", mode="w", encoding="utf-8" + ) - stream_handler = logging.StreamHandler() - stream_handler.setLevel(logging.DEBUG if verbose else logging.INFO) + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter( + logging.Formatter( + "%(levelname)s [%(name)s.%(funcName)s] %(message)s", datefmt="[%X]" + ) + ) + + rich_handler = RichHandler(console=ctx.obj.console, rich_tracebacks=True) + rich_handler.setLevel( + logging.DEBUG if verbose else logging.ERROR if quiet else logging.INFO + ) logging.basicConfig( level=logging.DEBUG, handlers=[ - stream_handler, + rich_handler, file_handler, ], - format="%(levelname)s [%(name)s.%(funcName)s] %(message)s", + format="%(message)s", + datefmt="[%X]", ) logging.getLogger("urllib3").setLevel(logging.ERROR) diff --git a/tiddl/cli/ctx.py b/tiddl/cli/ctx.py index d641a0e..1913806 100644 --- a/tiddl/cli/ctx.py +++ b/tiddl/cli/ctx.py @@ -1,6 +1,8 @@ import functools import click +from rich.console import Console + from typing import Callable, TypeVar, cast from tiddl.api import TidalApi @@ -12,11 +14,13 @@ class ContextObj: api: TidalApi | None config: Config resources: list[TidalResource] + console: Console def __init__(self) -> None: self.config = Config.fromFile() self.resources = [] self.api = None + self.console = Console() auth = self.config.auth diff --git a/tiddl/cli/download/__init__.py b/tiddl/cli/download/__init__.py index 4126c8f..5d3ba0b 100644 --- a/tiddl/cli/download/__init__.py +++ b/tiddl/cli/download/__init__.py @@ -1,3 +1,4 @@ +import logging import click from .fav import FavGroup @@ -67,9 +68,7 @@ def DownloadCommand( credits: List[AlbumItemsCredits.ItemWithCredits.CreditsEntry] = [], ): if not track.allowStreaming: - click.echo( - f"{click.style('✖', 'yellow')} Track {click.style(file_name, 'yellow')} does not allow streaming" - ) + logging.warning(f"Track {file_name} does not allow streaming") return download_quality = ARG_TO_QUALITY[ @@ -83,14 +82,10 @@ def DownloadCommand( if not noskip and trackExists( track.audioQuality, download_quality, path ): - click.echo( - f"{click.style('✔', 'cyan')} Skipping track {click.style(file_name, 'cyan')}" - ) + logging.info(f"Skipping track {file_name}") return - click.echo( - f"{click.style('✔', 'green')} Downloading track {click.style(file_name, 'green')}" - ) + logging.info(f"Downloading track {file_name}") track_stream = api.getTrackStream(track.id, download_quality) @@ -117,12 +112,10 @@ def DownloadCommand( full_path, track, cover_data=cover_data, credits=credits ) except Exception as e: - click.echo( - f"{click.style('✖', 'yellow')} Cant set metadata to {click.style(file_name, 'yellow')}: {e}" - ) + logging.error(f"Cant set metadata to {file_name}: {e}") def downloadAlbum(album: Album): - click.echo(f"★ Album {album.title}") + logging.info(f"Album {album.title}") cover_data = Cover(album.cover).content if album.cover else b"" offset = 0 @@ -204,7 +197,7 @@ def DownloadCommand( case "playlist": playlist = api.getPlaylist(resource.id) - click.echo(f"★ Playlist {playlist.title}") + logging.info(f"Playlist {playlist.title}") offset = 0 @@ -243,10 +236,10 @@ def DownloadCommand( handleResource(resource) except ApiError as e: - click.echo(click.style(f"✖ {e}", "red")) + logging.error(e) except AuthError as e: - click.echo(click.style(f"✖ {e}", "red")) + logging.error(e) return diff --git a/tiddl/config.py b/tiddl/config.py index 145c22b..8203dad 100644 --- a/tiddl/config.py +++ b/tiddl/config.py @@ -1,3 +1,5 @@ +# TODO: 3.0 change config path to ~/.config/tiddl.json + from pydantic import BaseModel from pathlib import Path