From 038eaf85cbc24166935f1392dc2432f3886a59c8 Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Fri, 19 Jan 2024 08:17:47 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Generalised=20download.py.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/cli.py | 20 ++++----- tidal_dl_ng/download.py | 78 +++++++++++++++++------------------ tidal_dl_ng/gui.py | 33 +++++++-------- tidal_dl_ng/helper/wrapper.py | 2 +- 4 files changed, 65 insertions(+), 68 deletions(-) diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py index 3394232..8b2e80c 100644 --- a/tidal_dl_ng/cli.py +++ b/tidal_dl_ng/cli.py @@ -14,7 +14,7 @@ from tidal_dl_ng.constants import CTX_TIDAL, MediaType from tidal_dl_ng.download import Download from tidal_dl_ng.helper.path import get_format_template, path_file_settings from tidal_dl_ng.helper.tidal import get_tidal_media_id, get_tidal_media_type -from tidal_dl_ng.helper.wrapper import WrapperLogger +from tidal_dl_ng.helper.wrapper import LoggerWrapped from tidal_dl_ng.model.cfg import HelpSettings app = typer.Typer() @@ -135,21 +135,27 @@ def download( # Create initial objects. settings: Settings = Settings() - dl = Download(ctx.obj[CTX_TIDAL].session, ctx.obj[CTX_TIDAL].settings.data.skip_existing) progress: Progress = Progress( "{task.description}", SpinnerColumn(), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), ) - fn_logger = WrapperLogger(progress.print) + fn_logger = LoggerWrapped(progress.print) + dl = Download( + session=ctx.obj[CTX_TIDAL].session, + skip_existing=ctx.obj[CTX_TIDAL].settings.data.skip_existing, + path_base=settings.data.download_base_path, + fn_logger=fn_logger, + progress=progress, + ) progress_table = Table.grid() # Style Progress display. progress_table.add_row(Panel.fit(progress, title="Download Progress", border_style="green", padding=(2, 2))) for item in urls: - media_type: str | bool = False + media_type: MediaType | bool = False # Extract media name and id from link. if "http" in item: @@ -170,21 +176,15 @@ def download( dl.item( media_id=item_id, media_type=media_type, - path_base=settings.data.download_base_path, file_template=file_template, - progress=progress, - fn_logger=fn_logger, ) elif media_type in [MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX]: dl.items( media_id=item_id, media_type=media_type, - path_base=settings.data.download_base_path, file_template=file_template, video_download=ctx.obj[CTX_TIDAL].settings.data.video_download, - progress=progress, download_delay=settings.data.download_delay, - fn_logger=fn_logger, ) # Stop Progress display. diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index a6addb3..863aa20 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -6,7 +6,6 @@ import shutil import tempfile import time from collections.abc import Callable -from logging import Logger from uuid import uuid4 import ffmpeg @@ -31,7 +30,6 @@ from tidal_dl_ng.helper.decryption import decrypt_file, decrypt_security_token from tidal_dl_ng.helper.exceptions import MediaMissing, MediaUnknown, UnknownManifestFormat from tidal_dl_ng.helper.path import check_file_exists, format_path_media, path_file_sanitize from tidal_dl_ng.helper.tidal import items_results_all, name_builder_item -from tidal_dl_ng.helper.wrapper import WrapperLogger from tidal_dl_ng.metadata import Metadata from tidal_dl_ng.model.gui_data import ProgressBars from tidal_dl_ng.model.tidal import StreamManifest @@ -55,30 +53,42 @@ class Download: settings: Settings = None session: Session = None skip_existing: SkipExisting = False + fn_logger: Callable = None + progress_gui: ProgressBars = None + progress: Progress = None - def __init__(self, session: Session, skip_existing: SkipExisting = SkipExisting.Disabled): + def __init__( + self, + session: Session, + path_base: str, + fn_logger: Callable, + skip_existing: SkipExisting = SkipExisting.Disabled, + progress_gui: ProgressBars = None, + progress: Progress = None, + ): self.settings = Settings() self.session = session self.skip_existing = skip_existing + self.fn_logger = fn_logger + self.progress_gui = progress_gui + self.progress = progress + self.path_base = path_base def _download( self, - fn_logger: Callable, media: Track | Video, - progress: Progress, - progress_gui: ProgressBars, stream_manifest: StreamManifest, path_file: str, - ): + ) -> str: media_name: str = name_builder_item(media) # Set the correct progress output channel. - if progress_gui is None: + if self.progress_gui is None: progress_stdout: bool = True else: progress_stdout: bool = False # Send signal to GUI with media name - progress_gui.item_name.emit(media_name[:30]) + self.progress_gui.item_name.emit(media_name[:30]) try: # Compute total iterations for progress @@ -99,14 +109,14 @@ class Download: progress_total: float = total_size_in_bytes / block_size # Create progress Task - p_task: TaskID = progress.add_task( + p_task: TaskID = self.progress.add_task( f"[blue]Item '{media_name[:30]}'", total=progress_total, visible=progress_stdout, ) # Write content to file until progress is finished. - while not progress.tasks[p_task].finished: + while not self.progress.tasks[p_task].finished: with open(path_file, "wb") as f: for url in stream_manifest.urls: # Create the request object with stream=True, so the content won't be loaded into memory at once. @@ -118,14 +128,14 @@ class Download: for data in r.iter_content(chunk_size=block_size): f.write(data) # Advance progress bar. - progress.advance(p_task) + self.progress.advance(p_task) # To send the progress to the GUI, we need to emit the percentage. if not progress_stdout: - progress_gui.item.emit(progress.tasks[p_task].percentage) + self.progress_gui.item.emit(self.progress.tasks[p_task].percentage) except HTTPError as e: # TODO: Handle Exception... - fn_logger(e) + self.fn_logger(e) # Check if file is encrypted. needs_decryption = self.is_encrypted(stream_manifest.encryption_type) @@ -166,15 +176,11 @@ class Download: def item( self, - path_base: str, file_template: str, - fn_logger: Callable, media: Track | Video = None, media_id: str = None, media_type: MediaType = None, video_download: bool = True, - progress_gui: ProgressBars = None, - progress: Progress = None, ) -> (bool, str): # If no media instance is provided, we need to create the media instance. if media_id and media_type: @@ -184,7 +190,7 @@ class Download: # If video download is not allowed end here if not video_download: - fn_logger.info( + self.fn_logger.info( f"Video downloads are deactivated (see settings). Skipping video: {name_builder_item(media)}" ) @@ -192,7 +198,7 @@ class Download: # Create file name and path file_name_relative = format_path_media(file_template, media) - path_file = os.path.abspath(os.path.normpath(os.path.join(path_base, file_name_relative))) + path_file = os.path.abspath(os.path.normpath(os.path.join(self.path_base, file_name_relative))) # Populate StreamManifest for further download. if isinstance(media, Track): @@ -220,7 +226,7 @@ class Download: with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp_path_dir: tmp_path_file = os.path.join(tmp_path_dir, str(uuid4()) + stream_manifest.file_extension) # Download media. - tmp_path_file = self._download(fn_logger, media, progress, progress_gui, stream_manifest, tmp_path_file) + tmp_path_file = self._download(media=media, stream_manifest=stream_manifest, path_file=tmp_path_file) if isinstance(media, Video) and self.settings.data.video_convert_mp4: # Convert `*.ts` file to `*.mp4` using ffmpeg @@ -231,7 +237,7 @@ class Download: os.makedirs(os.path.dirname(path_file), exist_ok=True) shutil.move(tmp_path_file, path_file) else: - fn_logger.debug(f"Download skipped, since file exists: '{path_file}'") + self.fn_logger.debug(f"Download skipped, since file exists: '{path_file}'") return not download_skip, path_file @@ -286,15 +292,11 @@ class Download: def items( self, - path_base: str, - fn_logger: Logger | WrapperLogger, + file_template: str, + media: Album | Playlist | UserPlaylist | Mix = None, media_id: str = None, media_type: MediaType = None, - file_template: str = None, - media: Album | Playlist | UserPlaylist | Mix = None, video_download: bool = False, - progress_gui: ProgressBars = None, - progress: Progress = None, download_delay: bool = True, ): # If no media instance is provided, we need to create the media instance. @@ -321,42 +323,38 @@ class Download: items = items_results_all(media, videos_include=videos_include) # Determine where to redirect the progress information. - if progress_gui is None: + if self.progress_gui is None: progress_stdout: bool = True else: progress_stdout: bool = False - progress_gui.item_name.emit(list_media_name[:30]) + self.progress_gui.item_name.emit(list_media_name[:30]) # Create the list progress task. - p_task1: TaskID = progress.add_task( + p_task1: TaskID = self.progress.add_task( f"[green]List '{list_media_name}'", total=len(items), visible=progress_stdout ) # Iterate through list items - while not progress.finished: + while not self.progress.finished: for media in items: # Download the item. status_download, result_path_file = self.item( - path_base=path_base, - file_template=file_name_relative, media=media, - progress_gui=progress_gui, - progress=progress, - fn_logger=fn_logger, + file_template=file_name_relative, ) # Advance progress bar. - progress.advance(p_task1) + self.progress.advance(p_task1) if not progress_stdout: - progress_gui.list_item.emit(progress.tasks[p_task1].percentage) + self.progress_gui.list_item.emit(self.progress.tasks[p_task1].percentage) # If a file was downloaded and the download delay is enabled, wait until the next download. if download_delay and status_download: time_sleep: float = round(random.SystemRandom().uniform(2, 5), 1) # TODO: Fix logging. Is not displayed in debug window. - fn_logger.debug(f"Next download will start in {time_sleep} seconds.") + self.fn_logger.debug(f"Next download will start in {time_sleep} seconds.") time.sleep(time_sleep) def is_encrypted(self, encryption_type: str) -> bool: diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index e1be127..b8b626e 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -98,7 +98,22 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): break if result: - self.dl = Download(self.tidal.session, self.tidal.settings.data.skip_existing) + # Init `Download` object. + data_pb: ProgressBars = ProgressBars( + item=self.s_item_advance, + list_item=self.s_list_advance, + item_name=self.s_item_name, + list_name=self.s_list_name, + ) + progress: Progress = Progress() + self.dl = Download( + session=self.tidal.session, + skip_existing=self.tidal.settings.data.skip_existing, + path_base=self.settings.data.download_base_path, + fn_logger=logger_gui, + progress_gui=data_pb, + progress=progress, + ) self.thread_it(self.tidal_user_lists) @@ -438,38 +453,22 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.s_pb_reset.emit() self.s_statusbar_message.emit(StatusbarMessage(message="Download started...")) - data_pb: ProgressBars = ProgressBars( - item=self.s_item_advance, - list_item=self.s_list_advance, - item_name=self.s_item_name, - list_name=self.s_list_name, - ) - progress: Progress = Progress() file_template = get_format_template(media, self.settings) if isinstance(media, Track | Video): dl.item( media=media, - path_base=self.settings.data.download_base_path, file_template=file_template, - progress_gui=data_pb, - progress=progress, - fn_logger=logger_gui, ) elif isinstance(media, Album | Playlist | Mix): dl.items( media=media, - path_base=self.settings.data.download_base_path, file_template=file_template, video_download=self.settings.data.video_download, - progress_gui=data_pb, - progress=progress, download_delay=self.settings.data.download_delay, - fn_logger=logger_gui, ) self.s_statusbar_message.emit(StatusbarMessage(message="Download finished.", timout=2000)) - progress.stop() # TODO: Comment with Google Docstrings. diff --git a/tidal_dl_ng/helper/wrapper.py b/tidal_dl_ng/helper/wrapper.py index 5b88f59..d8ffb09 100644 --- a/tidal_dl_ng/helper/wrapper.py +++ b/tidal_dl_ng/helper/wrapper.py @@ -1,7 +1,7 @@ from collections.abc import Callable -class WrapperLogger: +class LoggerWrapped: fn_print: Callable = None def __init__(self, fn_print: Callable):