From 393058bfe063b7ca41bec7e13d4ca3dd9e78754d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3nos?= Date: Mon, 14 Mar 2022 16:04:36 +0100 Subject: [PATCH] download hook Rimosso show_progress e rimpiazzato con un hook --- animeworld/episodio.py | 14 ++- animeworld/server.py | 190 +++++++++++++++++++++++++++++---------- documentation/example.py | 68 +++++++++----- setup.py | 4 +- 4 files changed, 199 insertions(+), 77 deletions(-) diff --git a/animeworld/episodio.py b/animeworld/episodio.py index 7bc4d3b..a83d6db 100644 --- a/animeworld/episodio.py +++ b/animeworld/episodio.py @@ -66,20 +66,28 @@ def links(self) -> List[Server]: # lista dei provider dove sono hostati gli ep return self.__setServer(tmp, self.number) - def download(self, title: Optional[str]=None, folder: str='', show_progress: bool=True) -> Optional[str]: # Scarica l'episodio con il primo link nella lista + def download(self, title: Optional[str]=None, folder: str='', hook: Callable[[Dict], None] = lambda *args:None) -> Optional[str]: # Scarica l'episodio con il primo link nella lista """ Scarica l'episodio dal primo server della lista links. - `title`: Nome con cui verrà nominato il file scaricato. - `folder`: Posizione in cui verrà spostato il file scaricato. - - `show_progress`: Mostra la barra di avanzamento. + - `hook`: Funzione che viene richiamata varie volte durante il download; la funzione riceve come argomento un dizionario con le seguenti chiavi: + - `total_bytes`: Byte totali da scaricare. + - `downloaded_bytes`: Byte attualmente scaricati. + - `percentage`: Percentuale del progresso di download. + - `speed`: Velocità di download (byte/s) + - `elapsed`: Tempo trascorso dall'inizio del download. + - `eta`: Tempo stimato rimanente per fine del download. + - `status`: 'downloading' | 'finished' + - `filename`: Nome del file in download. ``` return str # File scaricato ``` """ - return self.links[0].download(title,folder) + return self.links[0].download(title,folder,hook) # Private def __setServer(self, links: List[Dict], numero: str) -> List[Server]: # Per ogni link li posizioni nelle rispettive classi diff --git a/animeworld/server.py b/animeworld/server.py index 6962eab..bf67253 100644 --- a/animeworld/server.py +++ b/animeworld/server.py @@ -7,7 +7,7 @@ import re import os from typing import * -from alive_progress import alive_bar +import time from .globals import HDR, cookies from .utility import HealthCheck @@ -63,13 +63,21 @@ def _sanitize(self, title: str) -> str: # Toglie i caratteri illegali per i file title = title.replace(x, '') return title - def download(self, title: Optional[str]=None, folder: str='', show_progress: bool=True) -> Optional[str]: + def download(self, title: Optional[str]=None, folder: str='', hook: Callable[[Dict], None] = lambda *args:None) -> Optional[str]: """ Scarica l'episodio. - `title`: Nome con cui verrà nominato il file scaricato. - `folder`: Posizione in cui verrà spostato il file scaricato. - - `show_progress`: Mostra la barra di avanzamento. + - `hook`: Funzione che viene richiamata varie volte durante il download; la funzione riceve come argomento un dizionario con le seguenti chiavi: + - `total_bytes`: Byte totali da scaricare. + - `downloaded_bytes`: Byte attualmente scaricati. + - `percentage`: Percentuale del progresso di download. + - `speed`: Velocità di download (byte/s) + - `elapsed`: Tempo trascorso dall'inizio del download. + - `eta`: Tempo stimato rimanente per fine del download. + - `status`: 'downloading' | 'finished' + - `filename`: Nome del file in download. ``` return str # File scaricato @@ -78,13 +86,21 @@ def download(self, title: Optional[str]=None, folder: str='', show_progress: boo raise ServerNotSupported(self.name) # Protected - def _downloadIn(self, title: str, folder: str, show_progress: bool) -> bool: # Scarica l'episodio + def _downloadIn(self, title: str, folder: str, hook: Callable[[Dict], None]) -> bool: # Scarica l'episodio """ Scarica il file utilizzando requests. - `title`: Nome con cui verrà nominato il file scaricato. - `folder`: Posizione in cui verrà spostato il file scaricato. - - `show_progress`: Mostra la barra di avanzamento. + - `hook`: Funzione che viene richiamata varie volte durante il download; la funzione riceve come argomento un dizionario con le seguenti chiavi: + - `total_bytes`: Byte totali da scaricare. + - `downloaded_bytes`: Byte attualmente scaricati. + - `percentage`: Percentuale del progresso di download. + - `speed`: Velocità di download (byte/s) + - `elapsed`: Tempo trascorso dall'inizio del download. + - `eta`: Tempo stimato rimanente per fine del download. + - `status`: 'downloading' | 'finished' + - `filename`: Nome del file in download. ``` return bool # File scaricato @@ -97,57 +113,101 @@ def _downloadIn(self, title: str, folder: str, show_progress: bool) -> bool: # S file = f"{title}.{ext}" total_length = int(r.headers.get('content-length')) - with open(f"{os.path.join(folder,file)}", 'wb') as f, alive_bar(total_length//65536+1, disable = not show_progress, length = 80, monitor = "[{percent:.0%}]", stats ="(ETA: {eta})", stats_end=False) as bar: - for chunk in r.iter_content(chunk_size = 65536): + current_lenght = 0 + start = time.time() + step = time.time() + + with open(f"{os.path.join(folder,file)}", 'wb') as f: + for chunk in r.iter_content(chunk_size = 524288): if chunk: f.write(chunk) f.flush() - bar() + + current_lenght += len(chunk) + + hook({ + 'total_bytes': total_length, + 'downloaded_bytes': current_lenght, + 'percentage': current_lenght/total_length, + 'speed': len(chunk) / (time.time() - step) if (time.time() - step) != 0 else 0, + 'elapsed': time.time() - start, + 'filename': file, + 'eta': ((total_length - current_lenght) / len(chunk)) * (time.time() - step), + 'status': 'downloading' + }) + + step = time.time() + else: + hook({ + 'total_bytes': total_length, + 'downloaded_bytes': total_length, + 'percentage': 1, + 'speed': 0, + 'elapsed': time.time() - start, + 'eta': 0, + 'status': 'finished' + }) + return file # Se il file è stato scaricato correttamente return None # Se è accaduto qualche imprevisto # Protected - def _dowloadEx(self, title: str, folder: str, show_progress: bool) -> Optional[str]: + def _dowloadEx(self, title: str, folder: str, hook: Callable[[Dict], None]) -> Optional[str]: """ Scarica il file utilizzando yutube_dl. - `title`: Nome con cui verrà nominato il file scaricato. - `folder`: Posizione in cui verrà spostato il file scaricato. - - `show_progress`: Mostra la barra di avanzamento. + - `hook`: Funzione che viene richiamata varie volte durante il download; la funzione riceve come argomento un dizionario con le seguenti chiavi: + - `total_bytes`: Byte totali da scaricare. + - `downloaded_bytes`: Byte attualmente scaricati. + - `percentage`: Percentuale del progresso di download. + - `speed`: Velocità di download (byte/s) + - `elapsed`: Tempo trascorso dall'inizio del download. + - `eta`: Tempo stimato rimanente per fine del download. + - `status`: 'downloading' | 'finished' + - `filename`: Nome del file in download. ``` return str # File scaricato ``` """ - with alive_bar(disable = not show_progress, length = 80, monitor = "[{percent:.0%}]", stats ="(ETA: {eta})", stats_end=False, manual=True) as bar: - class MyLogger(object): - def debug(self, msg): - pass - def warning(self, msg): - pass - def error(self, msg): - print(msg) - return False - - def my_hook(d): - if d['status'] == 'downloading': - bar(float(d['downloaded_bytes'])/float(d['total_bytes_estimate'])) - if d['status'] == 'finished': - return True - - ydl_opts = { - 'outtmpl': f"{os.path.join(folder,title)}.%(ext)s", - 'logger': MyLogger(), - 'progress_hooks': [my_hook], - } - with youtube_dl.YoutubeDL(ydl_opts) as ydl: - url = self._getFileLink() - info = ydl.extract_info(url, download=False) - filename = ydl.prepare_filename(info) - ydl.download([url]) - return filename + class MyLogger(object): + def debug(self, msg): + pass + def warning(self, msg): + pass + def error(self, msg): + print(msg) + return False + + def my_hook(d): + hook({ + 'total_bytes': int(d['total_bytes_estimate']), + 'downloaded_bytes': int(d['downloaded_bytes']), + 'percentage': int(d['downloaded_bytes'])/int(d['total_bytes_estimate']), + 'speed': float(d['speed']) if d['speed'] is not None else 0, + 'elapsed': float(d['elapsed']), + 'filename': d['filename'], + 'eta': int(d['eta']), + 'status': d['status'] + }) + if d['status'] == 'finished': + return True + + ydl_opts = { + 'outtmpl': f"{os.path.join(folder,title)}.%(ext)s", + 'logger': MyLogger(), + 'progress_hooks': [my_hook], + } + with youtube_dl.YoutubeDL(ydl_opts) as ydl: + url = self._getFileLink() + info = ydl.extract_info(url, download=False) + filename = ydl.prepare_filename(info) + ydl.download([url]) + return filename class AnimeWorld_Server(Server): @@ -157,13 +217,21 @@ def _getFileLink(self): return self.link.replace('download-file.php?id=', '') - def download(self, title: Optional[str]=None, folder: str='', show_progress: bool=True) -> bool: + def download(self, title: Optional[str]=None, folder: str='', hook: Callable[[Dict], None] = lambda *args:None) -> bool: """ Scarica l'episodio. - `title`: Nome con cui verrà nominato il file scaricato. - `folder`: Posizione in cui verrà spostato il file scaricato. - - `show_progress`: Mostra la barra di avanzamento. + - `hook`: Funzione che viene richiamata varie volte durante il download; la funzione riceve come argomento un dizionario con le seguenti chiavi: + - `total_bytes`: Byte totali da scaricare. + - `downloaded_bytes`: Byte attualmente scaricati. + - `percentage`: Percentuale del progresso di download. + - `speed`: Velocità di download (byte/s) + - `elapsed`: Tempo trascorso dall'inizio del download. + - `eta`: Tempo stimato rimanente per fine del download. + - `status`: 'downloading' | 'finished' + - `filename`: Nome del file in download. ``` return bool # File scaricato @@ -171,7 +239,7 @@ def download(self, title: Optional[str]=None, folder: str='', show_progress: boo """ if title is None: title = self._defTitle else: title = self._sanitize(title) - return self._downloadIn(title,folder,show_progress) + return self._downloadIn(title,folder,hook) class VVVVID(Server): # Protected @@ -191,13 +259,21 @@ def _getFileLink(self): raw = soupeddata.find("a", { "class" : "VVVVID-link" }) return raw.get("href") - def download(self, title: Optional[str]=None, folder: str='', show_progress: bool=True) -> bool: + def download(self, title: Optional[str]=None, folder: str='', hook: Callable[[Dict], None] = lambda *args:None) -> bool: """ Scarica l'episodio. - `title`: Nome con cui verrà nominato il file scaricato. - `folder`: Posizione in cui verrà spostato il file scaricato. - - `show_progress`: Mostra la barra di avanzamento. + - `hook`: Funzione che viene richiamata varie volte durante il download; la funzione riceve come argomento un dizionario con le seguenti chiavi: + - `total_bytes`: Byte totali da scaricare. + - `downloaded_bytes`: Byte attualmente scaricati. + - `percentage`: Percentuale del progresso di download. + - `speed`: Velocità di download (byte/s) + - `elapsed`: Tempo trascorso dall'inizio del download. + - `eta`: Tempo stimato rimanente per fine del download. + - `status`: 'downloading' | 'finished' + - `filename`: Nome del file in download. ``` return bool # File scaricato @@ -205,7 +281,7 @@ def download(self, title: Optional[str]=None, folder: str='', show_progress: boo """ if title is None: title = self._defTitle else: title = self._sanitize(title) - return self._dowloadEx(title,folder,show_progress) + return self._dowloadEx(title,folder,hook) class YouTube(Server): @@ -228,13 +304,21 @@ def _getFileLink(self): return yutubelink_raw.replace('embed/', 'watch?v=') - def download(self, title: Optional[str]=None, folder: str='', show_progress: bool=True) -> bool: + def download(self, title: Optional[str]=None, folder: str='', hook: Callable[[Dict], None] = lambda *args:None) -> bool: """ Scarica l'episodio. - `title`: Nome con cui verrà nominato il file scaricato. - `folder`: Posizione in cui verrà spostato il file scaricato. - - `show_progress`: Mostra la barra di avanzamento. + - `hook`: Funzione che viene richiamata varie volte durante il download; la funzione riceve come argomento un dizionario con le seguenti chiavi: + - `total_bytes`: Byte totali da scaricare. + - `downloaded_bytes`: Byte attualmente scaricati. + - `percentage`: Percentuale del progresso di download. + - `speed`: Velocità di download (byte/s) + - `elapsed`: Tempo trascorso dall'inizio del download. + - `eta`: Tempo stimato rimanente per fine del download. + - `status`: 'downloading' | 'finished' + - `filename`: Nome del file in download. ``` return bool # File scaricato @@ -242,7 +326,7 @@ def download(self, title: Optional[str]=None, folder: str='', show_progress: boo """ if title is None: title = self._defTitle else: title = self._sanitize(title) - return self._dowloadEx(title,folder,show_progress) + return self._dowloadEx(title,folder,hook) class Streamtape(Server): # Protected @@ -268,13 +352,21 @@ def _getFileLink(self): return mp4_link - def download(self, title: Optional[str]=None, folder: str='', show_progress: bool=True) -> bool: + def download(self, title: Optional[str]=None, folder: str='', hook: Callable[[Dict], None] = lambda *args:None) -> bool: """ Scarica l'episodio. - `title`: Nome con cui verrà nominato il file scaricato. - `folder`: Posizione in cui verrà spostato il file scaricato. - - `show_progress`: Mostra la barra di avanzamento. + - `hook`: Funzione che viene richiamata varie volte durante il download; la funzione riceve come argomento un dizionario con le seguenti chiavi: + - `total_bytes`: Byte totali da scaricare. + - `downloaded_bytes`: Byte attualmente scaricati. + - `percentage`: Percentuale del progresso di download. + - `speed`: Velocità di download (byte/s) + - `elapsed`: Tempo trascorso dall'inizio del download. + - `eta`: Tempo stimato rimanente per fine del download. + - `status`: 'downloading' | 'finished' + - `filename`: Nome del file in download. ``` return bool # File scaricato @@ -282,4 +374,4 @@ def download(self, title: Optional[str]=None, folder: str='', show_progress: boo """ if title is None: title = self._defTitle else: title = self._sanitize(title) - return self._downloadIn(title,folder,show_progress) \ No newline at end of file + return self._downloadIn(title,folder,hook) \ No newline at end of file diff --git a/documentation/example.py b/documentation/example.py index 0cbcdbb..90e0591 100644 --- a/documentation/example.py +++ b/documentation/example.py @@ -1,34 +1,56 @@ import animeworld as aw +from datetime import datetime + + +def my_hook(d): + """ + Stampa una ProgressBar con tutte le informazioni di download. + """ + if d['status'] == 'downloading': + out = "{filename}:\n[{bar}][{percentage:^6.1%}]\n{total_bytes}/{downloaded_bytes} in {elapsed:%H:%M:%S} (ETA: {eta:%H:%M:%S})\x1B[3A" + + width = 70 # grandezza progressbar + + d['elapsed'] = datetime.utcfromtimestamp(d['elapsed']) + d['eta'] = datetime.utcfromtimestamp(d['eta']) + d['bar'] = '#'*int(width*d['percentage']) + ' '*(width-int(width*d['percentage'])) + + print(out.format(**d)) + + elif d['status'] == 'finished': + print('\n\n\n') + try: - anime = aw.Anime(link="https://www.animeworld.tv/play/jaku-chara-tomozaki-kun.RDPHq") - - print("Titolo:", anime.getName()) # Titolo dell'anime + anime = aw.Anime(link="https://www.animeworld.tv/play/jaku-chara-tomozaki-kun.RDPHq") + + print("Titolo:", anime.getName()) # Titolo dell'anime - print("\n----------------------------------\n") + print("\n----------------------------------\n") - print("Trama:", anime.getTrama()) # Trama + print("Trama:", anime.getTrama()) # Trama - print("\n----------------------------------\n") + print("\n----------------------------------\n") - print("Info:") - info = anime.getInfo() - for x in info: print(f"{x}: {info[x]}") # Informazioni presenti su Animeworld riguardanti l'anime + print("Info:") + info = anime.getInfo() + for x in info: print(f"{x}: {info[x]}") # Informazioni presenti su Animeworld riguardanti l'anime - print("\n----------------------------------\n") + print("\n----------------------------------\n") - print("Episodi:") - try: - episodi = anime.getEpisodes() - except (aw.ServerNotSupported, aw.AnimeNotAvailable) as error: - print("Errore:", error) - else: - for x in episodi: - print(f"\n-> Ep. {x.number}") - for k in x.links: - print(f"\t{k.name} - {k.link}") + print("Episodi:") + try: + episodi = anime.getEpisodes() + except (aw.ServerNotSupported, aw.AnimeNotAvailable) as error: + print("Errore:", error) + else: + for x in episodi: + print(f"\n-> Ep. {x.number}") + for k in x.links: + print(f"\t{k.name} - {k.link}") - if x.number == '1': - x.download() + if x.number == '1': + x.download(hook=my_hook) + break except (aw.DeprecatedLibrary, aw.Error404) as error: - print(error) + print(error) diff --git a/setup.py b/setup.py index 680cbee..96a1f67 100644 --- a/setup.py +++ b/setup.py @@ -5,14 +5,14 @@ setuptools.setup( name="animeworld", - version="1.4.12", + version="1.4.13", author="MainKronos", description="AnimeWorld UNOFFICIAL API", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/MainKronos/AnimeWorld-API", packages=setuptools.find_packages(), - install_requires=['requests', 'youtube_dl', 'beautifulsoup4', 'alive_progress'], + install_requires=['requests', 'youtube_dl', 'beautifulsoup4'], license='MIT', classifiers=[ "Programming Language :: Python :: 3",