From c622cbbdf02852675fd0f05b4472a68fa55f6e17 Mon Sep 17 00:00:00 2001 From: Dark1291 Date: Wed, 5 Feb 2025 13:20:20 +0100 Subject: [PATCH] Add USSR (#247) * Add betterer formattation and ffmpeg installation testing * Update readme * Better formattation * Update readme with tg * Change tg folder * Add RRC * Rename ... --- README.md | 108 +- .../Api/Site/1337xx/__init__.py | 30 +- StreamingCommunity/Api/Site/1337xx/site.py | 25 - StreamingCommunity/Api/Site/1337xx/title.py | 20 +- .../Site/altadefinizionegratis/__init__.py | 45 +- .../Api/Site/altadefinizionegratis/costant.py | 4 +- .../Api/Site/altadefinizionegratis/film.py | 61 +- .../Api/Site/altadefinizionegratis/site.py | 20 +- .../Api/Site/animeunity/__init__.py | 47 +- .../Api/Site/animeunity/costant.py | 4 +- .../Api/Site/animeunity/film_serie.py | 68 +- .../Api/Site/animeunity/site.py | 21 +- StreamingCommunity/Api/Site/cb01new/film.py | 26 +- .../Api/Site/guardaserie/series.py | 37 +- .../Api/Site/mostraguarda/film.py | 31 +- .../Api/Site/streamingcommunity/__init__.py | 41 +- .../Api/Site/streamingcommunity/costant.py | 4 +- .../Api/Site/streamingcommunity/film.py | 64 +- .../Api/Site/streamingcommunity/series.py | 128 +- .../Api/Site/streamingcommunity/site.py | 23 +- StreamingCommunity/HelpTg/config.json | 91 -- StreamingCommunity/HelpTg/request_manager.py | 79 -- StreamingCommunity/HelpTg/requirements.txt | 14 - .../Lib/Downloader/HLS/downloader.py | 1249 ++++++----------- .../Lib/Downloader/HLS/segments.py | 18 +- .../Lib/Downloader/MP4/downloader.py | 51 +- .../Lib/Downloader/TOR/downloader.py | 1 + StreamingCommunity/Lib/FFmpeg/command.py | 8 +- StreamingCommunity/Lib/M3U8/estimator.py | 16 +- .../{HelpTg => TelegramHelp}/__init__.py | 0 .../TelegramHelp/request_manager.py | 82 ++ .../{HelpTg => TelegramHelp}/session.py | 20 +- .../{HelpTg => TelegramHelp}/telegram_bot.py | 372 ++--- StreamingCommunity/Upload/update.py | 5 +- StreamingCommunity/Upload/version.py | 2 +- StreamingCommunity/Util/_jsonConfig.py | 2 +- StreamingCommunity/Util/message.py | 46 +- StreamingCommunity/Util/os.py | 5 +- StreamingCommunity/Util/table.py | 2 +- StreamingCommunity/run.py | 99 +- Test/Download/HLS.py | 4 +- Test/Util/ffmpegVersion.py | 29 + Test/Util/{oss.py => osPath.py} | 0 config.json | 16 +- requirements.txt | 6 +- test_run.py | 20 +- 46 files changed, 1203 insertions(+), 1841 deletions(-) delete mode 100644 StreamingCommunity/HelpTg/config.json delete mode 100644 StreamingCommunity/HelpTg/request_manager.py delete mode 100644 StreamingCommunity/HelpTg/requirements.txt rename StreamingCommunity/{HelpTg => TelegramHelp}/__init__.py (100%) create mode 100644 StreamingCommunity/TelegramHelp/request_manager.py rename StreamingCommunity/{HelpTg => TelegramHelp}/session.py (69%) mode change 100755 => 100644 rename StreamingCommunity/{HelpTg => TelegramHelp}/telegram_bot.py (58%) create mode 100644 Test/Util/ffmpegVersion.py rename Test/Util/{oss.py => osPath.py} (100%) diff --git a/README.md b/README.md index 2a17245..34e7bec 100644 --- a/README.md +++ b/README.md @@ -9,23 +9,23 @@ Donate - - Commits + + Commits - - Last Commit + + Last Commit

- + License PyPI Downloads - - Lines of Code + + Lines of Code

@@ -44,7 +44,10 @@ - πŸ“© [Request](#requests-settings) - πŸ“₯ [Download](#m3u8_download-settings) - πŸ” [Parser](#m3u8_parser-settings) +- πŸ“ [Command](#command) +- πŸ’» [Examples of terminal](#examples-of-terminal-usage) - 🐳 [Docker](#docker) +- πŸ“ [Telegram Usage](#telegram-usage) - πŸŽ“ [Tutorial](#tutorials) - πŸ“ [To do](#to-do) - πŸ’¬ [Support](#support) @@ -55,16 +58,13 @@ # Installation

- + Windows - - PyPI - - + Source Tarball - + All Versions

@@ -286,25 +286,37 @@ The configuration file is divided into several main sections: `

` - `movie_folder_name`: The name of the subdirectory where movies will be stored. + * Can be changed from terminal with `--movie_folder_name` +

+ - `serie_folder_name`: The name of the subdirectory where TV series will be stored. + * Can be changed from terminal with `--serie_folder_name` +

- `map_episode_name`: Template for TV series episode filenames ### Episode name usage: You can choose different vars: - - * `%(tv_name)` : Is the name of TV Show * `%(season)` : Is the number of the season * `%(episode)` : Is the number of the episode * `%(episode_name)` : Is the name of the episode `

` + * Can be changed from terminal with `--map_episode_name` +

- `add_siteName`: If set to true, appends the site_name to the root path before the movie and serie folders. -- `disable_searchDomain`: If set to true, disables the search for a new domain for all sites. -- `not_close`: If set to true, keeps the program running after the download is complete. + * Can be changed from terminal with `--add_siteName true/false` +

+- `disable_searchDomain`: If set to true, disables the search for a new domain for all sites. + * Can be changed from terminal with `--disable_searchDomain true/false` +

+ +- `not_close`: If set to true, keeps the program running after the download is complete. + * Can be changed from terminal with `--not_close true/false` +

### qBittorrent Configuration @@ -350,7 +362,13 @@ The configuration file is divided into several main sections: - `tqdm_delay`: Delay between progress bar updates - `tqdm_use_large_bar`: Use detailed progress bar (recommended for desktop) set to false for mobile - `default_video_workser`: Number of threads for video download + * Can be changed from terminal with `--default_video_worker ` +

+ - `default_audio_workser`: Number of threads for audio download + * Can be changed from terminal with `--default_audio_worker ` +

+ - `cleanup_tmp_folder`: Remove temporary .ts files after download > [!IMPORTANT] @@ -361,6 +379,9 @@ The configuration file is divided into several main sections: ### Language Settings The following codes can be used for `specific_list_audio` and `specific_list_subtitles`: +* Can be changed from terminal with `--specific_list_audio ita,eng` for audio +* Can be changed from terminal with `--specific_list_subtitles eng,spa` for subtitles + ``` ara - Arabic eng - English ita - Italian por - Portuguese @@ -432,6 +453,21 @@ You can download VLC Media Player from the [official website](https://www.videol - Enter a season number followed by `-*` to download from that season to the end. * **Example:** `3-*` will download from *Season 3* to the final season. +# Examples of terminal usage + +```bash +# Change video and audio workers +python test_run.py --default_video_worker 8 --default_audio_worker 8 + +# Set specific languages +python test_run.py --specific_list_audio ita,eng --specific_list_subtitles eng,spa + +# Keep console open after download +python test_run.py --not_close true + +# Disable domain search and add site name +python test_run.py --disable_searchDomain true --add_siteName true +``` # Docker @@ -466,15 +502,43 @@ make LOCAL_DIR=/path/to/download run-container The `run-container` command mounts also the `config.json` file, so any change to the configuration file is reflected immediately without having to rebuild the image. +# Telegram Usage + +## Configuration + +You need to create an .env file and enter your Telegram token + +and user ID to authorize only one user to use it + +## .env Example: + +``` +TOKEN_TELEGRAM=IlTuo2131TOKEN$12D3Telegram +AUTHORIZED_USER_ID=12345678 +DEBUG=False +``` + +## Install Python Dependencies + +```bash +pip install -r requirements.txt +``` + +## On Linux/MacOS: + +```bash +python3 telegram_bot.py +``` + # Website Status | Website | Status | |:-------------------|:------:| | [1337xx](https://1337xx.to/) | βœ… | -| [AltadefinizioneGratis](https://altadefinizionegratis.site/) | βœ… | +| [AltadefinizioneGratis](https://altadefinizionegratis.pro/) | βœ… | | [AnimeUnity](https://animeunity.so/) | βœ… | | [Ilcorsaronero](https://ilcorsaronero.link/) | βœ… | -| [CB01New](https://cb01new.video/) | βœ… | +| [CB01New](https://cb01new.media/) | βœ… | | [DDLStreamItaly](https://ddlstreamitaly.co/) | βœ… | | [GuardaSerie](https://guardaserie.meme/) | βœ… | | [MostraGuarda](https://mostraguarda.stream/) | βœ… | @@ -490,7 +554,7 @@ The `run-container` command mounts also the `config.json` file, so any change to # To Do -- Finish [website API](https://github.com/Lovi-0/StreamingCommunity/tree/test_gui_1) +- Finish [website API](https://github.com/Abc-dc/StreamingCommunity/tree/test_gui_1) # Contributing @@ -508,6 +572,6 @@ This software is provided "as is", without warranty of any kind, express or impl ## Contributors - - Contributors + + Contributors diff --git a/StreamingCommunity/Api/Site/1337xx/__init__.py b/StreamingCommunity/Api/Site/1337xx/__init__.py index bad0b7c..f0d5725 100644 --- a/StreamingCommunity/Api/Site/1337xx/__init__.py +++ b/StreamingCommunity/Api/Site/1337xx/__init__.py @@ -1,5 +1,7 @@ # 02.07.24 +import sys +import subprocess from urllib.parse import quote_plus @@ -11,12 +13,6 @@ from StreamingCommunity.Util.console import console, msg from .site import title_search, run_get_select_title, media_search_manager from .title import download_title -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') -import sys -import subprocess # Variable indice = 8 @@ -31,25 +27,9 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False): """ Main function of the application for film and series. """ - if TELEGRAM_BOT: - bot = get_bot_instance() - - # Chiedi la scelta all'utente con il bot Telegram - string_to_search = bot.ask( - "key_search", - f"Inserisci la parola da cercare\noppure πŸ”™ back per tornare alla scelta: ", - None - ) - - if string_to_search == 'back': - # Riavvia lo script - # Chiude il processo attuale e avvia una nuova istanza dello script - subprocess.Popen([sys.executable] + sys.argv) - sys.exit() - else: - if string_to_search is None: + if string_to_search is None: string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip() - + # Search on database len_database = title_search(quote_plus(string_to_search)) @@ -69,4 +49,4 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False): console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}") # Retry - search() + search() \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/1337xx/site.py b/StreamingCommunity/Api/Site/1337xx/site.py index a472616..6d683cb 100644 --- a/StreamingCommunity/Api/Site/1337xx/site.py +++ b/StreamingCommunity/Api/Site/1337xx/site.py @@ -17,10 +17,6 @@ from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template.Util import search_domain from StreamingCommunity.Api.Template.Class.SearchType import MediaManager -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') - # Variable from .costant import SITE_NAME, DOMAIN_NOW @@ -40,10 +36,6 @@ def title_search(word_to_search: str) -> int: Returns: - int: The number of titles found. """ - - if TELEGRAM_BOT: - bot = get_bot_instance() - media_search_manager.clear() table_show_manager.clear() @@ -69,10 +61,6 @@ def title_search(word_to_search: str) -> int: # Create soup and find table soup = BeautifulSoup(response.text, "html.parser") - if TELEGRAM_BOT: - # Inizializza la lista delle scelte - choices = [] - for tr in soup.find_all('tr'): try: @@ -85,22 +73,9 @@ def title_search(word_to_search: str) -> int: 'size': tr.find_all("td")[-2].get_text(strip=True) } - if TELEGRAM_BOT: - # Crea una stringa formattata per ogni scelta con numero - choice_text = f"{len(choices)} - {title_info.get('name')} ({title_info.get('type')}) - {title_info.get('date')}" - choices.append(choice_text) - - media_search_manager.add_media(title_info) - except Exception as e: print(f"Error parsing a film entry: {e}") - if TELEGRAM_BOT: - # Se ci sono scelte, inviale a Telegram - if choices: - # Invio a telegram la lista - bot.send_message(f"Lista dei risultati:", choices) - # Return the number of titles found return media_search_manager.get_length() diff --git a/StreamingCommunity/Api/Site/1337xx/title.py b/StreamingCommunity/Api/Site/1337xx/title.py index c299f40..c0fd43e 100644 --- a/StreamingCommunity/Api/Site/1337xx/title.py +++ b/StreamingCommunity/Api/Site/1337xx/title.py @@ -23,11 +23,6 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem # Config from .costant import DOMAIN_NOW, SITE_NAME, MOVIE_FOLDER -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.HelpTg.session import get_session, updateScriptId, deleteScriptId -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') def download_title(select_title: MediaItem): """ @@ -36,13 +31,6 @@ def download_title(select_title: MediaItem): Parameters: - select_title (MediaItem): The media item to be downloaded. This should be an instance of the MediaItem class, containing attributes like `name` and `url`. """ - if TELEGRAM_BOT: - bot = get_bot_instance() - bot.send_message(f"Download in corso:\n{select_title.name}", None) - script_id = get_session() - if script_id != "unknown": - updateScriptId(script_id, select_title.name) - start_message() console.print(f"[yellow]Download: [red]{select_title.name} \n") print() @@ -72,10 +60,4 @@ def download_title(select_title: MediaItem): manager = TOR_downloader() manager.add_magnet_link(final_url) manager.start_download() - manager.move_downloaded_files(mp4_path) - - if TELEGRAM_BOT: - # Delete script_id - script_id = get_session() - if script_id != "unknown": - deleteScriptId(script_id) + manager.move_downloaded_files(mp4_path) \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py b/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py index 40c86df..df2737f 100644 --- a/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py +++ b/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py @@ -1,20 +1,19 @@ # 26.05.24 +import sys +import subprocess from urllib.parse import quote_plus # Internal utilities from StreamingCommunity.Util.console import console, msg +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance # Logic class from .site import title_search, run_get_select_title, media_search_manager from .film import download_film -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') # Variable indice = 2 @@ -22,33 +21,34 @@ _useFor = "film" _deprecate = False _priority = 2 _engineDownload = "hls" -from .costant import SITE_NAME +from .costant import SITE_NAME, TELEGRAM_BOT def search(string_to_search: str = None, get_onylDatabase: bool = False): """ Main function of the application for film and series. """ - if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() - if string_to_search is None: - # Chiedi la scelta all'utente con il bot Telegram - string_to_search = bot.ask( - "key_search", - f"Inserisci la parola da cercare\noppure πŸ”™ back per tornare alla scelta: ", - None - ) + if string_to_search is None: + + # Chiedi la scelta all'utente con il bot Telegram + string_to_search = bot.ask( + "key_search", + f"Inserisci la parola da cercare\noppure πŸ”™ back per tornare alla scelta: ", + None + ) + + if string_to_search == 'back': + # Riavvia lo script + # Chiude il processo attuale e avvia una nuova istanza dello script + subprocess.Popen([sys.executable] + sys.argv) + sys.exit() - if string_to_search == 'back': - # Riavvia lo script - # Chiude il processo attuale e avvia una nuova istanza dello script - subprocess.Popen([sys.executable] + sys.argv) - sys.exit() else: - if string_to_search is None: - string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip() + if string_to_search is None: + string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip() # Search on database len_database = title_search(quote_plus(string_to_search)) @@ -66,9 +66,8 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False): download_film(select_title) else: - if TELEGRAM_BOT: - bot.send_message(f"Nessun risultato trovato riprova", None) + bot.send_message(f"Nessun risultato trovato riprova", None) console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}") diff --git a/StreamingCommunity/Api/Site/altadefinizionegratis/costant.py b/StreamingCommunity/Api/Site/altadefinizionegratis/costant.py index cb76a8b..e4d4d9a 100644 --- a/StreamingCommunity/Api/Site/altadefinizionegratis/costant.py +++ b/StreamingCommunity/Api/Site/altadefinizionegratis/costant.py @@ -16,4 +16,6 @@ MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_fold if config_manager.get_bool("DEFAULT", "add_siteName"): SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name')) - MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name')) \ No newline at end of file + MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name')) + +TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/altadefinizionegratis/film.py b/StreamingCommunity/Api/Site/altadefinizionegratis/film.py index 7a91b53..d7ff463 100644 --- a/StreamingCommunity/Api/Site/altadefinizionegratis/film.py +++ b/StreamingCommunity/Api/Site/altadefinizionegratis/film.py @@ -1,19 +1,18 @@ # 26.05.24 import os -import time # Internal utilities -from StreamingCommunity.Util.console import console, msg +from StreamingCommunity.Util.console import console from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.message import start_message -from StreamingCommunity.Util.call_stack import get_call_stack from StreamingCommunity.Lib.Downloader import HLS_Downloader +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance +from StreamingCommunity.TelegramHelp.session import get_session, updateScriptId, deleteScriptId # Logic class -from StreamingCommunity.Api.Template.Util import execute_search from StreamingCommunity.Api.Template.Class.SearchType import MediaItem @@ -22,13 +21,7 @@ from StreamingCommunity.Api.Player.supervideo import VideoSource # Config -from .costant import MOVIE_FOLDER - -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.HelpTg.session import get_session, updateScriptId, deleteScriptId -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') +from .costant import MOVIE_FOLDER, TELEGRAM_BOT def download_film(select_title: MediaItem) -> str: @@ -42,17 +35,14 @@ def download_film(select_title: MediaItem) -> str: Return: - str: output path """ - if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() + bot.send_message(f"Download in corso:\n{select_title.name}", None) - # Invio a telegram - bot.send_message(f"Download in corso:\n{select_title.name}", None) - - # Get script_id - script_id = get_session() - if script_id != "unknown": - updateScriptId(script_id, select_title.name) + # Get script_id + script_id = get_session() + if script_id != "unknown": + updateScriptId(script_id, select_title.name) # Start message and display film information start_message() @@ -71,26 +61,21 @@ def download_film(select_title: MediaItem) -> str: # Download the film using the m3u8 playlist, and output filename r_proc = HLS_Downloader( - m3u8_playlist=master_playlist, - output_filename=os.path.join(mp4_path, title_name) + m3u8_url=master_playlist, + output_path=os.path.join(mp4_path, title_name) ).start() - - """if r_proc == 404: - time.sleep(2) - - # Re call search function - if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n": - frames = get_call_stack() - execute_search(frames[-4])""" if TELEGRAM_BOT: - # Delete script_id - script_id = get_session() - if script_id != "unknown": - deleteScriptId(script_id) + + # Delete script_id + script_id = get_session() + if script_id != "unknown": + deleteScriptId(script_id) - if r_proc != None: - console.print("[green]Result: ") - console.print(r_proc) + if "error" in r_proc.keys(): + try: + os.remove(r_proc['path']) + except: + pass - return os.path.join(mp4_path, title_name) \ No newline at end of file + return r_proc['path'] \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/altadefinizionegratis/site.py b/StreamingCommunity/Api/Site/altadefinizionegratis/site.py index 8c03bbe..3b1a1a3 100644 --- a/StreamingCommunity/Api/Site/altadefinizionegratis/site.py +++ b/StreamingCommunity/Api/Site/altadefinizionegratis/site.py @@ -26,7 +26,7 @@ max_timeout = config_manager.get_int("REQUESTS", "timeout") disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain") # Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance from StreamingCommunity.Util._jsonConfig import config_manager TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') @@ -42,7 +42,7 @@ def title_search(title_search: str) -> int: int: The number of titles found. """ if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() media_search_manager.clear() table_show_manager.clear() @@ -71,9 +71,9 @@ def title_search(title_search: str) -> int: # Create soup and find table soup = BeautifulSoup(response.text, "html.parser") + # Inizializza la lista delle scelte if TELEGRAM_BOT: - # Inizializza la lista delle scelte - choices = [] + choices = [] for row in soup.find_all('div', class_='col-lg-3 col-md-3 col-xs-4'): try: @@ -94,19 +94,17 @@ def title_search(title_search: str) -> int: media_search_manager.add_media(film_info) if TELEGRAM_BOT: - # Crea una stringa formattata per ogni scelta con numero - choice_text = f"{len(choices)} - {film_info.get('name')} ({film_info.get('url')}) {film_info.get('score')}" - choices.append(choice_text) + # Crea una stringa formattata per ogni scelta con numero + choice_text = f"{len(choices)} - {film_info.get('name')} ({film_info.get('url')}) {film_info.get('score')}" + choices.append(choice_text) except AttributeError as e: print(f"Error parsing a film entry: {e}") if TELEGRAM_BOT: - # Se ci sono scelte, inviale a Telegram - if choices: - # Invio a telegram la lista - bot.send_message(f"Lista dei risultati:", choices) + if choices: + bot.send_message(f"Lista dei risultati:", choices) # Return the number of titles found return media_search_manager.get_length() diff --git a/StreamingCommunity/Api/Site/animeunity/__init__.py b/StreamingCommunity/Api/Site/animeunity/__init__.py index 6c83ca5..5a12833 100644 --- a/StreamingCommunity/Api/Site/animeunity/__init__.py +++ b/StreamingCommunity/Api/Site/animeunity/__init__.py @@ -1,23 +1,19 @@ # 21.05.24 +import sys +import subprocess from urllib.parse import quote_plus # Internal utilities from StreamingCommunity.Util.console import console, msg +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance # Logic class from .site import title_search, run_get_select_title, media_search_manager from .film_serie import download_film, download_series -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') -import sys -import subprocess - # Variable indice = 1 @@ -25,30 +21,32 @@ _useFor = "anime" _deprecate = False _priority = 2 _engineDownload = "mp4" -from .costant import SITE_NAME +from .costant import SITE_NAME, TELEGRAM_BOT def search(string_to_search: str = None, get_onylDatabase: bool = False): if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() - if string_to_search is None: - # Chiedi la scelta all'utente con il bot Telegram - string_to_search = bot.ask( - "key_search", - f"Inserisci la parola da cercare\noppure πŸ”™ back per tornare alla scelta: ", - None - ) + if string_to_search is None: + + # Chiedi la scelta all'utente con il bot Telegram + string_to_search = bot.ask( + "key_search", + f"Inserisci la parola da cercare\noppure πŸ”™ back per tornare alla scelta: ", + None + ) + + if string_to_search == 'back': + # Riavvia lo script + # Chiude il processo attuale e avvia una nuova istanza dello script + subprocess.Popen([sys.executable] + sys.argv) + sys.exit() - if string_to_search == 'back': - # Riavvia lo script - # Chiude il processo attuale e avvia una nuova istanza dello script - subprocess.Popen([sys.executable] + sys.argv) - sys.exit() else: - if string_to_search is None: - string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip() + if string_to_search is None: + string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip() # Search on database len_database = title_search(string_to_search) @@ -69,9 +67,8 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False): download_series(select_title) else: - if TELEGRAM_BOT: - bot.send_message(f"Nessun risultato trovato riprova", None) + bot.send_message(f"Nessun risultato trovato riprova", None) console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}") diff --git a/StreamingCommunity/Api/Site/animeunity/costant.py b/StreamingCommunity/Api/Site/animeunity/costant.py index 4bb90f8..1702282 100644 --- a/StreamingCommunity/Api/Site/animeunity/costant.py +++ b/StreamingCommunity/Api/Site/animeunity/costant.py @@ -16,4 +16,6 @@ ANIME_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'anime_fold if config_manager.get_bool("DEFAULT", "add_siteName"): MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name')) - ANIME_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'anime_folder_name')) \ No newline at end of file + ANIME_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'anime_folder_name')) + +TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/animeunity/film_serie.py b/StreamingCommunity/Api/Site/animeunity/film_serie.py index 84bd5af..92b74ae 100644 --- a/StreamingCommunity/Api/Site/animeunity/film_serie.py +++ b/StreamingCommunity/Api/Site/animeunity/film_serie.py @@ -10,6 +10,8 @@ from StreamingCommunity.Util.console import console, msg from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.message import start_message from StreamingCommunity.Lib.Downloader import MP4_downloader +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance +from StreamingCommunity.TelegramHelp.session import get_session, updateScriptId, deleteScriptId # Logic class @@ -23,14 +25,10 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime # Variable -from .costant import SITE_NAME, ANIME_FOLDER, MOVIE_FOLDER +from .costant import SITE_NAME, ANIME_FOLDER, MOVIE_FOLDER, TELEGRAM_BOT KILL_HANDLER = bool(False) -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.HelpTg.session import get_session, updateScriptId, deleteScriptId -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') + def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_source: VideoSourceAnime) -> tuple[str,bool]: """ @@ -44,9 +42,8 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so - bool: kill handler status """ if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() - # Get information about the selected episode obj_episode = scrape_serie.get_info_episode(index_select) @@ -57,13 +54,13 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so console.print("[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n") if TELEGRAM_BOT: - # Invio a telegram - bot.send_message(f"Download in corso:\nTitolo:{scrape_serie.series_name}\nEpisodio: {obj_episode.number}", None) + bot.send_message(f"Download in corso:\nTitolo:{scrape_serie.series_name}\nEpisodio: {obj_episode.number}", None) + + # Get script_id + script_id = get_session() + if script_id != "unknown": + updateScriptId(script_id, f"{scrape_serie.series_name} - E{obj_episode.number}") - # Get script_id - script_id = get_session() - if script_id != "unknown": - updateScriptId(script_id, f"{scrape_serie.series_name} - E{obj_episode.number}") # Collect mp4 url video_source.get_embed(obj_episode.id) @@ -72,19 +69,15 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so title_name = f"{scrape_serie.series_name}_EP_{obj_episode.number}.mp4" if scrape_serie.is_series: - mp4_path = os_manager.get_sanitize_path( - os.path.join(ANIME_FOLDER, scrape_serie.series_name) - ) + mp4_path = os_manager.get_sanitize_path(os.path.join(ANIME_FOLDER, scrape_serie.series_name)) + else: - mp4_path = os_manager.get_sanitize_path( - os.path.join(MOVIE_FOLDER, scrape_serie.series_name) - ) + mp4_path = os_manager.get_sanitize_path(os.path.join(MOVIE_FOLDER, scrape_serie.series_name)) # Create output folder os_manager.create_path(mp4_path) # Start downloading - r_proc = MP4_downloader( url=str(video_source.src_mp4).strip(), path=os.path.join(mp4_path, title_name) @@ -109,7 +102,8 @@ def download_series(select_title: MediaItem): - tv_name (str): The name of the TV series. """ if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() + scrape_serie = ScrapeSerieAnime(SITE_NAME) video_source = VideoSourceAnime(SITE_NAME) @@ -121,19 +115,19 @@ def download_series(select_title: MediaItem): console.print(f"[cyan]Episodes find: [red]{episoded_count}") if TELEGRAM_BOT: - console.print(f"\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") + console.print(f"\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") + bot.send_message(f"Episodi trovati: {episoded_count}", None) - # Invio a telegram - bot.send_message(f"Episodi trovati: {episoded_count}", None) + last_command = bot.ask( + "select_title", + f"Inserisci l'indice del media o (*) per scaricare tutti i media, oppure [1-2] o [3-*] per un intervallo di media.", + None + ) - last_command = bot.ask( - "select_title", - f"Inserisci l'indice del media o (*) per scaricare tutti i media, oppure [1-2] o [3-*] per un intervallo di media.", - None - ) else: - # Prompt user to select an episode index - last_command = msg.ask("\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") + + # Prompt user to select an episode index + last_command = msg.ask("\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") # Manage user selection list_episode_select = manage_selection(last_command, episoded_count) @@ -151,12 +145,12 @@ def download_series(select_title: MediaItem): kill_handler= download_episode(i_episode-1, scrape_serie, video_source)[1] if TELEGRAM_BOT: - bot.send_message(f"Finito di scaricare tutte le serie e episodi", None) + bot.send_message(f"Finito di scaricare tutte le serie e episodi", None) - # Get script_id - script_id = get_session() - if script_id != "unknown": - deleteScriptId(script_id) + # Get script_id + script_id = get_session() + if script_id != "unknown": + deleteScriptId(script_id) def download_film(select_title: MediaItem): diff --git a/StreamingCommunity/Api/Site/animeunity/site.py b/StreamingCommunity/Api/Site/animeunity/site.py index 1ffc46b..d36411f 100644 --- a/StreamingCommunity/Api/Site/animeunity/site.py +++ b/StreamingCommunity/Api/Site/animeunity/site.py @@ -12,6 +12,7 @@ from bs4 import BeautifulSoup from StreamingCommunity.Util.console import console from StreamingCommunity.Util._jsonConfig import config_manager from StreamingCommunity.Util.table import TVShowManager +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance # Logic class @@ -19,14 +20,9 @@ from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template.Util import search_domain from StreamingCommunity.Api.Template.Class.SearchType import MediaManager -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') - # Variable -from .costant import SITE_NAME, DOMAIN_NOW +from .costant import SITE_NAME, DOMAIN_NOW, TELEGRAM_BOT media_search_manager = MediaManager() table_show_manager = TVShowManager() max_timeout = config_manager.get_int("REQUESTS", "timeout") @@ -109,7 +105,7 @@ def title_search(title: str) -> int: - int: A number containing the length of media search manager. """ if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() media_search_manager.clear() table_show_manager.clear() @@ -154,9 +150,9 @@ def title_search(title: str) -> int: except Exception as e: console.print(f"Site: {SITE_NAME}, request search error: {e}") + # Inizializza la lista delle scelte if TELEGRAM_BOT: - # Inizializza la lista delle scelte - choices = [] + choices = [] for dict_title in response.json()['records']: try: @@ -180,11 +176,10 @@ def title_search(title: str) -> int: except Exception as e: print(f"Error parsing a film entry: {e}") + if TELEGRAM_BOT: - # Se ci sono scelte, inviale a Telegram - if choices: - # Invio a telegram la lista - bot.send_message(f"Lista dei risultati:", choices) + if choices: + bot.send_message(f"Lista dei risultati:", choices) # Return the length of media search manager return media_search_manager.get_length() diff --git a/StreamingCommunity/Api/Site/cb01new/film.py b/StreamingCommunity/Api/Site/cb01new/film.py index b886cac..249329d 100644 --- a/StreamingCommunity/Api/Site/cb01new/film.py +++ b/StreamingCommunity/Api/Site/cb01new/film.py @@ -7,12 +7,10 @@ import os from StreamingCommunity.Util.console import console from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.message import start_message -from StreamingCommunity.Util.call_stack import get_call_stack from StreamingCommunity.Lib.Downloader import HLS_Downloader # Logic class -from StreamingCommunity.Api.Template.Util import execute_search from StreamingCommunity.Api.Template.Class.SearchType import MediaItem @@ -34,8 +32,6 @@ def download_film(select_title: MediaItem) -> str: Return: - str: output path """ - - # Start message and display film information start_message() console.print(f"[yellow]Download: [red]{select_title.name} \n") console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n") @@ -53,20 +49,14 @@ def download_film(select_title: MediaItem) -> str: # Download the film using the m3u8 playlist, and output filename r_proc = HLS_Downloader( - m3u8_playlist=master_playlist, - output_filename=os.path.join(mp4_path, title_name) + m3u8_url=master_playlist, + output_path=os.path.join(mp4_path, title_name) ).start() - """if r_proc == 404: - time.sleep(2) + if "error" in r_proc.keys(): + try: + os.remove(r_proc['path']) + except: + pass - # Re call search function - if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n": - frames = get_call_stack() - execute_search(frames[-4])""" - - if r_proc != None: - console.print("[green]Result: ") - console.print(r_proc) - - return os.path.join(mp4_path, title_name) \ No newline at end of file + return r_proc['path'] \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/guardaserie/series.py b/StreamingCommunity/Api/Site/guardaserie/series.py index 93a2658..7ce0442 100644 --- a/StreamingCommunity/Api/Site/guardaserie/series.py +++ b/StreamingCommunity/Api/Site/guardaserie/series.py @@ -7,13 +7,12 @@ import sys # Internal utilities from StreamingCommunity.Util.console import console, msg from StreamingCommunity.Util.message import start_message -from StreamingCommunity.Util.call_stack import get_call_stack from StreamingCommunity.Util.table import TVShowManager from StreamingCommunity.Lib.Downloader import HLS_Downloader # Logic class -from StreamingCommunity.Api.Template.Util import manage_selection, map_episode_title, dynamic_format_number, validate_selection, validate_episode_selection, execute_search +from StreamingCommunity.Api.Template.Util import manage_selection, map_episode_title, dynamic_format_number, validate_selection, validate_episode_selection from StreamingCommunity.Api.Template.Class.SearchType import MediaItem @@ -46,6 +45,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1] console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.get('name')}\n") console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n") + # Define filename and path for the downloaded video mp4_name = f"{map_episode_title(scape_info_serie.tv_name, index_season_selected, index_episode_selected, obj_episode.get('name'))}.mp4" mp4_path = os.path.join(SERIES_FOLDER, scape_info_serie.tv_name, f"S{index_season_selected}") @@ -58,23 +58,18 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap # Download the film using the m3u8 playlist, and output filename r_proc = HLS_Downloader( - m3u8_playlist=master_playlist, - output_filename=os.path.join(mp4_path, mp4_name) + m3u8_url=master_playlist, + output_path=os.path.join(mp4_path, mp4_name) ).start() - """if r_proc == 404: - time.sleep(2) - - # Re call search function - if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n": - frames = get_call_stack() - execute_search(frames[-4])""" - if r_proc != None: - console.print("[green]Result: ") - console.print(r_proc) + if "error" in r_proc.keys(): + try: + os.remove(r_proc['path']) + except: + pass - return os.path.join(mp4_path, mp4_name) + return r_proc['path'], r_proc['stopped'] def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, download_all: bool = False) -> None: @@ -96,7 +91,11 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, # Download all episodes without asking for i_episode in range(1, episodes_count + 1): - download_video(index_season_selected, i_episode, scape_info_serie) + path, stopped = download_video(index_season_selected, i_episode, scape_info_serie) + + if stopped: + break + console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.") else: @@ -112,11 +111,11 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, return # Download selected episodes - stopped = bool(False) for i_episode in list_episode_select: + path, stopped = download_video(index_season_selected, i_episode, scape_info_serie) + if stopped: break - download_video(index_season_selected, i_episode, scape_info_serie) def download_series(dict_serie: MediaItem) -> None: @@ -197,4 +196,4 @@ def display_episodes_list(obj_episode_manager) -> str: console.print("\n[red]Quit [white]...") sys.exit(0) - return last_command + return last_command \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/mostraguarda/film.py b/StreamingCommunity/Api/Site/mostraguarda/film.py index 2e04228..2d9ff36 100644 --- a/StreamingCommunity/Api/Site/mostraguarda/film.py +++ b/StreamingCommunity/Api/Site/mostraguarda/film.py @@ -2,7 +2,6 @@ import os import sys -import time import logging @@ -12,18 +11,13 @@ from bs4 import BeautifulSoup # Internal utilities -from StreamingCommunity.Util.console import console, msg +from StreamingCommunity.Util.console import console from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.message import start_message -from StreamingCommunity.Util.call_stack import get_call_stack from StreamingCommunity.Util.headers import get_headers from StreamingCommunity.Lib.Downloader import HLS_Downloader -# Logic class -from StreamingCommunity.Api.Template.Util import execute_search - - # Player from StreamingCommunity.Api.Player.supervideo import VideoSource @@ -51,6 +45,7 @@ def download_film(movie_details: Json_film) -> str: start_message() console.print(f"[yellow]Download: [red]{movie_details.title} \n") console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n") + # Make request to main site try: url = f"https://{SITE_NAME}.{DOMAIN_NOW}/set-movie-a/{movie_details.imdb_id}" @@ -82,20 +77,14 @@ def download_film(movie_details: Json_film) -> str: # Download the film using the m3u8 playlist, and output filename r_proc = HLS_Downloader( - m3u8_playlist=master_playlist, - output_filename=os.path.join(mp4_path, title_name) + m3u8_url=master_playlist, + output_path=os.path.join(mp4_path, title_name) ).start() - """if r_proc == 404: - time.sleep(2) + if "error" in r_proc.keys(): + try: + os.remove(r_proc['path']) + except: + pass - # Re call search function - if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n": - frames = get_call_stack() - execute_search(frames[-4])""" - - if r_proc != None: - console.print("[green]Result: ") - console.print(r_proc) - - return os.path.join(mp4_path, title_name) \ No newline at end of file + return r_proc['path'] \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/streamingcommunity/__init__.py b/StreamingCommunity/Api/Site/streamingcommunity/__init__.py index a0ad193..83aeb15 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/__init__.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/__init__.py @@ -1,11 +1,13 @@ # 21.05.24 -from urllib.parse import quote_plus -import subprocess import sys +import subprocess +from urllib.parse import quote_plus + # Internal utilities from StreamingCommunity.Util.console import console, msg +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance # Logic class @@ -13,10 +15,6 @@ from .site import get_version_and_domain, title_search, run_get_select_title, me from .film import download_film from .series import download_series -# Telegram bot instance -from StreamingCommunity.Util._jsonConfig import config_manager -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') # Variable indice = 0 @@ -24,30 +22,31 @@ _useFor = "film_serie" _deprecate = False _priority = 1 _engineDownload = "hls" -from .costant import SITE_NAME +from .costant import SITE_NAME, TELEGRAM_BOT def search(string_to_search: str = None, get_onylDatabase: bool = False): """ Main function of the application for film and series. """ - if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() - if string_to_search is None: - # Chiedi la scelta all'utente con il bot Telegram - string_to_search = bot.ask( - "key_search", - f"Inserisci la parola da cercare\noppure πŸ”™ back per tornare alla scelta: ", - None - ) + if string_to_search is None: + + # Chiedi la scelta all'utente con il bot Telegram + string_to_search = bot.ask( + "key_search", + f"Inserisci la parola da cercare\noppure πŸ”™ back per tornare alla scelta: ", + None + ) - if string_to_search == 'back': - # Riavvia lo script - # Chiude il processo attuale e avvia una nuova istanza dello script - subprocess.Popen([sys.executable] + sys.argv) - sys.exit() + if string_to_search == 'back': + # Riavvia lo script + # Chiude il processo attuale e avvia una nuova istanza dello script + subprocess.Popen([sys.executable] + sys.argv) + sys.exit() + else: if string_to_search is None: string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip() diff --git a/StreamingCommunity/Api/Site/streamingcommunity/costant.py b/StreamingCommunity/Api/Site/streamingcommunity/costant.py index cb76a8b..bb5f066 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/costant.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/costant.py @@ -16,4 +16,6 @@ MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_fold if config_manager.get_bool("DEFAULT", "add_siteName"): SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name')) - MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name')) \ No newline at end of file + MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name')) + +TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/streamingcommunity/film.py b/StreamingCommunity/Api/Site/streamingcommunity/film.py index 80e1bc4..790c3ea 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/film.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/film.py @@ -1,19 +1,18 @@ # 3.12.23 import os -import time # Internal utilities -from StreamingCommunity.Util.console import console, msg +from StreamingCommunity.Util.console import console from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.message import start_message -from StreamingCommunity.Util.call_stack import get_call_stack from StreamingCommunity.Lib.Downloader import HLS_Downloader +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance +from StreamingCommunity.TelegramHelp.session import get_session, updateScriptId, deleteScriptId # Logic class -from StreamingCommunity.Api.Template.Util import execute_search from StreamingCommunity.Api.Template.Class.SearchType import MediaItem @@ -22,13 +21,7 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSource # Variable -from .costant import SITE_NAME, MOVIE_FOLDER - -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.HelpTg.session import get_session, updateScriptId, deleteScriptId -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') +from .costant import SITE_NAME, MOVIE_FOLDER, TELEGRAM_BOT def download_film(select_title: MediaItem) -> str: @@ -43,18 +36,16 @@ def download_film(select_title: MediaItem) -> str: - str: output path """ if TELEGRAM_BOT: - bot = get_bot_instance() - - # Invio a telegram - bot.send_message(f"Download in corso:\n{select_title.name}", None) + bot = get_bot_instance() + bot.send_message(f"Download in corso:\n{select_title.name}", None) - # Viene usato per lo screen - console.print(f"## Download: [red]{select_title.name} ##") + # Viene usato per lo screen + console.print(f"## Download: [red]{select_title.name} ##") - # Get script_id - script_id = get_session() - if script_id != "unknown": - updateScriptId(script_id, select_title.name) + # Get script_id + script_id = get_session() + if script_id != "unknown": + updateScriptId(script_id, select_title.name) # Start message and display film information start_message() @@ -75,26 +66,21 @@ def download_film(select_title: MediaItem) -> str: # Download the film using the m3u8 playlist, and output filename r_proc = HLS_Downloader( - m3u8_playlist=master_playlist, - output_filename=os.path.join(mp4_path, title_name) + m3u8_url=master_playlist, + output_path=os.path.join(mp4_path, title_name) ).start() if TELEGRAM_BOT: - # Delete script_id - script_id = get_session() - if script_id != "unknown": - deleteScriptId(script_id) + + # Delete script_id + script_id = get_session() + if script_id != "unknown": + deleteScriptId(script_id) - """if r_proc == 404: - time.sleep(2) + if "error" in r_proc.keys(): + try: + os.remove(r_proc['path']) + except: + pass - # Re call search function - if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n": - frames = get_call_stack() - execute_search(frames[-4])""" - - if r_proc != None: - console.print("[green]Result: ") - console.print(r_proc) - - return os.path.join(mp4_path, title_name) \ No newline at end of file + return r_proc['path'] \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/streamingcommunity/series.py b/StreamingCommunity/Api/Site/streamingcommunity/series.py index 50a3a73..e94d962 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/series.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/series.py @@ -2,20 +2,20 @@ import os import sys -import time # Internal utilities from StreamingCommunity.Util.console import console, msg from StreamingCommunity.Util.message import start_message -from StreamingCommunity.Util.call_stack import get_call_stack from StreamingCommunity.Util.table import TVShowManager from StreamingCommunity.Lib.Downloader import HLS_Downloader +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance +from StreamingCommunity.TelegramHelp.session import get_session, updateScriptId, deleteScriptId # Logic class from .util.ScrapeSerie import ScrapeSerie -from StreamingCommunity.Api.Template.Util import manage_selection, map_episode_title, dynamic_format_number, validate_selection, validate_episode_selection, execute_search +from StreamingCommunity.Api.Template.Util import manage_selection, map_episode_title, dynamic_format_number, validate_selection, validate_episode_selection from StreamingCommunity.Api.Template.Class.SearchType import MediaItem @@ -24,13 +24,7 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSource # Variable -from .costant import SITE_NAME, SERIES_FOLDER - -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -from StreamingCommunity.HelpTg.session import get_session, updateScriptId, deleteScriptId -from StreamingCommunity.Util._jsonConfig import config_manager -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') +from .costant import SITE_NAME, SERIES_FOLDER, TELEGRAM_BOT def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: ScrapeSerie, video_source: VideoSource) -> tuple[str,bool]: @@ -54,18 +48,17 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra print() if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() - # Invio a telegram - bot.send_message( - f"Download in corso\nSerie: {scrape_serie.series_name}\nStagione: {index_season_selected}\nEpisodio: {index_episode_selected}\nTitolo: {obj_episode.name}", - None - ) + # Invio a telegram + bot.send_message( + f"Download in corso\nSerie: {scrape_serie.series_name}\nStagione: {index_season_selected}\nEpisodio: {index_episode_selected}\nTitolo: {obj_episode.name}", + None + ) - # Get script_id + # Get script_id and update it script_id = get_session() if script_id != "unknown": - # Update script_id updateScriptId(script_id, f"{scrape_serie.series_name} - S{index_season_selected} - E{index_episode_selected} - {obj_episode.name}") # Define filename and path for the downloaded video @@ -79,26 +72,17 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra # Download the episode r_proc = HLS_Downloader( - m3u8_playlist=master_playlist, - output_filename=os.path.join(mp4_path, mp4_name) + m3u8_url=master_playlist, + output_path=os.path.join(mp4_path, mp4_name) ).start() - #bot.send_message(f"Serie scaricata tutta", None) + if "error" in r_proc.keys(): + try: + os.remove(r_proc['path']) + except: + pass - """if r_proc == 404: - time.sleep(2) - - # Re call search function - if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n": - frames = get_call_stack() - execute_search(frames[-4])""" - - if r_proc != None: - console.print("[green]Result: ") - console.print(r_proc) - #bot.send_message(f"Episodio scaricato", None) - - return os.path.join(mp4_path, mp4_name) + return r_proc['path'], r_proc['stopped'] def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, video_source: VideoSource, download_all: bool = False) -> None: """ @@ -109,8 +93,6 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide - download_all (bool): Download all episodes in the season. """ - #bot = get_bot_instance() - # Clean memory of all episodes and get the number of the season scrape_serie.episode_manager.clear() @@ -123,10 +105,12 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide # Download all episodes without asking for i_episode in range(1, episodes_count + 1): - download_video(index_season_selected, i_episode, scrape_serie, video_source) - console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.") + path, stopped = download_video(index_season_selected, i_episode, scrape_serie, video_source) - #bot.send_message(f"Finito di scaricare la stagione: {index_season_selected}", None) + if stopped: + break + + console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.") else: @@ -138,15 +122,14 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide list_episode_select = validate_episode_selection(list_episode_select, episodes_count) except ValueError as e: console.print(f"[red]{str(e)}") - #bot.send_message(f"{str(e)}", None) return # Download selected episodes if not stopped - stopped = bool(False) for i_episode in list_episode_select: + path, stopped = download_video(index_season_selected, i_episode, scrape_serie, video_source) + if stopped: break - stopped=download_video(index_season_selected, i_episode, scrape_serie, video_source)[1] def download_series(select_season: MediaItem, version: str) -> None: """ @@ -158,7 +141,7 @@ def download_series(select_season: MediaItem, version: str) -> None: - version (str): Version of the site. """ if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() # Start message and set up video source start_message() @@ -179,21 +162,22 @@ def download_series(select_season: MediaItem, version: str) -> None: console.print(f"\n[green]Seasons found: [red]{seasons_count}") if TELEGRAM_BOT: - console.print("\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, " + console.print("\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, " "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end") - bot.send_message(f"Stagioni trovate: {seasons_count}", None) + bot.send_message(f"Stagioni trovate: {seasons_count}", None) + + index_season_selected = bot.ask( + "select_title_episode", + "Inserisci il numero della stagione (es. 1), * per scaricare tutte le stagioni, (es. 1-2) per un intervallo di stagioni, o (es. 3-*) per scaricare dalla stagione specificata fino alla fine", + None + ) - index_season_selected = bot.ask( - "select_title_episode", - "Inserisci il numero della stagione (es. 1), * per scaricare tutte le stagioni, (es. 1-2) per un intervallo di stagioni, o (es. 3-*) per scaricare dalla stagione specificata fino alla fine", - None - ) else: - index_season_selected = msg.ask( - "\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, " - "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end" - ) + index_season_selected = msg.ask( + "\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, " + "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end" + ) # Manage and validate the selection list_season_select = manage_selection(index_season_selected, seasons_count) @@ -216,11 +200,12 @@ def download_series(select_season: MediaItem, version: str) -> None: download_episode(i_season, scrape_serie, video_source, download_all=False) if TELEGRAM_BOT: - bot.send_message(f"Finito di scaricare tutte le serie e episodi", None) - # Get script_id - script_id = get_session() - if script_id != "unknown": - deleteScriptId(script_id) + bot.send_message(f"Finito di scaricare tutte le serie e episodi", None) + + # Get script_id + script_id = get_session() + if script_id != "unknown": + deleteScriptId(script_id) def display_episodes_list(scrape_serie) -> str: @@ -231,7 +216,7 @@ def display_episodes_list(scrape_serie) -> str: last_command (str): Last command entered by the user. """ if TELEGRAM_BOT: - bot = get_bot_instance() + bot = get_bot_instance() # Set up table for displaying episodes table_show_manager = TVShowManager() @@ -247,7 +232,8 @@ def display_episodes_list(scrape_serie) -> str: # Populate the table with episodes information if TELEGRAM_BOT: - choices = [] + choices = [] + for i, media in enumerate(scrape_serie.episode_manager.episodes): table_show_manager.add_tv_show({ 'Index': str(media.number), @@ -256,20 +242,12 @@ def display_episodes_list(scrape_serie) -> str: }) if TELEGRAM_BOT: - # Creazione della stringa per il messaggio Telegram - choice_text = f"{media.number} - {media.name} ({media.duration} min)" - choices.append(choice_text) + choice_text = f"{media.number} - {media.name} ({media.duration} min)" + choices.append(choice_text) if TELEGRAM_BOT: - # creo episoded_count - #episoded_count = len(scrape_serie.episode_manager.episodes) - - # Invio a telegram - #bot.send_message(f"Episodi trovati: {episoded_count}", None) - - # Invia la lista degli episodi al bot Telegram - if choices: - bot.send_message(f"Lista episodi:", choices) + if choices: + bot.send_message(f"Lista episodi:", choices) # Run the table and handle user input last_command = table_show_manager.run() @@ -278,4 +256,4 @@ def display_episodes_list(scrape_serie) -> str: console.print("\n[red]Quit [white]...") sys.exit(0) - return last_command + return last_command \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/streamingcommunity/site.py b/StreamingCommunity/Api/Site/streamingcommunity/site.py index be84384..0064865 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/site.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/site.py @@ -15,7 +15,7 @@ from StreamingCommunity.Util.console import console from StreamingCommunity.Util._jsonConfig import config_manager from StreamingCommunity.Util.headers import get_headers from StreamingCommunity.Util.table import TVShowManager - +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance # Logic class @@ -25,11 +25,8 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager # Config -from .costant import SITE_NAME, DOMAIN_NOW +from .costant import SITE_NAME, DOMAIN_NOW, TELEGRAM_BOT -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') # Variable media_search_manager = MediaManager() @@ -123,13 +120,12 @@ def title_search(title_search: str, domain: str) -> int: except Exception as e: console.print(f"Site: {SITE_NAME}, request search error: {e}") + # Prepara le scelte per l'utente if TELEGRAM_BOT: - # Prepara le scelte per l'utente - choices = [] + choices = [] for i, dict_title in enumerate(response.json()['data']): try: - media_search_manager.add_media({ 'id': dict_title.get('id'), 'slug': dict_title.get('slug'), @@ -138,18 +134,17 @@ def title_search(title_search: str, domain: str) -> int: 'date': dict_title.get('last_air_date'), 'score': dict_title.get('score') }) + if TELEGRAM_BOT: - # Crea una stringa formattata per ogni scelta con numero - choice_text = f"{i} - {dict_title.get('name')} ({dict_title.get('type')}) - {dict_title.get('last_air_date')}" - choices.append(choice_text) + choice_text = f"{i} - {dict_title.get('name')} ({dict_title.get('type')}) - {dict_title.get('last_air_date')}" + choices.append(choice_text) except Exception as e: print(f"Error parsing a film entry: {e}") if TELEGRAM_BOT: - if choices: - # Invio a telegram la lista - bot.send_message(f"Lista dei risultati:", choices) + if choices: + bot.send_message(f"Lista dei risultati:", choices) # Return the number of titles found return media_search_manager.get_length() diff --git a/StreamingCommunity/HelpTg/config.json b/StreamingCommunity/HelpTg/config.json deleted file mode 100644 index 3ad11ca..0000000 --- a/StreamingCommunity/HelpTg/config.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "DEFAULT": { - "debug": false, - "log_file": "app.log", - "log_to_file": true, - "show_message": true, - "clean_console": true, - "root_path": "Video", - "movie_folder_name": "Movie", - "serie_folder_name": "TV", - "anime_folder_name": "Anime", - "map_episode_name": "E%(episode)_%(episode_name)", - "config_qbit_tor": { - "host": "192.168.1.99", - "port": "7060", - "user": "admin", - "pass": "adminadmin" - }, - "add_siteName": false, - "disable_searchDomain": false, - "not_close": false, - "telegram_bot": true - }, - "REQUESTS": { - "timeout": 30, - "max_retry": 8, - "proxy_start_min": 0.1, - "proxy_start_max": 0.5 - }, - "M3U8_DOWNLOAD": { - "tqdm_delay": 0.12, - "default_video_workser": 12, - "default_audio_workser": 12, - "merge_audio": true, - "specific_list_audio": [ - "ita" - ], - "merge_subs": false, - "specific_list_subtitles": [ - "eng", - "spa" - ], - "cleanup_tmp_folder": true - }, - "M3U8_CONVERSION": { - "use_codec": false, - "use_vcodec": true, - "use_acodec": true, - "use_bitrate": true, - "use_gpu": false, - "default_preset": "ultrafast" - }, - "M3U8_PARSER": { - "force_resolution": -1, - "get_only_link": false - }, - "SITE": { - "streamingcommunity": { - "domain": "paris" - }, - "altadefinizionegratis": { - "domain": "site" - }, - "guardaserie": { - "domain": "meme" - }, - "mostraguarda": { - "domain": "stream" - }, - "ddlstreamitaly": { - "domain": "co", - "extra": { - "ips4_device_key": "", - "ips4_member_id": "", - "ips4_login_key": "" - } - }, - "animeunity": { - "domain": "so" - }, - "cb01new": { - "domain": "mobi" - }, - "1337xx": { - "domain": "to" - }, - "ilcorsaronero": { - "domain": "link" - } - } -} \ No newline at end of file diff --git a/StreamingCommunity/HelpTg/request_manager.py b/StreamingCommunity/HelpTg/request_manager.py deleted file mode 100644 index 963bbad..0000000 --- a/StreamingCommunity/HelpTg/request_manager.py +++ /dev/null @@ -1,79 +0,0 @@ -import json -import time -from typing import Optional - -class RequestManager: - _instance = None - - def __new__(cls, *args, **kwargs): - if not cls._instance: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self, json_file: str = "active_requests.json"): - if not hasattr(self, 'initialized'): - self.json_file = json_file - self.initialized = True - self.on_response_callback = None # Aggiungi un campo per il callback - - def create_request(self, type: str) -> str: - request_data = { - "type": type, - "response": None, - "timestamp": time.time() - } - - # Aggiungi il tipo al salvataggio della richiesta - with open(self.json_file, "w") as f: - json.dump(request_data, f) - - return "Ok" - - def save_response(self, message_text: str) -> bool: - try: - # Carica il file JSON - with open(self.json_file, "r") as f: - data = json.load(f) - - # Controlla se esiste la chiave 'type' e se la risposta Γ¨ presente - if "type" in data and "response" in data: - data["response"] = message_text # Aggiorna la risposta - - # Scrivi il file JSON aggiornato - with open(self.json_file, "w") as f: - json.dump(data, f, indent=4) # Formatta il file JSON - - return True - else: - return False - - except (FileNotFoundError, json.JSONDecodeError) as e: - print(f"⚠️ save_response - errore: {e}") - return False - - def get_response(self) -> Optional[str]: - try: - with open(self.json_file, "r") as f: - data = json.load(f) - # Verifica se esiste la chiave "response" - if "response" in data: - response = data["response"] # Ottieni la risposta direttamente - if response is not None and self.on_response_callback: - # Se la risposta Γ¨ disponibile, chiama il callback - self.on_response_callback(response) - return response - - except (FileNotFoundError, json.JSONDecodeError) as e: - print(f"get_response - errore: {e}") - return None - - def clear_file(self) -> bool: - try: - # Svuota il file JSON scrivendo un oggetto vuoto - with open(self.json_file, "w") as f: - json.dump({}, f) - print(f"File {self.json_file} Γ¨ stato svuotato con successo.") - return True - except Exception as e: - print(f"⚠️ clear_file - errore: {e}") - return False \ No newline at end of file diff --git a/StreamingCommunity/HelpTg/requirements.txt b/StreamingCommunity/HelpTg/requirements.txt deleted file mode 100644 index 01f23e8..0000000 --- a/StreamingCommunity/HelpTg/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -ο»Ώhttpx -bs4 -rich -tqdm -m3u8 -psutil -unidecode -jsbeautifier -pathvalidate -pycryptodomex -googlesearch-python -fake-useragent -qbittorrent-api -python-qbittorrent \ No newline at end of file diff --git a/StreamingCommunity/Lib/Downloader/HLS/downloader.py b/StreamingCommunity/Lib/Downloader/HLS/downloader.py index 601f4c7..2ee4b4e 100644 --- a/StreamingCommunity/Lib/Downloader/HLS/downloader.py +++ b/StreamingCommunity/Lib/Downloader/HLS/downloader.py @@ -2,10 +2,10 @@ import os import re -import sys import time import logging -import subprocess +import shutil +from typing import Any, Dict, List, Optional # External libraries @@ -16,12 +16,13 @@ import httpx from StreamingCommunity.Util._jsonConfig import config_manager from StreamingCommunity.Util.headers import get_headers from StreamingCommunity.Util.console import console, Panel -from StreamingCommunity.Util.color import Colors from StreamingCommunity.Util.os import ( compute_sha1_hash, os_manager, internet_manager ) +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance + # Logic class from ...FFmpeg import ( @@ -30,19 +31,12 @@ from ...FFmpeg import ( join_audios, join_subtitle ) -from ...M3U8 import ( - M3U8_Parser, - M3U8_Codec, - M3U8_UrlFix -) +from ...M3U8 import M3U8_Parser, M3U8_UrlFix from .segments import M3U8_Segments -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') # Config -DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_audio') +DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_audio') DOWNLOAD_SPECIFIC_SUBTITLE = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_subtitles') MERGE_AUDIO = config_manager.get_bool('M3U8_DOWNLOAD', 'merge_audio') MERGE_SUBTITLE = config_manager.get_bool('M3U8_DOWNLOAD', 'merge_subs') @@ -50,934 +44,483 @@ REMOVE_SEGMENTS_FOLDER = config_manager.get_bool('M3U8_DOWNLOAD', 'cleanup_tmp_f FILTER_CUSTOM_REOLUTION = config_manager.get_int('M3U8_PARSER', 'force_resolution') GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link') RETRY_LIMIT = config_manager.get_int('REQUESTS', 'max_retry') +MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout") - -# Variable -max_timeout = config_manager.get_int("REQUESTS", "timeout") -m3u8_url_fixer = M3U8_UrlFix() -list_MissingTs = [] +TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') -class HttpClient: - def __init__(self, headers: dict = None): - self.headers = headers or {'User-Agent': get_headers()} - self.client = httpx.Client(headers=self.headers, timeout=max_timeout, follow_redirects=True) +class HLSClient: + """Client for making HTTP requests to HLS endpoints with retry mechanism.""" + def __init__(self): + self.headers = {'User-Agent': get_headers()} - def _make_request(self, url: str, return_content: bool = False): + def request(self, url: str, return_content: bool = False) -> Optional[httpx.Response]: + """ + Makes HTTP GET requests with retry logic. + + Args: + url: Target URL to request + return_content: If True, returns response content instead of text + + Returns: + Response content/text or None if all retries fail + """ + client = httpx.Client(headers=self.headers, timeout=MAX_TIMEOUT, follow_redirects=True) for attempt in range(RETRY_LIMIT): try: - response = self.client.get(url) + response = client.get(url) response.raise_for_status() return response.content if return_content else response.text + except Exception as e: - logging.error(f"Attempt {attempt+1} failed for {url}: {str(e)}") + logging.error(f"Attempt {attempt+1} failed: {str(e)}") time.sleep(1.5 ** attempt) return None - def get(self, url: str) -> str: - return self._make_request(url) - - def get_content(self, url: str) -> bytes: - return self._make_request(url, return_content=True) - class PathManager: - def __init__(self, output_filename): + """Manages file paths and directories for downloaded content.""" + def __init__(self, m3u8_url: str, output_path: Optional[str]): """ - Initializes the PathManager with the output filename. - Args: - output_filename (str): The name of the output file (should end with .mp4). + m3u8_url: Source M3U8 playlist URL + output_path: Desired output path for the final video file """ - self.output_filename = output_filename + self.m3u8_url = m3u8_url + self.output_path = self._sanitize_output_path(output_path) + base_name = os.path.basename(self.output_path).replace(".mp4", "") + self.temp_dir = os.path.join(os.path.dirname(self.output_path), f"{base_name}_tmp") + + def _sanitize_output_path(self, path: Optional[str]) -> str: + """ + Ensures output path is valid and follows expected format. + Creates a hash-based filename if no path is provided. + """ + if not path: + root = config_manager.get('DEFAULT', 'root_path') + hash_name = compute_sha1_hash(self.m3u8_url) + ".mp4" + return os.path.join(root, "undefined", hash_name) - # Create the base path by removing the '.mp4' extension from the output filename - self.base_path = str(output_filename).replace(".mp4", "") - logging.info(f"class 'PathManager'; set base path: {self.base_path}") + if not path.endswith(".mp4"): + path += ".mp4" + + return os_manager.get_sanitize_path(path) + + def setup_directories(self): + """Creates necessary directories for temporary files (video, audio, subtitles).""" + os.makedirs(self.temp_dir, exist_ok=True) + for subdir in ['video', 'audio', 'subs']: + os.makedirs(os.path.join(self.temp_dir, subdir), exist_ok=True) + + def move_final_file(self, final_file: str): + """Moves the final merged file to the desired output location.""" + if os.path.exists(self.output_path): + os.remove(self.output_path) + shutil.move(final_file, self.output_path) + + def cleanup(self): + """Removes temporary directories if configured to do so.""" + if REMOVE_SEGMENTS_FOLDER: + os_manager.remove_folder(self.temp_dir) + + +class M3U8Manager: + """Handles M3U8 playlist parsing and stream selection.""" + def __init__(self, m3u8_url: str, client: HLSClient): + self.m3u8_url = m3u8_url + self.client = client + self.parser = M3U8_Parser() + self.url_fixer = M3U8_UrlFix() + self.video_url = None + self.video_res = None + self.audio_streams = [] + self.sub_streams = [] + self.is_master = False + + def parse(self): + """ + Fetches and parses the M3U8 playlist content. + Determines if it's a master playlist (index) or media playlist. + """ + content = self.client.request(self.m3u8_url) + if not content: + raise ValueError("Failed to fetch M3U8 content") - # Define the path for a temporary directory where segments will be stored - self.base_temp = os.path.join(self.base_path, "tmp") - self.video_segments_path = os.path.join(self.base_temp, "video") - self.audio_segments_path = os.path.join(self.base_temp, "audio") - self.subtitle_segments_path = os.path.join(self.base_temp, "subtitle") + self.parser.parse_data(uri=self.m3u8_url, raw_content=content) + self.url_fixer.set_playlist(self.m3u8_url) + self.is_master = self.parser.is_master_playlist - def create_directories(self): + def select_streams(self): """ - Creates the necessary directories for storing video, audio, and subtitle segments. + Selects video, audio, and subtitle streams based on configuration. + If it's a master playlist, only selects video stream. """ + if not self.is_master: + # For master playlist, only get the video stream + if FILTER_CUSTOM_REOLUTION != -1: + self.video_url, self.video_res = self.parser._video.get_custom_uri(y_resolution=FILTER_CUSTOM_REOLUTION) + else: + self.video_url, self.video_res = self.parser._video.get_best_uri() - os.makedirs(self.base_temp, exist_ok=True) - os.makedirs(self.video_segments_path, exist_ok=True) - os.makedirs(self.audio_segments_path, exist_ok=True) - os.makedirs(self.subtitle_segments_path, exist_ok=True) - - -class ContentExtractor: - def start(self, obj_parse: M3U8_Parser): - """ - Starts the extraction process by parsing the M3U8 playlist and collecting audio, subtitle, and video data. - - Args: - obj_parse (str): The M3U8_Parser obj of the M3U8 playlist. - """ - self.obj_parse = obj_parse - self._collect_audio() - self._collect_subtitle() - self._collect_video() - - def _collect_audio(self): - """ - It checks for available audio languages and the specific audio tracks to download. - """ - logging.info(f"class 'ContentExtractor'; call _collect_audio()") - - # Collect available audio tracks and their corresponding URIs and names - self.list_available_audio = self.obj_parse._audio.get_all_uris_and_names() - - # Check if there are any audio tracks available; if not, disable download - if self.list_available_audio is not None: - - # Extract available languages from the audio tracks - available_languages = [obj_audio.get('language') for obj_audio in self.list_available_audio] - set_language = DOWNLOAD_SPECIFIC_AUDIO - downloadable_languages = list(set(available_languages) & set(set_language)) - - # Only show if there is something available - if len(available_languages) > 0: - console.print(f"[cyan bold]Audio β†’[/cyan bold] [green]Available:[/green] [purple]{', '.join(available_languages)}[/purple] | " - f"[red]Set:[/red] [purple]{', '.join(set_language)}[/purple] | " - f"[yellow]Downloadable:[/yellow] [purple]{', '.join(downloadable_languages)}[/purple]") - + # Don't process audio or subtitles for master playlist + self.audio_streams = [] + self.sub_streams = [] + else: - console.log("[red]Can't find a list of audios") + # For media playlist, process all streams as before + if FILTER_CUSTOM_REOLUTION != -1: + self.video_url, self.video_res = self.parser._video.get_custom_uri(y_resolution=FILTER_CUSTOM_REOLUTION) + else: + self.video_url, self.video_res = self.parser._video.get_best_uri() - def _collect_subtitle(self): - """ - It checks for available subtitle languages and the specific subtitles to download. - """ - logging.info(f"class 'ContentExtractor'; call _collect_subtitle()") + self.audio_streams = [ + s for s in (self.parser._audio.get_all_uris_and_names() or []) + if s.get('language') in DOWNLOAD_SPECIFIC_AUDIO + ] - # Collect available subtitles and their corresponding URIs and names - self.list_available_subtitles = self.obj_parse._subtitle.get_all_uris_and_names() - - # Check if there are any subtitles available; if not, disable download - if self.list_available_subtitles is not None: - - # Extract available languages from the subtitles - available_languages = [obj_subtitle.get('language') for obj_subtitle in self.list_available_subtitles] - set_language = DOWNLOAD_SPECIFIC_SUBTITLE - downloadable_languages = list(set(available_languages) & set(set_language)) - - # Only show if there is something available - if len(available_languages) > 0: - console.print(f"[cyan bold]Subtitle β†’[/cyan bold] [green]Available:[/green] [purple]{', '.join(available_languages)}[/purple] | " - f"[red]Set:[/red] [purple]{', '.join(set_language)}[/purple] | " - f"[yellow]Downloadable:[/yellow] [purple]{', '.join(downloadable_languages)}[/purple]") + self.sub_streams = [ + s for s in (self.parser._subtitle.get_all_uris_and_names() or []) + if s.get('language') in DOWNLOAD_SPECIFIC_SUBTITLE + ] + def log_selection(self): + if FILTER_CUSTOM_REOLUTION == -1: + set_resolution = "Best" else: - console.log("[red]Can't find a list of subtitles") - - def _collect_video(self): - """ - It identifies the best video quality and displays relevant information to the user. - """ - logging.info(f"class 'ContentExtractor'; call _collect_video()") - set_resolution = "Best" - - # Collect custom quality video if a specific resolution is set - if FILTER_CUSTOM_REOLUTION != -1: - self.m3u8_index, video_res = self.obj_parse._video.get_custom_uri(y_resolution=FILTER_CUSTOM_REOLUTION) set_resolution = f"{FILTER_CUSTOM_REOLUTION}p" - else: + tuple_available_resolution = self.parser._video.get_list_resolution() + list_available_resolution = [f"{r[0]}x{r[1]}" for r in tuple_available_resolution] - # Otherwise, get the best available video quality - self.m3u8_index, video_res = self.obj_parse._video.get_best_uri() - - self.codec: M3U8_Codec = self.obj_parse.codec + console.print( + f"[cyan bold]Video β†’[/cyan bold] [green]Available:[/green] [purple]{', '.join(list_available_resolution)}[/purple] | " + f"[red]Set:[/red] [purple]{set_resolution}[/purple] | " + f"[yellow]Downloadable:[/yellow] [purple]{self.video_res[0]}x{self.video_res[1]}[/purple]" + ) - # List all available resolutions - tuple_available_resolution = self.obj_parse._video.get_list_resolution() - list_available_resolution = [str(resolution[0]) + "x" + str(resolution[1]) for resolution in tuple_available_resolution] - logging.info(f"M3U8 index selected: {self.m3u8_index}, with resolution: {video_res}") - - # Create a formatted table to display video info - console.print(f"[cyan bold]Video β†’[/cyan bold] [green]Available:[/green] [purple]{', '.join(list_available_resolution)}[/purple] | " - f"[red]Set:[/red] [purple]{set_resolution}[/purple] | " - f"[yellow]Downloadable:[/yellow] [purple]{video_res[0]}x{video_res[1]}[/purple]") - - if self.codec is not None: - - # Generate the string for available codec information + if self.parser.codec is not None: available_codec_info = ( - f"[green]v[/green]: [yellow]{self.codec.video_codec_name}[/yellow] " - f"([green]b[/green]: [yellow]{self.codec.video_bitrate // 1000}k[/yellow]), " - f"[green]a[/green]: [yellow]{self.codec.audio_codec_name}[/yellow] " - f"([green]b[/green]: [yellow]{self.codec.audio_bitrate // 1000}k[/yellow])" + f"[green]v[/green]: [yellow]{self.parser.codec.video_codec_name}[/yellow] " + f"([green]b[/green]: [yellow]{self.parser.codec.video_bitrate // 1000}k[/yellow]), " + f"[green]a[/green]: [yellow]{self.parser.codec.audio_codec_name}[/yellow] " + f"([green]b[/green]: [yellow]{self.parser.codec.audio_bitrate // 1000}k[/yellow])" ) + set_codec_info = available_codec_info if config_manager.get_bool("M3U8_CONVERSION", "use_codec") else "[purple]copy[/purple]" - # Determine what to display for "Set" - # If the codec usage is enabled in the configuration, use the detailed codec info - # Otherwise, display "copy" - if config_manager.get_bool("M3U8_CONVERSION", "use_codec"): - set_codec_info = available_codec_info - else: - set_codec_info = "[purple]copy[/purple]" - - # Print the formatted result with "Available" and "Set" information console.print( f"[bold cyan]Codec β†’[/bold cyan] [green]Available:[/green] {available_codec_info} | " f"[red]Set:[/red] {set_codec_info}" ) + available_subtitles = self.parser._subtitle.get_all_uris_and_names() or [] + available_sub_languages = [sub.get('language') for sub in available_subtitles] + downloadable_sub_languages = list(set(available_sub_languages) & set(DOWNLOAD_SPECIFIC_SUBTITLE)) + if available_sub_languages: + console.print( + f"[cyan bold]Subtitle β†’[/cyan bold] [green]Available:[/green] [purple]{', '.join(available_sub_languages)}[/purple] | " + f"[red]Set:[/red] [purple]{', '.join(DOWNLOAD_SPECIFIC_SUBTITLE)}[/purple] | " + f"[yellow]Downloadable:[/yellow] [purple]{', '.join(downloadable_sub_languages)}[/purple]" + ) - # Fix the URL if it does not include the full protocol - if "http" not in self.m3u8_index: - - # Generate the full URL - self.m3u8_index = m3u8_url_fixer.generate_full_url(self.m3u8_index) - logging.info(f"Generated index URL: {self.m3u8_index}") - - # Check if a valid HTTPS URL is obtained - if self.m3u8_index is not None and "https" in self.m3u8_index: - #console.print(f"[cyan]Found m3u8 index [white]=> [red]{self.m3u8_index}") - print() - - else: - logging.error("[download_m3u8] Can't find a valid m3u8 index") - raise ValueError("Invalid m3u8 index URL") - + available_audio = self.parser._audio.get_all_uris_and_names() or [] + available_audio_languages = [audio.get('language') for audio in available_audio] + downloadable_audio_languages = list(set(available_audio_languages) & set(DOWNLOAD_SPECIFIC_AUDIO)) + if available_audio_languages: + console.print( + f"[cyan bold]Audio β†’[/cyan bold] [green]Available:[/green] [purple]{', '.join(available_audio_languages)}[/purple] | " + f"[red]Set:[/red] [purple]{', '.join(DOWNLOAD_SPECIFIC_AUDIO)}[/purple] | " + f"[yellow]Downloadable:[/yellow] [purple]{', '.join(downloadable_audio_languages)}[/purple]" + ) print("") -class DownloadTracker: - def __init__(self, path_manager: PathManager): +class DownloadManager: + """Manages downloading of video, audio, and subtitle streams.""" + def __init__(self, temp_dir: str, client: HLSClient, url_fixer: M3U8_UrlFix): """ - Initializes the DownloadTracker with paths for audio, subtitle, and video segments. - Args: - path_manager (PathManager): An instance of the PathManager class to manage file paths. + temp_dir: Directory for storing temporary files + client: HLSClient instance for making requests + url_fixer: URL fixer instance for generating complete URLs """ + self.temp_dir = temp_dir + self.client = client + self.url_fixer = url_fixer + self.missing_segments = [] + self.stopped = False - # Initialize lists to track downloaded audio, subtitles, and video - self.downloaded_audio = [] - self.downloaded_subtitle = [] - self.downloaded_video = [] + def download_video(self, video_url: str): + """Downloads video segments from the M3U8 playlist.""" + video_full_url = self.url_fixer.generate_full_url(video_url) + video_tmp_dir = os.path.join(self.temp_dir, 'video') - self.video_segment_path = path_manager.video_segments_path - self.audio_segments_path = path_manager.audio_segments_path - self.subtitle_segments_path = path_manager.subtitle_segments_path + downloader = M3U8_Segments(url=video_full_url, tmp_folder=video_tmp_dir) + result = downloader.download_streams("Video", "video") + self.missing_segments.append(result) - def add_video(self, available_video): - """ - Adds a single video to the list of downloaded videos. + if result.get('stopped', False): + self.stopped = True + return self.stopped - Args: - available_video (str): The URL of the video to be downloaded. - """ - logging.info(f"class 'DownloadTracker'; call add_video() with parameter: {available_video}") - - self.downloaded_video.append({ - 'type': 'video', - 'url': available_video, - 'path': os.path.join(self.video_segment_path, "0.ts") - }) - - def add_audio(self, list_available_audio): - """ - Adds available audio tracks to the list of downloaded audio. - - Args: - list_available_audio (list): A list of available audio track objects. - """ - logging.info(f"class 'DownloadTracker'; call add_audio() with parameter: {list_available_audio}") - - for obj_audio in list_available_audio: - - # Check if specific audio languages are set for download - if len(DOWNLOAD_SPECIFIC_AUDIO) > 0: - - # Skip this audio track if its language is not in the specified list - if obj_audio.get('language') not in DOWNLOAD_SPECIFIC_AUDIO: - continue - - # Construct the full path for the audio segment directory - full_path_audio = os.path.join(self.audio_segments_path, obj_audio.get('language')) - - # Append the audio information to the downloaded audio list - self.downloaded_audio.append({ - 'type': 'audio', - 'url': obj_audio.get('uri'), - 'language': obj_audio.get('language'), - 'path': os.path.join(full_path_audio, "0.ts") - }) - - def add_subtitle(self, list_available_subtitles): - """ - Adds available subtitles to the list of downloaded subtitles. - - Args: - list_available_subtitles (list): A list of available subtitle objects. - """ - logging.info(f"class 'DownloadTracker'; call add_subtitle() with parameter: {list_available_subtitles}") - - for obj_subtitle in list_available_subtitles: - - # Check if specific subtitle languages are set for download - if len(DOWNLOAD_SPECIFIC_SUBTITLE) > 0: - - # Skip this subtitle if its language is not in the specified list - if obj_subtitle.get('language') not in DOWNLOAD_SPECIFIC_SUBTITLE: - continue - - sub_language = obj_subtitle.get('language') - - # Construct the full path for the subtitle file - sub_full_path = os.path.join(self.subtitle_segments_path, sub_language + ".vtt") - - self.downloaded_subtitle.append({ - 'type': 'sub', - 'url': obj_subtitle.get('uri'), - 'language': obj_subtitle.get('language'), - 'path': sub_full_path - }) - - -class ContentDownloader: - def __init__(self): - """ - Initializes the ContentDownloader class. - - Attributes: - expected_real_time (float): Expected real-time duration of the video download. - """ - self.expected_real_time = None - - def download_video(self, downloaded_video): - """ - Downloads the video if it doesn't already exist. - - Args: - downloaded_video (list): A list containing information about the video to download. - """ - logging.info(f"class 'ContentDownloader'; call download_video() with parameter: {downloaded_video}") - - # Check if the video file already exists - if not os.path.exists(downloaded_video[0].get('path')): - folder_name = os.path.dirname(downloaded_video[0].get('path')) - - # Create an instance of M3U8_Segments to handle video segments download - video_m3u8 = M3U8_Segments(downloaded_video[0].get('url'), folder_name) - - # Get information about the video segments (e.g., duration, ts files to download) - video_m3u8.get_info() - - # Store the expected real-time duration of the video - self.expected_real_time = video_m3u8.expected_real_time - - # Download the video streams and print status - info_dw = video_m3u8.download_streams(f"{Colors.MAGENTA}video", "video") - list_MissingTs.append(info_dw) - self.stopped=list_MissingTs.pop() - - else: - console.log("[cyan]Video [red]already exists.") - - def download_audio(self, downloaded_audio): - """ - Downloads audio tracks if they don't already exist. - - Args: - downloaded_audio (list): A list containing information about audio tracks to download. - """ - logging.info(f"class 'ContentDownloader'; call download_audio() with parameter: {downloaded_audio}") - - for obj_audio in downloaded_audio: - folder_name = os.path.dirname(obj_audio.get('path')) - - # Check if the audio file already exists - if not os.path.exists(obj_audio.get('path')): - - # Create an instance of M3U8_Segments to handle audio segments download - audio_m3u8 = M3U8_Segments(obj_audio.get('url'), folder_name) - - # Get information about the audio segments (e.g., duration, ts files to download) - audio_m3u8.get_info() - - # Download the audio segments and print status - info_dw = audio_m3u8.download_streams(f"{Colors.MAGENTA}audio {Colors.RED}{obj_audio.get('language')}", f"audio_{obj_audio.get('language')}") - list_MissingTs.append(info_dw) - self.stopped=list_MissingTs.pop() - - else: - console.log(f"[cyan]Audio [white]([green]{obj_audio.get('language')}[white]) [red]already exists.") - - def download_subtitle(self, downloaded_subtitle): - """ - Downloads subtitle files if they don't already exist. - - Args: - downloaded_subtitle (list): A list containing information about subtitles to download. - """ - logging.info(f"class 'ContentDownloader'; call download_subtitle() with parameter: {downloaded_subtitle}") - - for obj_subtitle in downloaded_subtitle: - sub_language = obj_subtitle.get('language') - - # Check if the subtitle file already exists - if os.path.exists(obj_subtitle.get("path")): - console.log(f"[cyan]Subtitle [white]([green]{sub_language}[white]) [red]already exists.") - continue # Skip to the next subtitle if it exists - - # Parse the M3U8 file to get the subtitle URI - m3u8_sub_parser = M3U8_Parser() - url = obj_subtitle.get('url') - success = False - - for attempt in range(RETRY_LIMIT): - try: - response = httpx.get(url, headers={'user-agent': get_headers()}, timeout=20) - response.raise_for_status() - m3u8_sub_parser.parse_data( - uri=obj_subtitle.get('uri'), - raw_content=response.text - ) - success = True - break - - except httpx.RequestError as e: - logging.warning(f"Attempt {attempt + 1} failed for URL {url}: {e}") - time.sleep(2) - - if not success: - console.log(f"[red]Failed to download subtitle data for: {sub_language}") - continue - - # Print the status of the subtitle download - console.print(f"[cyan] - Downloading subtitle: [red]{sub_language.lower()}") - - # Download the subtitle content with retry - subtitle_content = None - for attempt in range(RETRY_LIMIT): - try: - response = httpx.get(m3u8_sub_parser.subtitle[-1], headers={'user-agent': get_headers()}, timeout=20) - response.raise_for_status() - subtitle_content = response.content - break - - except httpx.RequestError as e: - logging.warning(f"Attempt {attempt + 1} failed for subtitle content URL: {e}") - time.sleep(2) - - if subtitle_content is None: - console.log(f"[red]Failed to download subtitle content for: {sub_language}") - continue - - # Write the content to the specified file - try: - with open(obj_subtitle.get("path"), "wb") as f: - f.write(subtitle_content) - #console.log(f"[green]Subtitle downloaded successfully: {sub_language}") - - except Exception as e: - logging.error(f"Failed to write subtitle file for {sub_language}: {e}") - #console.log(f"[red]Error writing subtitle file: {sub_language}") - - -class ContentJoiner: - def __init__(self, path_manager): - """ - Initializes the ContentJoiner class. - - Args: - path_manager (PathManager): An instance of PathManager to manage output paths. - """ - self.path_manager: PathManager = path_manager - - def setup(self, downloaded_video, downloaded_audio, downloaded_subtitle, codec = None): - """ - Sets up the content joiner with downloaded media files. - - Args: - downloaded_video (list): List of downloaded video information. - downloaded_audio (list): List of downloaded audio information. - downloaded_subtitle (list): List of downloaded subtitle information. - """ - self.downloaded_video = downloaded_video - self.downloaded_audio = downloaded_audio - self.downloaded_subtitle = downloaded_subtitle - self.codec = codec + def download_audio(self, audio: Dict): + """Downloads audio segments for a specific language track.""" + if self.stopped: + return True - # Initialize flags to check if media is available - self.converted_out_path = None - self.there_is_video = len(downloaded_video) > 0 - self.there_is_audio = len(downloaded_audio) > 0 - self.there_is_subtitle = len(downloaded_subtitle) > 0 + audio_full_url = self.url_fixer.generate_full_url(audio['uri']) + audio_tmp_dir = os.path.join(self.temp_dir, 'audio', audio['language']) - # Start the joining process - self.conversione() + downloader = M3U8_Segments(url=audio_full_url, tmp_folder=audio_tmp_dir) + result = downloader.download_streams(f"Audio {audio['language']}", "audio") + self.missing_segments.append(result) - def conversione(self): + if result.get('stopped', False): + self.stopped = True + return self.stopped + + def download_subtitle(self, sub: Dict): + """Downloads and saves subtitle file for a specific language.""" + if self.stopped: + return True + + content = self.client.request(sub['uri']) + if content: + sub_path = os.path.join(self.temp_dir, 'subs', f"{sub['language']}.vtt") + with open(sub_path, 'w', encoding='utf-8') as f: + f.write(content) + + return self.stopped + + def download_all(self, video_url: str, audio_streams: List[Dict], sub_streams: List[Dict]): """ - Handles the joining of video, audio, and subtitles based on availability. + Downloads all selected streams (video, audio, subtitles). """ + video_file = os.path.join(self.temp_dir, 'video', '0.ts') + if not os.path.exists(video_file): + if self.download_video(video_url): + return True + + for audio in audio_streams: + if self.stopped: + break - # Join audio and video if audio is available - if self.there_is_audio: - if MERGE_AUDIO: + audio_file = os.path.join(self.temp_dir, 'audio', audio['language'], '0.ts') + if not os.path.exists(audio_file): + if self.download_audio(audio): + return True - # Join video with audio tracks - self.converted_out_path = self._join_video_audio() + for sub in sub_streams: + if self.stopped: + break - else: + sub_file = os.path.join(self.temp_dir, 'subs', f"{sub['language']}.vtt") + if not os.path.exists(sub_file): + if self.download_subtitle(sub): + return True + + return self.stopped - # Process each available audio track - for obj_audio in self.downloaded_audio: - language = obj_audio.get('language') - path = obj_audio.get('path') - # Set the new path for regular audio - new_path = self.path_manager.output_filename.replace(".mp4", f"_{language}.mp4") - - try: - - # Rename the audio file to the new path - os.rename(path, new_path) - logging.info(f"Audio moved to {new_path}") - - except Exception as e: - logging.error(f"Failed to move audio {path} to {new_path}: {e}") - - # Convert video if available - if self.there_is_video: - self.converted_out_path = self._join_video() - - # If no audio but video is available, join video - else: - if self.there_is_video: - self.converted_out_path = self._join_video() - - # Join subtitles if available - if self.there_is_subtitle: - if MERGE_SUBTITLE: - if self.converted_out_path is not None: - self.converted_out_path = self._join_video_subtitles(self.converted_out_path) - - else: - - # Process each available subtitle track - for obj_sub in self.downloaded_subtitle: - language = obj_sub.get('language') - path = obj_sub.get('path') - forced = 'forced' in language - - # Adjust the language name and set the new path based on forced status - if forced: - language = language.replace("forced-", "") - new_path = self.path_manager.output_filename.replace(".mp4", f".{language}.forced.vtt") - else: - new_path = self.path_manager.output_filename.replace(".mp4", f".{language}.vtt") - - try: - # Rename the subtitle file to the new path - os.rename(path, new_path) - logging.info(f"Subtitle moved to {new_path}") - - except Exception as e: - logging.error(f"Failed to move subtitle {path} to {new_path}: {e}") - - def _join_video(self): +class MergeManager: + """Handles merging of video, audio, and subtitle streams.""" + def __init__(self, temp_dir: str, parser: M3U8_Parser, audio_streams: List[Dict], sub_streams: List[Dict]): """ - Joins video segments into a single video file. - - Returns: - str: The path to the joined video file. - """ - path_join_video = os.path.join(self.path_manager.base_path, "v_v.mp4") - logging.info(f"JOIN video path: {path_join_video}") - - # Check if the joined video file already exists - if not os.path.exists(path_join_video): - - # Join the video segments into a single video file - join_video( - video_path=self.downloaded_video[0].get('path'), - out_path=path_join_video, - codec=self.codec - ) - - else: - console.log("[red]Output join video already exists.") - - return path_join_video - - def _join_video_audio(self): - """ - Joins video segments with audio tracks into a single video with audio file. - - Returns: - str: The path to the joined video with audio file. - """ - path_join_video_audio = os.path.join(self.path_manager.base_path, "v_a.mp4") - logging.info(f"JOIN audio path: {path_join_video_audio}") - - # Check if the joined video with audio file already exists - if not os.path.exists(path_join_video_audio): - - # Set codec to None if not defined in class - #if not hasattr(self, 'codec'): - # self.codec = None - - # Join the video with audio segments - join_audios( - video_path=self.downloaded_video[0].get('path'), - audio_tracks=self.downloaded_audio, - out_path=path_join_video_audio, - codec=self.codec - ) - - else: - console.log("[red]Output join video and audio already exists.") - - return path_join_video_audio - - def _join_video_subtitles(self, input_path): - """ - Joins subtitles with the video. - Args: - input_path (str): The path to the video file to which subtitles will be added. - - Returns: - str: The path to the video with subtitles file. + temp_dir: Directory containing temporary files + parser: M3U8 parser instance with codec information + audio_streams: List of audio streams to merge + sub_streams: List of subtitle streams to merge """ - path_join_video_subtitle = os.path.join(self.path_manager.base_path, "v_s.mp4") - logging.info(f"JOIN subtitle path: {path_join_video_subtitle}") + self.temp_dir = temp_dir + self.parser = parser + self.audio_streams = audio_streams + self.sub_streams = sub_streams - # Check if the video with subtitles file already exists - if not os.path.exists(path_join_video_subtitle): + def merge(self) -> str: + """ + Merges downloaded streams into final video file. + Returns path to the final merged file. + + Process: + 1. If no audio/subs, just process video + 2. If audio exists, merge with video + 3. If subtitles exist, add them to the video + """ + video_file = os.path.join(self.temp_dir, 'video', '0.ts') + merged_file = video_file - # Join the video with subtitles - join_subtitle( - input_path, - self.downloaded_subtitle, - path_join_video_subtitle + if not self.audio_streams and not self.sub_streams: + merged_file = join_video( + video_path=video_file, + out_path=os.path.join(self.temp_dir, 'video.mp4'), + codec=self.parser.codec ) - return path_join_video_subtitle + else: + if MERGE_AUDIO and self.audio_streams: + audio_tracks = [{ + 'path': os.path.join(self.temp_dir, 'audio', a['language'], '0.ts'), + 'name': a['language'] + } for a in self.audio_streams] + + merged_audio_path = os.path.join(self.temp_dir, 'merged_audio.mp4') + merged_file = join_audios( + video_path=video_file, + audio_tracks=audio_tracks, + out_path=merged_audio_path, + codec=self.parser.codec + ) + + if MERGE_SUBTITLE and self.sub_streams: + sub_tracks = [{ + 'path': os.path.join(self.temp_dir, 'subs', f"{s['language']}.vtt"), + 'language': s['language'] + } for s in self.sub_streams] + + merged_subs_path = os.path.join(self.temp_dir, 'final.mp4') + merged_file = join_subtitle( + video_path=merged_file, + subtitles_list=sub_tracks, + out_path=merged_subs_path + ) + + return merged_file class HLS_Downloader: - stopped = bool(False) - - def __init__(self, output_filename: str=None, m3u8_playlist: str=None, m3u8_index: str=None, is_playlist_url: bool=True, is_index_url: bool=True): + """Main class for HLS video download and processing.""" + def __init__(self, m3u8_url: str, output_path: Optional[str] = None): + self.m3u8_url = m3u8_url + self.path_manager = PathManager(m3u8_url, output_path) + self.client = HLSClient() + self.m3u8_manager = M3U8Manager(m3u8_url, self.client) + self.download_manager: Optional[DownloadManager] = None + self.merge_manager: Optional[MergeManager] = None + + def start(self) -> Dict[str, Any]: """ - Initializes the HLS_Downloader class. - - Args: - output_filename (str): The desired output filename for the downloaded content. - m3u8_playlist (str): The URL or content of the m3u8 playlist. - m3u8_index (str): The index URL for m3u8 streams. - is_playlist_url (bool): Flag indicating if the m3u8_playlist is a URL. - is_index_url (bool): Flag indicating if the m3u8_index is a URL. - """ - if ((m3u8_playlist == None or m3u8_playlist == "") and output_filename is None) or ((m3u8_index == None or m3u8_index == "") and output_filename is None): - logging.info(f"class 'HLS_Downloader'; call __init__(); no parameter") - sys.exit(0) - - self.output_filename = self._generate_output_filename(output_filename, m3u8_playlist, m3u8_index) - self.path_manager = PathManager(self.output_filename) - self.download_tracker = DownloadTracker(self.path_manager) - self.content_extractor = ContentExtractor() - self.content_downloader = ContentDownloader() - self.content_joiner = ContentJoiner(self.path_manager) - - self.m3u8_playlist = m3u8_playlist - self.m3u8_index = m3u8_index - self.is_playlist_url = is_playlist_url - self.is_index_url = is_index_url - self.expected_real_time = None - self.instace_parserClass = M3U8_Parser() - - self.request_m3u8_playlist = None - self.request_m3u8_index = None - if (m3u8_playlist == None or m3u8_playlist == ""): - self.request_m3u8_index = HttpClient().get(self.m3u8_index) - if (m3u8_index == None or m3u8_index == ""): - self.request_m3u8_playlist = HttpClient().get(self.m3u8_playlist) - - def _generate_output_filename(self, output_filename, m3u8_playlist, m3u8_index): - """ - Generates a valid output filename based on provided parameters. - - Args: - output_filename (str): The desired output filename. - m3u8_playlist (str): The m3u8 playlist URL or content. - m3u8_index (str): The m3u8 index URL. - + Main execution flow with handling for both index and playlist M3U8s. + Returns: - str: The generated output filename. - """ - root_path = config_manager.get('DEFAULT', 'root_path') - new_filename = None - new_folder = os.path.join(root_path, "undefined") - logging.info(f"class 'HLS_Downloader'; call _generate_output_filename(); destination folder: {new_folder}") + Dict containing: + - path: Output file path + - url: Original M3U8 URL + - is_master: Whether the M3U8 was a master playlist + Or raises an exception if there's an error + """ + if TELEGRAM_BOT: + bot = get_bot_instance() - # Auto-generate output file name if not present - if (output_filename is None) or ("mp4" not in output_filename): - if m3u8_playlist is not None: - new_filename = os.path.join(new_folder, compute_sha1_hash(m3u8_playlist) + ".mp4") - else: - new_filename = os.path.join(new_folder, compute_sha1_hash(m3u8_index) + ".mp4") + try: + if os.path.exists(self.path_manager.output_path): + console.print(f"[red]Output file {self.path_manager.output_path} already exists![/red]") + response = { + 'path': self.path_manager.output_path, + 'url': self.m3u8_url, + 'is_master': False, + 'error': 'File already exists', + 'stopped': False + } + if TELEGRAM_BOT: + bot.send_message(response) + return response - else: + self.path_manager.setup_directories() - # Check if output_filename contains a folder path - folder, base_name = os.path.split(output_filename) + # Parse M3U8 and determine if it's a master playlist + self.m3u8_manager.parse() + self.m3u8_manager.select_streams() + self.m3u8_manager.log_selection() + + self.download_manager = DownloadManager( + temp_dir=self.path_manager.temp_dir, + client=self.client, + url_fixer=self.m3u8_manager.url_fixer + ) - # If no folder is specified, default to 'undefined' - if not folder: - folder = new_folder - - # Sanitize base name and folder - folder = os_manager.get_sanitize_path(folder) - base_name = os_manager.get_sanitize_file(base_name) - os_manager.create_path(folder) - - # Parse to only ASCII for compatibility across platforms - new_filename = os.path.join(folder, base_name) - - logging.info(f"class 'HLS_Downloader'; call _generate_output_filename(); return path: {new_filename}") - return new_filename - - def start(self): - """ - Initiates the downloading process. Checks if the output file already exists and proceeds with processing the playlist or index. - """ - if TELEGRAM_BOT: - bot = get_bot_instance() - - if os.path.exists(self.output_filename): - console.log("[red]Output file already exists.") - if TELEGRAM_BOT: - bot.send_message(f"Contenuto giΓ  scaricato!", None) - return 400 - - self.path_manager.create_directories() - - # Determine whether to process a playlist or index - if self.m3u8_playlist: - if self.m3u8_playlist is not None: - if self.request_m3u8_playlist != 404: - logging.info(f"class 'HLS_Downloader'; call start(); parse m3u8 data") - - self.instace_parserClass.parse_data(uri=self.m3u8_playlist, raw_content=self.request_m3u8_playlist) - is_masterPlaylist = self.instace_parserClass.is_master_playlist - - # Check if it's a real master playlist - if is_masterPlaylist: - if not GET_ONLY_LINK: - r_proc = self._process_playlist() - - if r_proc == 404: - return 404 - else: - return None - - else: - return { - 'path': self.output_filename, - 'url': self.m3u8_playlist, - } - - else: - console.log("[red]Error: URL passed to M3U8_Parser is an index playlist; expected a master playlist. Crucimorfo strikes again!") - else: - console.log(f"[red]Error: m3u8_playlist failed request for: {self.m3u8_playlist}") - else: - console.log("[red]Error: m3u8_playlist is None") - - elif self.m3u8_index: - if self.m3u8_index is not None: - if self.request_m3u8_index != 404: - logging.info(f"class 'HLS_Downloader'; call start(); parse m3u8 data") - - self.instace_parserClass.parse_data(uri=self.m3u8_index, raw_content=self.request_m3u8_index) - is_masterPlaylist = self.instace_parserClass.is_master_playlist - - # Check if it's a real index playlist - if not is_masterPlaylist: - if not GET_ONLY_LINK: - self._process_index() - return None - - else: - return { - 'path': self.output_filename, - 'url': self.m3u8_index, - } - - else: - console.log("[red]Error: URL passed to M3U8_Parser is an master playlist; expected a index playlist. Crucimorfo strikes again!") - else: - console.log("[red]Error: m3u8_index failed request") - else: - console.log("[red]Error: m3u8_index is None") - - # Reset - self._reset() - - def _clean(self, out_path: str) -> None: - """ - Cleans up temporary files and folders after downloading and processing. - - Args: - out_path (str): The path of the output file to be cleaned up. - """ - if TELEGRAM_BOT: - bot = get_bot_instance() - - # Check if the final output file exists - logging.info(f"Check if end file converted exists: {out_path}") - if out_path is None or not os.path.isfile(out_path): - logging.error("Video file converted does not exist.") - sys.exit(0) - - # Rename the output file to the desired output filename if it does not already exist - if not os.path.exists(self.output_filename): - missing_ts = False - missing_info = "" - - # Rename the converted file to the specified output filename - os.rename(out_path, self.output_filename) - - # Calculate file size and duration for reporting - formatted_size = internet_manager.format_file_size(os.path.getsize(self.output_filename)) - formatted_duration = print_duration_table(self.output_filename, description=False, return_string=True) - - # Collect info about type missing - for item in list_MissingTs: - if int(item['nFailed']) >= 1: - missing_ts = True - missing_info += f"[red]TS Failed: {item['nFailed']} {item['type']} tracks[/red]\n" - - # Prepare the report panel content - print("") - panel_content = ( - f"[bold green]Download completed![/bold green]\n" - f"[cyan]File size: [bold red]{formatted_size}[/bold red]\n" - f"[cyan]Duration: [bold]{formatted_duration}[/bold]\n" - f"[cyan]Output: [bold]{os.path.abspath(self.output_filename)}[/bold]" + # Check if download was stopped + download_stopped = self.download_manager.download_all( + video_url=self.m3u8_manager.video_url, + audio_streams=self.m3u8_manager.audio_streams, + sub_streams=self.m3u8_manager.sub_streams ) - if missing_ts: - panel_content += f"\n{missing_info}" + if download_stopped: + return { + 'path': None, + 'url': self.m3u8_url, + 'is_master': self.m3u8_manager.is_master, + 'error': 'Download stopped by user', + 'stopped': True + } - # Display the download completion message - console.print(Panel( - panel_content, - title=f"{os.path.basename(self.output_filename.replace('.mp4', ''))}", - border_style="green" - )) + self.merge_manager = MergeManager( + temp_dir=self.path_manager.temp_dir, + parser=self.m3u8_manager.parser, + audio_streams=self.m3u8_manager.audio_streams, + sub_streams=self.m3u8_manager.sub_streams + ) - if TELEGRAM_BOT: - message = f"Download completato\nDimensione: {formatted_size}\nDurata: {formatted_duration}\nPercorso: {os.path.abspath(self.output_filename)}" - # Rimuovere i tag di colore usando una regex - clean_message = re.sub(r'\[[a-zA-Z]+\]', '', message) - # Invio a telegram - bot.send_message(clean_message, None) + final_file = self.merge_manager.merge() + self.path_manager.move_final_file(final_file) + self.path_manager.cleanup() - # Handle missing segments - if missing_ts: - os.rename(self.output_filename, self.output_filename.replace(".mp4", "_failed.mp4")) + self._print_summary() - # Delete all temporary files except for the output file - os_manager.remove_files_except_one(self.path_manager.base_path, os.path.basename(self.output_filename.replace(".mp4", "_failed.mp4"))) + return { + 'path': self.path_manager.output_path, + 'url': self.m3u8_url, + 'is_master': self.m3u8_manager.is_master, + 'stopped': False + } - # Remove the base folder if specified - if REMOVE_SEGMENTS_FOLDER: - os_manager.remove_folder(self.path_manager.base_path) - - else: - logging.info("Video file converted already exists.") - - def _valida_playlist(self): - """ - Validates the m3u8 playlist content, saves it to a temporary file, and collects playlist information. - """ - logging.info("class 'HLS_Downloader'; call _valida_playlist()") - - # Retrieve the m3u8 playlist content - if self.is_playlist_url: - if self.request_m3u8_playlist != 404: - m3u8_playlist_text = self.request_m3u8_playlist - m3u8_url_fixer.set_playlist(self.m3u8_playlist) - - else: - logging.info(f"class 'HLS_Downloader'; call _process_playlist(); return 404") - return 404 - - else: - m3u8_playlist_text = self.m3u8_playlist - - # Check if the m3u8 content is valid - if m3u8_playlist_text is None: - console.log("[red]Playlist m3u8 to download is empty.") - sys.exit(0) - - # Save the m3u8 playlist text to a temporary file - open(os.path.join(self.path_manager.base_temp, "playlist.m3u8"), "w+", encoding="utf-8").write(m3u8_playlist_text) - - # Collect information about the playlist - if self.is_playlist_url: - self.content_extractor.start(self.instace_parserClass) - else: - self.content_extractor.start("https://fake.com", m3u8_playlist_text) - - def _process_playlist(self): - """ - Processes the m3u8 playlist to download video, audio, and subtitles. - """ - self._valida_playlist() - - # Add downloaded elements to the tracker - self.download_tracker.add_video(self.content_extractor.m3u8_index) - self.download_tracker.add_audio(self.content_extractor.list_available_audio) - self.download_tracker.add_subtitle(self.content_extractor.list_available_subtitles) - - # Download each type of content - if len(self.download_tracker.downloaded_video) > 0: - self.content_downloader.download_video(self.download_tracker.downloaded_video) - if len(self.download_tracker.downloaded_audio) > 0: - self.content_downloader.download_audio(self.download_tracker.downloaded_audio) - if len(self.download_tracker.downloaded_subtitle) > 0: - self.content_downloader.download_subtitle(self.download_tracker.downloaded_subtitle) - - # Join downloaded content - self.content_joiner.setup(self.download_tracker.downloaded_video, self.download_tracker.downloaded_audio, self.download_tracker.downloaded_subtitle, self.content_extractor.codec) - - # Clean up temporary files and directories - self._clean(self.content_joiner.converted_out_path) + except Exception as e: + error_msg = str(e) + console.print(f"[red]Download failed: {error_msg}[/red]") + logging.error("Download error", exc_info=True) + + return { + 'path': None, + 'url': self.m3u8_url, + 'is_master': getattr(self.m3u8_manager, 'is_master', None), + 'error': error_msg, + 'stopped': False + } - def _process_index(self): - """ - Processes the m3u8 index to download only video. - """ - m3u8_url_fixer.set_playlist(self.m3u8_index) + def _print_summary(self): + """Prints download summary including file size, duration, and any missing segments.""" + if TELEGRAM_BOT: + bot = get_bot_instance() - # Download video - self.download_tracker.add_video(self.m3u8_index) - self.content_downloader.download_video(self.download_tracker.downloaded_video) - - # Join video - self.content_joiner.setup(self.download_tracker.downloaded_video, [], []) + missing_ts = False + missing_info = "" + for item in self.download_manager.missing_segments: + if int(item['nFailed']) >= 1: + missing_ts = True + missing_info += f"[red]TS Failed: {item['nFailed']} {item['type']} tracks[/red]\n" - # Clean up temporary files and directories - self._clean(self.content_joiner.converted_out_path) + file_size = internet_manager.format_file_size(os.path.getsize(self.path_manager.output_path)) + duration = print_duration_table(self.path_manager.output_path, description=False, return_string=True) - def _reset(self): - global list_MissingTs, m3u8_url_fixer + panel_content = ( + f"[bold green]Download completed![/bold green]\n" + f"[cyan]File size: [bold red]{file_size}[/bold red]\n" + f"[cyan]Duration: [bold]{duration}[/bold]\n" + f"[cyan]Output: [bold]{os.path.abspath(self.path_manager.output_path)}[/bold]" + ) - m3u8_url_fixer.reset_playlist() - list_MissingTs = [] + if TELEGRAM_BOT: + message = f"Download completato\nDimensione: {file_size}\nDurata: {duration}\nPercorso: {os.path.abspath(self.path_manager.output_path)}" + clean_message = re.sub(r'\[[a-zA-Z]+\]', '', message) + bot.send_message(clean_message, None) + + if missing_ts: + panel_content += f"\n{missing_info}" + os.rename(self.path_manager.output_path, self.path_manager.output_path.replace(".mp4", "_failed.mp4")) + + console.print(Panel( + panel_content, + title=f"{os.path.basename(self.path_manager.output_path.replace('.mp4', ''))}", + border_style="green" + )) \ No newline at end of file diff --git a/StreamingCommunity/Lib/Downloader/HLS/segments.py b/StreamingCommunity/Lib/Downloader/HLS/segments.py index 51d3532..b17b453 100644 --- a/StreamingCommunity/Lib/Downloader/HLS/segments.py +++ b/StreamingCommunity/Lib/Downloader/HLS/segments.py @@ -47,7 +47,6 @@ PROXY_START_MAX = config_manager.get_float('REQUESTS', 'proxy_start_max') DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workser') DEFAULT_AUDIO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_audio_workser') MAX_TIMEOOUT = config_manager.get_int("REQUESTS", "timeout") -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') @@ -70,7 +69,7 @@ class M3U8_Segments: # Util class self.decryption: M3U8_Decryption = None - self.class_ts_estimator = M3U8_Ts_Estimator(0) + self.class_ts_estimator = M3U8_Ts_Estimator(0, self) self.class_url_fixer = M3U8_UrlFix(url) # Sync @@ -88,6 +87,8 @@ class M3U8_Segments: self.info_maxRetry = 0 self.info_nRetry = 0 self.info_nFailed = 0 + self.active_retries = 0 + self.active_retries_lock = threading.Lock() def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes: key_uri = urljoin(self.url, m3u8_parser.keys.get('uri')) @@ -232,10 +233,17 @@ class M3U8_Segments: self.queue.put((index, None)) # Marker for failed segment progress_bar.update(1) self.info_nFailed += 1 + return + + with self.active_retries_lock: + self.active_retries += 1 sleep_time = backoff_factor * (2 ** attempt) logging.info(f"Retrying segment {index} in {sleep_time} seconds...") time.sleep(sleep_time) + + with self.active_retries_lock: + self.active_retries -= 1 def write_segments_to_file(self): """ @@ -296,11 +304,7 @@ class M3U8_Segments: - description: Description to insert on tqdm bar - type (str): Type of download: 'video' or 'audio' """ - - if TELEGRAM_BOT: - # Viene usato per lo screen - console.log("####") - + self.get_info() self.setup_interrupt_handler() progress_bar = tqdm( diff --git a/StreamingCommunity/Lib/Downloader/MP4/downloader.py b/StreamingCommunity/Lib/Downloader/MP4/downloader.py index 9afd4b4..acae652 100644 --- a/StreamingCommunity/Lib/Downloader/MP4/downloader.py +++ b/StreamingCommunity/Lib/Downloader/MP4/downloader.py @@ -1,12 +1,11 @@ # 09.06.24 import os -import signal, re +import re import sys -import ssl -import certifi +import signal import logging -import atexit + # External libraries import httpx @@ -19,6 +18,7 @@ from StreamingCommunity.Util.color import Colors from StreamingCommunity.Util.console import console, Panel from StreamingCommunity.Util._jsonConfig import config_manager from StreamingCommunity.Util.os import internet_manager +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance # Logic class @@ -29,16 +29,15 @@ from ...FFmpeg import print_duration_table import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance -TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') - # Config GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link') TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform) REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout') +TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') + + #Ending constant KILL_HANDLER = bool(False) @@ -54,14 +53,13 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No - headers_ (dict, optional): Custom headers for the request. """ if TELEGRAM_BOT: - bot = get_bot_instance() - # Viene usato per lo screen - console.log("####") + bot = get_bot_instance() + console.log("####") if os.path.exists(path): console.log("[red]Output file already exists.") if TELEGRAM_BOT: - bot.send_message(f"Contenuto giΓ  scaricato!", None) + bot.send_message(f"Contenuto giΓ  scaricato!", None) return 400 # Early return for link-only mode @@ -94,8 +92,8 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No try: # Create a custom transport that bypasses SSL verification transport = httpx.HTTPTransport( - verify=False, # Disable SSL certificate verification - http2=True # Optional: enable HTTP/2 support + verify=False, + http2=True ) # Download with streaming and progress tracking @@ -137,7 +135,6 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No Parameters: - args (tuple): The signal arguments (to prevent errors). """ - if(downloaded MAX_DOWNLOAD_SIZE: # break @@ -172,11 +169,9 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No )) if TELEGRAM_BOT: - message = f"Download completato\nDimensione: {internet_manager.format_file_size(os.path.getsize(path))}\nDurata: {print_duration_table(path, description=False, return_string=True)}\nTitolo: {os.path.basename(path.replace('.mp4', ''))}" - # Rimuovere i tag di colore usando una regex - clean_message = re.sub(r'\[[a-zA-Z]+\]', '', message) - # Invio a telegram - bot.send_message(clean_message, None) + message = f"Download completato\nDimensione: {internet_manager.format_file_size(os.path.getsize(path))}\nDurata: {print_duration_table(path, description=False, return_string=True)}\nTitolo: {os.path.basename(path.replace('.mp4', ''))}" + clean_message = re.sub(r'\[[a-zA-Z]+\]', '', message) + bot.send_message(clean_message, None) return path @@ -184,16 +179,6 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No console.print("[bold red]Download failed or file is empty.[/bold red]") return None - except httpx.HTTPStatusError as http_err: - logging.error(f"HTTP error occurred: {http_err}") - console.print(f"[bold red]HTTP Error: {http_err}[/bold red]") - return None - - except httpx.RequestError as req_err: - logging.error(f"Request error: {req_err}") - console.print(f"[bold red]Request Error: {req_err}[/bold red]") - return None - except Exception as e: logging.error(f"Unexpected error during download: {e}") console.print(f"[bold red]Unexpected Error: {e}[/bold red]") diff --git a/StreamingCommunity/Lib/Downloader/TOR/downloader.py b/StreamingCommunity/Lib/Downloader/TOR/downloader.py index d5a79c0..ea45fd8 100644 --- a/StreamingCommunity/Lib/Downloader/TOR/downloader.py +++ b/StreamingCommunity/Lib/Downloader/TOR/downloader.py @@ -27,6 +27,7 @@ PORT = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['port']) USERNAME = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['user']) PASSWORD = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['pass']) + # Config TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform) REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout') diff --git a/StreamingCommunity/Lib/FFmpeg/command.py b/StreamingCommunity/Lib/FFmpeg/command.py index 4129c88..7665a8f 100644 --- a/StreamingCommunity/Lib/FFmpeg/command.py +++ b/StreamingCommunity/Lib/FFmpeg/command.py @@ -110,6 +110,8 @@ def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None): capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join video") print() + return out_path + def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: str, codec: M3U8_Codec = None): """ @@ -204,6 +206,8 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join audio") print() + return out_path + def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_path: str): """ @@ -255,4 +259,6 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat console.log(f"[purple]FFmpeg [white][[cyan]Join subtitle[white]] ...") with suppress_output(): capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join subtitle") - print() \ No newline at end of file + print() + + return out_path \ No newline at end of file diff --git a/StreamingCommunity/Lib/M3U8/estimator.py b/StreamingCommunity/Lib/M3U8/estimator.py index 543e39b..e9abab4 100644 --- a/StreamingCommunity/Lib/M3U8/estimator.py +++ b/StreamingCommunity/Lib/M3U8/estimator.py @@ -22,7 +22,7 @@ TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform) class M3U8_Ts_Estimator: - def __init__(self, total_segments: int): + def __init__(self, total_segments: int, segments_instance=None): """ Initialize the M3U8_Ts_Estimator object. @@ -32,6 +32,7 @@ class M3U8_Ts_Estimator: self.ts_file_sizes = [] self.now_downloaded_size = 0 self.total_segments = total_segments + self.segments_instance = segments_instance self.lock = threading.Lock() self.speed = {"upload": "N/A", "download": "N/A"} @@ -102,7 +103,6 @@ class M3U8_Ts_Estimator: return "Error" def update_progress_bar(self, total_downloaded: int, duration: float, progress_counter: tqdm) -> None: - """Updates the progress bar with download information.""" try: self.add_ts_file(total_downloaded * self.total_segments, total_downloaded, duration) @@ -120,21 +120,25 @@ class M3U8_Ts_Estimator: if len(speed_data) >= 2: average_internet_speed = speed_data[0] average_internet_unit = speed_data[1] - else: average_internet_speed = "N/A" average_internet_unit = "" + # Retrieve retry count from segments_instance + retry_count = self.segments_instance.active_retries if self.segments_instance else 0 progress_str = ( f"{Colors.WHITE}[ {Colors.GREEN}{number_file_downloaded} {Colors.WHITE}< " f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} " - f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}" + f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit} " + f"{Colors.WHITE}| {Colors.GREEN}CRR {Colors.RED}{retry_count}" ) - else: + # Retrieve retry count from segments_instance + retry_count = self.segments_instance.active_retries if self.segments_instance else 0 progress_str = ( f"{Colors.WHITE}[ {Colors.GREEN}{number_file_downloaded} {Colors.WHITE}< " - f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}" + f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} " + f"{Colors.WHITE}| {Colors.GREEN}CRR {Colors.RED}{retry_count}" ) progress_counter.set_postfix_str(progress_str) diff --git a/StreamingCommunity/HelpTg/__init__.py b/StreamingCommunity/TelegramHelp/__init__.py similarity index 100% rename from StreamingCommunity/HelpTg/__init__.py rename to StreamingCommunity/TelegramHelp/__init__.py diff --git a/StreamingCommunity/TelegramHelp/request_manager.py b/StreamingCommunity/TelegramHelp/request_manager.py new file mode 100644 index 0000000..51dd5f3 --- /dev/null +++ b/StreamingCommunity/TelegramHelp/request_manager.py @@ -0,0 +1,82 @@ +# 04.02.25 +# Made by: @GiuPic + +import json +import time +from typing import Optional + +class RequestManager: + _instance = None + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, json_file: str = "active_requests.json"): + if not hasattr(self, 'initialized'): + self.json_file = json_file + self.initialized = True + self.on_response_callback = None + + def create_request(self, type: str) -> str: + request_data = { + "type": type, + "response": None, + "timestamp": time.time() + } + + with open(self.json_file, "w") as f: + json.dump(request_data, f) + + return "Ok" + + def save_response(self, message_text: str) -> bool: + try: + # Carica il file JSON + with open(self.json_file, "r") as f: + data = json.load(f) + + # Controlla se esiste la chiave 'type' e se la risposta Γ¨ presente + if "type" in data and "response" in data: + data["response"] = message_text # Aggiorna la risposta + + with open(self.json_file, "w") as f: + json.dump(data, f, indent=4) + + return True + else: + return False + + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"⚠️ save_response - errore: {e}") + return False + + def get_response(self) -> Optional[str]: + try: + with open(self.json_file, "r") as f: + data = json.load(f) + + # Verifica se esiste la chiave "response" + if "response" in data: + response = data["response"] # Ottieni la risposta direttamente + + if response is not None and self.on_response_callback: + self.on_response_callback(response) + + return response + + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"get_response - errore: {e}") + return None + + def clear_file(self) -> bool: + try: + with open(self.json_file, "w") as f: + json.dump({}, f) + print(f"File {self.json_file} Γ¨ stato svuotato con successo.") + return True + + except Exception as e: + print(f"⚠️ clear_file - errore: {e}") + return False \ No newline at end of file diff --git a/StreamingCommunity/HelpTg/session.py b/StreamingCommunity/TelegramHelp/session.py old mode 100755 new mode 100644 similarity index 69% rename from StreamingCommunity/HelpTg/session.py rename to StreamingCommunity/TelegramHelp/session.py index b81add8..7a26034 --- a/StreamingCommunity/HelpTg/session.py +++ b/StreamingCommunity/TelegramHelp/session.py @@ -1,24 +1,22 @@ +# 04.02.25 +# Made by: @GiuPic + import json session_data = {} def set_session(value): - # salvo script_id in session_data session_data['script_id'] = value def get_session(): - # controllo se script_id Γ¨ presente in session_data return session_data.get('script_id', 'unknown') def updateScriptId(screen_id, titolo): - # definisco il nome del file json json_file = "scripts.json" try: - # apro il file json with open(json_file, 'r') as f: scripts_data = json.load(f) except FileNotFoundError: - # Se il file non esiste, inizializzo la lista vuota scripts_data = [] # cerco lo script con lo screen_id @@ -26,37 +24,33 @@ def updateScriptId(screen_id, titolo): if script["screen_id"] == screen_id: # se trovo il match, aggiorno il titolo script["titolo"] = titolo + # aggiorno il file json with open(json_file, 'w') as f: json.dump(scripts_data, f, indent=4) - #print(f"Titolo aggiornato per screen_id {screen_id}") + return - # se non trovo nessuno script con lo screen_id print(f"Screen_id {screen_id} non trovato.") -# creo la funzione che elimina lo script con lo screen_id specificato def deleteScriptId(screen_id): - # definisco il nome del file json json_file = "scripts.json" try: - # apro il file json with open(json_file, 'r') as f: scripts_data = json.load(f) except FileNotFoundError: - # Se il file non esiste, inizializzo la lista vuota scripts_data = [] - # cerco lo script con lo screen_id for script in scripts_data: if script["screen_id"] == screen_id: # se trovo il match, elimino lo script scripts_data.remove(script) + # aggiorno il file json with open(json_file, 'w') as f: json.dump(scripts_data, f, indent=4) + print(f"Script eliminato per screen_id {screen_id}") return - # se non trovo nessuno script con lo screen_id print(f"Screen_id {screen_id} non trovato.") \ No newline at end of file diff --git a/StreamingCommunity/HelpTg/telegram_bot.py b/StreamingCommunity/TelegramHelp/telegram_bot.py similarity index 58% rename from StreamingCommunity/HelpTg/telegram_bot.py rename to StreamingCommunity/TelegramHelp/telegram_bot.py index 4672a92..6b09cac 100644 --- a/StreamingCommunity/HelpTg/telegram_bot.py +++ b/StreamingCommunity/TelegramHelp/telegram_bot.py @@ -1,13 +1,24 @@ -import telebot -from telebot import types +# 04.02.25 +# Made by: @GiuPic + +import os +import re +import sys import time import uuid -import subprocess -import os, re, sys import json -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) -from StreamingCommunity.HelpTg.request_manager import RequestManager import threading +import subprocess + + +# External libraries +import telebot + + +# Fix import +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) +from StreamingCommunity.TelegramHelp.request_manager import RequestManager + # Funzione per caricare variabili da un file .env def load_env(file_path="../../.env"): @@ -21,6 +32,7 @@ def load_env(file_path="../../.env"): # Carica le variabili load_env() + class TelegramBot: _instance = None _config_file = "../../bot_config.json" @@ -30,11 +42,15 @@ class TelegramBot: if cls._instance is None: # Prova a caricare la configurazione e inizializzare il bot if os.path.exists(cls._config_file): - with open(cls._config_file, 'r') as f: + with open(cls._config_file, "r") as f: config = json.load(f) - cls._instance = cls.init_bot(config['token'], config['authorized_user_id']) + cls._instance = cls.init_bot( + config["token"], config["authorized_user_id"] + ) else: - raise Exception("Bot non ancora inizializzato. Chiamare prima init_bot() con token e authorized_user_id") + raise Exception( + "Bot non ancora inizializzato. Chiamare prima init_bot() con token e authorized_user_id" + ) return cls._instance @classmethod @@ -42,16 +58,12 @@ class TelegramBot: if cls._instance is None: cls._instance = cls(token, authorized_user_id) # Salva la configurazione - config = { - 'token': token, - 'authorized_user_id': authorized_user_id - } - with open(cls._config_file, 'w') as f: + config = {"token": token, "authorized_user_id": authorized_user_id} + with open(cls._config_file, "w") as f: json.dump(config, f) return cls._instance def __init__(self, token, authorized_user_id): - def monitor_scripts(): while True: try: @@ -68,25 +80,41 @@ class TelegramBot: for script in scripts_data: screen_exists = False try: - existing_screens = subprocess.check_output(["screen", "-list"]).decode('utf-8') + existing_screens = subprocess.check_output( + ["screen", "-list"] + ).decode("utf-8") if script["screen_id"] in existing_screens: screen_exists = True except subprocess.CalledProcessError: pass # Se il comando fallisce, significa che non ci sono screen attivi. if screen_exists: - if "titolo" not in script and script["status"] == "running" and (current_time - script["start_time"]) > 600: + if ( + "titolo" not in script + and script["status"] == "running" + and (current_time - script["start_time"]) > 600 + ): # Prova a terminare la sessione screen try: - subprocess.check_output(["screen", "-S", script["screen_id"], "-X", "quit"]) - print(f"βœ… La sessione screen con ID {script['screen_id']} Γ¨ stata fermata automaticamente.") + subprocess.check_output( + ["screen", "-S", script["screen_id"], "-X", "quit"] + ) + print( + f"βœ… La sessione screen con ID {script['screen_id']} Γ¨ stata fermata automaticamente." + ) except subprocess.CalledProcessError: - print(f"⚠️ Impossibile fermare la sessione screen con ID {script['screen_id']}.") - print(f"⚠️ Lo script con ID {script['screen_id']} ha superato i 10 minuti e verrΓ  rimosso.") + print( + f"⚠️ Impossibile fermare la sessione screen con ID {script['screen_id']}." + ) + print( + f"⚠️ Lo script con ID {script['screen_id']} ha superato i 10 minuti e verrΓ  rimosso." + ) else: scripts_data_to_save.append(script) else: - print(f"⚠️ La sessione screen con ID {script['screen_id']} non esiste piΓΉ e verrΓ  rimossa.") + print( + f"⚠️ La sessione screen con ID {script['screen_id']} non esiste piΓΉ e verrΓ  rimossa." + ) # Salva la lista aggiornata, senza gli script scaduti o le screen non esistenti with open("../../scripts.json", "w") as f: @@ -98,9 +126,10 @@ class TelegramBot: monitor_thread = threading.Thread(target=monitor_scripts, daemon=True) monitor_thread.start() - if TelegramBot._instance is not None: - raise Exception("Questa classe Γ¨ un singleton! Usa get_instance() per ottenere l'istanza.") + raise Exception( + "Questa classe Γ¨ un singleton! Usa get_instance() per ottenere l'istanza." + ) self.token = token self.authorized_user_id = authorized_user_id @@ -113,67 +142,30 @@ class TelegramBot: def register_handlers(self): - """ @self.bot.message_handler(commands=['start']) + """@self.bot.message_handler(commands=['start']) def start(message): - self.handle_start(message) """ + self.handle_start(message)""" - @self.bot.message_handler(commands=['get_id']) + @self.bot.message_handler(commands=["get_id"]) def get_id(message): self.handle_get_id(message) - @self.bot.message_handler(commands=['start']) + @self.bot.message_handler(commands=["start"]) def start_script(message): self.handle_start_script(message) - @self.bot.message_handler(commands=['list']) + @self.bot.message_handler(commands=["list"]) def list_scripts(message): self.handle_list_scripts(message) - @self.bot.message_handler(commands=['stop']) + @self.bot.message_handler(commands=["stop"]) def stop_script(message): self.handle_stop_script(message) - @self.bot.message_handler(commands=['screen']) + @self.bot.message_handler(commands=["screen"]) def screen_status(message): self.handle_screen_status(message) - """ @self.bot.message_handler(commands=['replay']) - def send_welcome(message): - # Crea una tastiera personalizzata - markup = types.ReplyKeyboardMarkup(row_width=2) - itembtn1 = types.KeyboardButton('Start') - itembtn2 = types.KeyboardButton('Lista') - markup.add(itembtn1, itembtn2) - - # Invia un messaggio con la tastiera - self.bot.send_message(message.chat.id, "Scegli un'opzione:", reply_markup=markup) """ - - """@self.bot.message_handler(commands=['inline']) - def send_welcome(message): - # Crea una tastiera inline - markup = types.InlineKeyboardMarkup() - itembtn1 = types.InlineKeyboardButton('Azione 1', callback_data='action1') - itembtn2 = types.InlineKeyboardButton('Azione 2', callback_data='action2') - itembtn3 = types.InlineKeyboardButton('Azione 3', callback_data='action3') - markup.add(itembtn1, itembtn2, itembtn3) - - # Invia un messaggio con la tastiera inline - self.bot.send_message(message.chat.id, "Scegli un'opzione:", reply_markup=markup) - - # Gestisce le callback delle tastiere inline - @self.bot.callback_query_handler(func=lambda call: True) - def handle_callback(call): - if call.data == 'action1': - self.bot.answer_callback_query(call.id, "Hai scelto Azione 1!") - self.bot.send_message(call.message.chat.id, "Hai eseguito Azione 1!") - elif call.data == 'action2': - self.bot.answer_callback_query(call.id, "Hai scelto Azione 2!") - self.bot.send_message(call.message.chat.id, "Hai eseguito Azione 2!") - elif call.data == 'action3': - self.bot.answer_callback_query(call.id, "Hai scelto Azione 3!") - self.bot.send_message(call.message.chat.id, "Hai eseguito Azione 3!") - """ - @self.bot.message_handler(func=lambda message: True) def handle_all_messages(message): self.handle_response(message) @@ -188,7 +180,11 @@ class TelegramBot: return print(f"Il tuo ID utente Γ¨: `{message.from_user.id}`") - self.bot.send_message(message.chat.id, f"Il tuo ID utente Γ¨: `{message.from_user.id}`", parse_mode="Markdown") + self.bot.send_message( + message.chat.id, + f"Il tuo ID utente Γ¨: `{message.from_user.id}`", + parse_mode="Markdown", + ) def handle_start_script(self, message): if not self.is_authorized(message.from_user.id): @@ -197,10 +193,6 @@ class TelegramBot: return screen_id = str(uuid.uuid4())[:8] - #screen_id = '0000' - - # Impostare a True per avviare il test_run.py in modalitΓ  verbose per il debug - debug_mode = os.getenv("DEBUG") verbose = debug_mode @@ -210,16 +202,28 @@ class TelegramBot: # Verifica se lo screen con il nome esiste giΓ  try: subprocess.check_output(["screen", "-list"]) - existing_screens = subprocess.check_output(["screen", "-list"]).decode('utf-8') + existing_screens = subprocess.check_output(["screen", "-list"]).decode( + "utf-8" + ) if screen_id in existing_screens: print(f"⚠️ Lo script con ID {screen_id} Γ¨ giΓ  in esecuzione.") - self.bot.send_message(message.chat.id, f"⚠️ Lo script con ID {screen_id} Γ¨ giΓ  in esecuzione.") + self.bot.send_message( + message.chat.id, + f"⚠️ Lo script con ID {screen_id} Γ¨ giΓ  in esecuzione.", + ) return except subprocess.CalledProcessError: pass # Se il comando fallisce, significa che non ci sono screen attivi. # Crea la sessione screen e avvia lo script al suo interno - command = ["screen", "-dmS", screen_id, "python3", "../../test_run.py", screen_id] + command = [ + "screen", + "-dmS", + screen_id, + "python3", + "../../test_run.py", + screen_id, + ] # Avvia il comando tramite subprocess subprocess.Popen(command) @@ -229,7 +233,7 @@ class TelegramBot: "screen_id": screen_id, "start_time": time.time(), "status": "running", - "user_id": message.from_user.id + "user_id": message.from_user.id, } # Salvataggio nel file JSON @@ -237,7 +241,7 @@ class TelegramBot: # Carica i dati esistenti o crea una nuova lista try: - with open(json_file, 'r') as f: + with open(json_file, "r") as f: scripts_data = json.load(f) except (FileNotFoundError, json.JSONDecodeError): scripts_data = [] @@ -246,65 +250,61 @@ class TelegramBot: scripts_data.append(script_info) # Scrivi il file aggiornato - with open(json_file, 'w') as f: + with open(json_file, "w") as f: json.dump(scripts_data, f, indent=4) def handle_list_scripts(self, message): - if not self.is_authorized(message.from_user.id): - print(f"❌ Non sei autorizzato.") - self.bot.send_message(message.chat.id, "❌ Non sei autorizzato.") - return + if not self.is_authorized(message.from_user.id): + print(f"❌ Non sei autorizzato.") + self.bot.send_message(message.chat.id, "❌ Non sei autorizzato.") + return - try: - with open("../../scripts.json", "r") as f: - scripts_data = json.load(f) - except (FileNotFoundError, json.JSONDecodeError): - scripts_data = [] + try: + with open("../../scripts.json", "r") as f: + scripts_data = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + scripts_data = [] - if not scripts_data: - print(f"⚠️ Nessuno script registrato.") - self.bot.send_message(message.chat.id, "⚠️ Nessuno script registrato.") - return + if not scripts_data: + print(f"⚠️ Nessuno script registrato.") + self.bot.send_message(message.chat.id, "⚠️ Nessuno script registrato.") + return - current_time = time.time() - msg = ["πŸ–₯️ **Script Registrati:**\n"] + current_time = time.time() + msg = ["πŸ–₯️ **Script Registrati:**\n"] - for script in scripts_data: - # Calcola la durata - duration = current_time - script["start_time"] - if "end_time" in script: - duration = script["end_time"] - script["start_time"] + for script in scripts_data: + # Calcola la durata + duration = current_time - script["start_time"] + if "end_time" in script: + duration = script["end_time"] - script["start_time"] - # Formatta la durata - hours, rem = divmod(duration, 3600) - minutes, seconds = divmod(rem, 60) - duration_str = f"{int(hours)}h {int(minutes)}m {int(seconds)}s" + # Formatta la durata + hours, rem = divmod(duration, 3600) + minutes, seconds = divmod(rem, 60) + duration_str = f"{int(hours)}h {int(minutes)}m {int(seconds)}s" - # Icona stato - status_icons = { - "running": "🟒", - "stopped": "πŸ”΄", - "completed": "βšͺ" - } + # Icona stato + status_icons = {"running": "🟒", "stopped": "πŸ”΄", "completed": "βšͺ"} - # Costruisci riga - line = ( - f"β€’ ID: `{script['screen_id']}`\n" - f"β€’ Stato: {status_icons.get(script['status'], '⚫')}\n" - f"β€’ Stop: `/stop {script['screen_id']}`\n" - f"β€’ Screen: `/screen {script['screen_id']}`\n" - f"β€’ Durata: {duration_str}\n" - f"β€’ Download:\n{script.get('titolo', 'N/A')}\n" - ) - msg.append(line) + # Costruisci riga + line = ( + f"β€’ ID: `{script['screen_id']}`\n" + f"β€’ Stato: {status_icons.get(script['status'], '⚫')}\n" + f"β€’ Stop: `/stop {script['screen_id']}`\n" + f"β€’ Screen: `/screen {script['screen_id']}`\n" + f"β€’ Durata: {duration_str}\n" + f"β€’ Download:\n{script.get('titolo', 'N/A')}\n" + ) + msg.append(line) - # Formatta la risposta finale - final_msg = "\n".join(msg) - if len(final_msg) > 4000: - final_msg = final_msg[:4000] + "\n[...] (messaggio troncato)" + # Formatta la risposta finale + final_msg = "\n".join(msg) + if len(final_msg) > 4000: + final_msg = final_msg[:4000] + "\n[...] (messaggio troncato)" - print(f"{final_msg}") - self.bot.send_message(message.chat.id, final_msg, parse_mode="Markdown") + print(f"{final_msg}") + self.bot.send_message(message.chat.id, final_msg, parse_mode="Markdown") def handle_stop_script(self, message): if not self.is_authorized(message.from_user.id): @@ -324,7 +324,9 @@ class TelegramBot: if not running_scripts: print(f"⚠️ Nessuno script attivo da fermare.") - self.bot.send_message(message.chat.id, "⚠️ Nessuno script attivo da fermare.") + self.bot.send_message( + message.chat.id, "⚠️ Nessuno script attivo da fermare." + ) return msg = "πŸ–₯️ **Script Attivi:**\n" @@ -344,12 +346,18 @@ class TelegramBot: scripts_data = [] # Filtra la lista eliminando lo script con l'ID specificato - new_scripts_data = [script for script in scripts_data if script["screen_id"] != screen_id] + new_scripts_data = [ + script for script in scripts_data if script["screen_id"] != screen_id + ] if len(new_scripts_data) == len(scripts_data): # Nessun elemento rimosso, quindi ID non trovato print(f"⚠️ Nessuno script attivo con ID `{screen_id}`.") - self.bot.send_message(message.chat.id, f"⚠️ Nessuno script attivo con ID `{screen_id}`.", parse_mode="Markdown") + self.bot.send_message( + message.chat.id, + f"⚠️ Nessuno script attivo con ID `{screen_id}`.", + parse_mode="Markdown", + ) return # Terminare la sessione screen @@ -357,8 +365,14 @@ class TelegramBot: subprocess.check_output(["screen", "-S", screen_id, "-X", "quit"]) print(f"βœ… La sessione screen con ID {screen_id} Γ¨ stata fermata.") except subprocess.CalledProcessError: - print(f"⚠️ Impossibile fermare la sessione screen con ID `{screen_id}`.") - self.bot.send_message(message.chat.id, f"⚠️ Impossibile fermare la sessione screen con ID `{screen_id}`.", parse_mode="Markdown") + print( + f"⚠️ Impossibile fermare la sessione screen con ID `{screen_id}`." + ) + self.bot.send_message( + message.chat.id, + f"⚠️ Impossibile fermare la sessione screen con ID `{screen_id}`.", + parse_mode="Markdown", + ) return # Salva la lista aggiornata senza lo script eliminato @@ -366,22 +380,13 @@ class TelegramBot: json.dump(new_scripts_data, f, indent=4) print(f"βœ… Script `{screen_id}` terminato con successo!") - self.bot.send_message(message.chat.id, f"βœ… Script `{screen_id}` terminato con successo!", parse_mode="Markdown") + self.bot.send_message( + message.chat.id, + f"βœ… Script `{screen_id}` terminato con successo!", + parse_mode="Markdown", + ) def handle_response(self, message): - """ if message.text == 'Start': - self.handle_start_script(self, message) - elif message.text == 'Lista': - self.handle_list_scripts(self, message) - elif message.text == 'Azione 3': - self.bot.send_message(message.chat.id, "Hai scelto Azione 3!") - else: - self.bot.send_message(message.chat.id, "Comando non riconosciuto.") - - if not self.is_authorized(message.from_user.id): - self.bot.reply_to(message, "❌ Non sei autorizzato.") - return """ - text = message.text if self.request_manager.save_response(text): print(f"πŸ“₯ Risposta salvata correttamente per il tipo {text}") @@ -393,7 +398,9 @@ class TelegramBot: command_parts = message.text.split() if len(command_parts) < 2: print(f"⚠️ ID mancante nel comando. Usa: /screen ") - self.bot.send_message(message.chat.id, "⚠️ ID mancante nel comando. Usa: /screen ") + self.bot.send_message( + message.chat.id, "⚠️ ID mancante nel comando. Usa: /screen " + ) return screen_id = command_parts[1] @@ -401,35 +408,55 @@ class TelegramBot: try: # Cattura l'output della screen - subprocess.run(["screen", "-X", "-S", screen_id, "hardcopy", "-h", temp_file], check=True) + subprocess.run( + ["screen", "-X", "-S", screen_id, "hardcopy", "-h", temp_file], + check=True, + ) except subprocess.CalledProcessError as e: print(f"❌ Errore durante la cattura dell'output della screen: {e}") - self.bot.send_message(message.chat.id, f"❌ Errore durante la cattura dell'output della screen: {e}") + self.bot.send_message( + message.chat.id, + f"❌ Errore durante la cattura dell'output della screen: {e}", + ) return if not os.path.exists(temp_file): print(f"❌ Impossibile catturare l'output della screen.") - self.bot.send_message(message.chat.id, f"❌ Impossibile catturare l'output della screen.") + self.bot.send_message( + message.chat.id, f"❌ Impossibile catturare l'output della screen." + ) return try: # Leggi il file con la codifica corretta - with open(temp_file, 'r', encoding='latin-1') as file: + with open(temp_file, "r", encoding="latin-1") as file: screen_output = file.read() # Pulisci l'output - cleaned_output = re.sub(r'[\x00-\x1F\x7F]', '', screen_output) # Rimuovi caratteri di controllo - cleaned_output = cleaned_output.replace('\n\n', '\n') # Rimuovi newline multipli + cleaned_output = re.sub( + r"[\x00-\x1F\x7F]", "", screen_output + ) # Rimuovi caratteri di controllo + cleaned_output = cleaned_output.replace( + "\n\n", "\n" + ) # Rimuovi newline multipli # Estrarre tutte le parti da "Download:" fino a "Video" o "Subtitle", senza includerli - download_matches = re.findall(r"Download: (.*?)(?:Video|Subtitle)", cleaned_output) + download_matches = re.findall( + r"Download: (.*?)(?:Video|Subtitle)", cleaned_output + ) if download_matches: # Serie TV e Film StreamingCommunity proc_matches = re.findall(r"Proc: ([\d\.]+%)", cleaned_output) # Creare una stringa unica con tutti i risultati - result_string = "\n".join([f"Download: {download_matches[i].strip()}\nDownload al {proc_matches[i]}" for i in range(len(download_matches)) if i < len(proc_matches)]) + result_string = "\n".join( + [ + f"Download: {download_matches[i].strip()}\nDownload al {proc_matches[i]}" + for i in range(len(download_matches)) + if i < len(proc_matches) + ] + ) if result_string != "": cleaned_output = result_string @@ -461,11 +488,18 @@ class TelegramBot: # Invia l'output pulito print(f"πŸ“„ Output della screen {screen_id}:\n{cleaned_output}") - self._send_long_message(message.chat.id, f"πŸ“„ Output della screen {screen_id}:\n{cleaned_output}") + self._send_long_message( + message.chat.id, f"πŸ“„ Output della screen {screen_id}:\n{cleaned_output}" + ) except Exception as e: - print(f"❌ Errore durante la lettura o l'invio dell'output della screen: {e}") - self.bot.send_message(message.chat.id, f"❌ Errore durante la lettura o l'invio dell'output della screen: {e}") + print( + f"❌ Errore durante la lettura o l'invio dell'output della screen: {e}" + ) + self.bot.send_message( + message.chat.id, + f"❌ Errore durante la lettura o l'invio dell'output della screen: {e}", + ) # Cancella il file temporaneo os.remove(temp_file) @@ -486,7 +520,7 @@ class TelegramBot: """Suddivide e invia un messaggio troppo lungo in piΓΉ parti.""" for i in range(0, len(text), chunk_size): print(f"{text[i:i+chunk_size]}") - self.bot.send_message(chat_id, text[i:i+chunk_size]) + self.bot.send_message(chat_id, text[i : i + chunk_size]) def ask(self, type, prompt_message, choices, timeout=60): self.request_manager.create_request(type) @@ -518,30 +552,10 @@ class TelegramBot: def run(self): print("πŸš€ Avvio del bot...") - # svuoto il file ../../scripts.json with open("../../scripts.json", "w") as f: json.dump([], f) self.bot.infinity_polling() + def get_bot_instance(): - return TelegramBot.get_instance() - -# Esempio di utilizzo -if __name__ == "__main__": - - # Usa le variabili - token = os.getenv("TOKEN_TELEGRAM") - authorized_user_id = os.getenv("AUTHORIZED_USER_ID") - - TOKEN = token # Inserisci il token del tuo bot Telegram sul file .env - AUTHORIZED_USER_ID = int(authorized_user_id) # Inserisci il tuo ID utente Telegram sul file .env - - # Inizializza il bot - bot = TelegramBot.init_bot(TOKEN, AUTHORIZED_USER_ID) - bot.run() - -""" -start - Avvia lo script -list - Lista script attivi -get - Mostra ID utente Telegram -""" + return TelegramBot.get_instance() \ No newline at end of file diff --git a/StreamingCommunity/Upload/update.py b/StreamingCommunity/Upload/update.py index 7dd7f22..d6617a9 100644 --- a/StreamingCommunity/Upload/update.py +++ b/StreamingCommunity/Upload/update.py @@ -25,8 +25,7 @@ def update(): """ Check for updates on GitHub and display relevant information. """ - - console.print("[green]Checking GitHub version [white]...") + console.print("\n[cyan]β†’ [green]Checking GitHub version ...") # Make the GitHub API requests and handle potential errors try: @@ -59,7 +58,7 @@ def update(): if str(__version__).replace('v', '') != str(last_version).replace('v', '') : console.print(f"[red]New version available: [yellow]{last_version} \n") else: - console.print(f" [yellow]Everything is up to date \n") + console.print(f" [red]Everything is up to date \n") console.print(f"[red]{__title__} has been downloaded [yellow]{total_download_count} [red]times, but only [yellow]{percentual_stars}% [red]of users have starred it.\n\ [cyan]Help the repository grow today by leaving a [yellow]star [cyan]and [yellow]sharing [cyan]it with others online!") diff --git a/StreamingCommunity/Upload/version.py b/StreamingCommunity/Upload/version.py index 6e81304..36ad52c 100644 --- a/StreamingCommunity/Upload/version.py +++ b/StreamingCommunity/Upload/version.py @@ -1,5 +1,5 @@ __title__ = 'StreamingCommunity' __version__ = '2.5.0' -__author__ = 'Lovi-0' +__author__ = 'Abc-dc' __description__ = 'A command-line program to download film' __copyright__ = 'Copyright 2024' diff --git a/StreamingCommunity/Util/_jsonConfig.py b/StreamingCommunity/Util/_jsonConfig.py index 653380b..79716c8 100644 --- a/StreamingCommunity/Util/_jsonConfig.py +++ b/StreamingCommunity/Util/_jsonConfig.py @@ -34,7 +34,7 @@ class ConfigManager: else: logging.info("Configuration file does not exist. Downloading...") self.download_requirements( - 'https://raw.githubusercontent.com/Lovi-0/StreamingCommunity/refs/heads/main/config.json', + 'https://raw.githubusercontent.com/Abc-dc/StreamingCommunity/refs/heads/main/config.json', self.file_path ) diff --git a/StreamingCommunity/Util/message.py b/StreamingCommunity/Util/message.py index ad541ad..804541c 100644 --- a/StreamingCommunity/Util/message.py +++ b/StreamingCommunity/Util/message.py @@ -7,58 +7,30 @@ import platform from StreamingCommunity.Util.console import console from StreamingCommunity.Util._jsonConfig import config_manager + # Variable CLEAN = config_manager.get_bool('DEFAULT', 'clean_console') SHOW = config_manager.get_bool('DEFAULT', 'show_message') -def create_italian_flag_colored_text(text: str) -> str: - """Create text divided into three sections with Italian flag colors, splitting each line at two spaces.""" - - # Split the text into lines - lines = text.splitlines() - - colored_lines = [] - - for line in lines: - # Split each line into parts using two spaces as a delimiter - parts = line.split(" ") - - # Ensure there are exactly 3 parts (add empty strings if necessary) - parts += [''] * (4 - len(parts)) - - # Apply flag colors to the parts - green_part = f"[green]{parts[0]}[/]" - white_part = f"[white]{parts[1]}[/]" - red_part = f"[red]{parts[2]}[/]" - - # Reassemble the colored line - colored_line = green_part + white_part + red_part - colored_lines.append(colored_line) - - # Join all colored lines back into a single string - return "\n".join(colored_lines) - def start_message(): """Display a stylized start message in the console.""" msg = r''' - β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— - β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β• - β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ•— - β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ•β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ - β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• - β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•šβ•β• β•šβ•β•β•β• β•šβ•β•β•β•β•β• + __ __ _ _____ __ _ + / //_/(_)________ ____ _ __ / ___// /_________ ____ _____ ___ (_)___ ____ _ + / ,< / / ___/ __ \/ __ \ | |/_/ \__ \/ __/ ___/ _ \/ __ `/ __ `__ \/ / __ \/ __ `/ + / /| |/ / / / /_/ / / / / _> < ___/ / /_/ / / __/ /_/ / / / / / / / / / / /_/ / +/_/ |_/_/_/ \____/_/ /_/ /_/|_| /____/\__/_/ \___/\__,_/_/ /_/ /_/_/_/ /_/\__, / + /____/ '''.rstrip() - colored_msg = create_italian_flag_colored_text(msg) - if CLEAN: os.system("cls" if platform.system() == 'Windows' else "clear") if SHOW: - console.print(colored_msg) + console.print(f"[purple]{msg}") # Print a decorative separator line using asterisks separator = "_" * (console.width - 2) # Ridotto di 2 per il padding - console.print(f"[yellow]{separator}[/yellow]\n") + console.print(f"[cyan]{separator}[/cyan]\n") diff --git a/StreamingCommunity/Util/os.py b/StreamingCommunity/Util/os.py index b8166d2..c849fbf 100644 --- a/StreamingCommunity/Util/os.py +++ b/StreamingCommunity/Util/os.py @@ -411,7 +411,6 @@ class OsSummary: def get_system_summary(self): self.check_python_version() InternManager().check_internet() - console.print("[bold blue]System Summary[/bold blue][white]:") # Python info python_version = sys.version.split()[0] @@ -477,7 +476,7 @@ class OsSummary: if not os.path.exists(requirements_file): self.download_requirements( - 'https://raw.githubusercontent.com/Lovi-0/StreamingCommunity/refs/heads/main/requirements.txt', + 'https://raw.githubusercontent.com/Abc-dc/StreamingCommunity/refs/heads/main/requirements.txt', requirements_file ) @@ -492,7 +491,7 @@ class OsSummary: else: logging.info(f"Library: {installed_version}") - console.print(f"[cyan]Libraries[white]: [bold red]{', '.join([self.get_library_version(lib) for lib in optional_libraries])}[/bold red]\n") + #console.print(f"[cyan]Libraries[white]: [bold red]{', '.join([self.get_library_version(lib) for lib in optional_libraries])}[/bold red]\n") logging.info(f"Libraries: {', '.join([self.get_library_version(lib) for lib in optional_libraries])}") diff --git a/StreamingCommunity/Util/table.py b/StreamingCommunity/Util/table.py index 67acb5c..a7ed84b 100644 --- a/StreamingCommunity/Util/table.py +++ b/StreamingCommunity/Util/table.py @@ -19,7 +19,7 @@ from .message import start_message from .call_stack import get_call_stack # Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance from StreamingCommunity.Util._jsonConfig import config_manager TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') diff --git a/StreamingCommunity/run.py b/StreamingCommunity/run.py index 4339452..ee7e71d 100644 --- a/StreamingCommunity/run.py +++ b/StreamingCommunity/run.py @@ -20,13 +20,16 @@ from StreamingCommunity.Upload.update import update as git_update from StreamingCommunity.Util.os import os_summary from StreamingCommunity.Util.logger import Logger -# Telegram bot instance -from StreamingCommunity.HelpTg.telegram_bot import get_bot_instance + +# Telegram util +from StreamingCommunity.TelegramHelp.session import get_session, deleteScriptId +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance + # Config CLOSE_CONSOLE = config_manager.get_bool('DEFAULT', 'not_close') TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') -from StreamingCommunity.HelpTg.session import get_session, deleteScriptId + def run_function(func: Callable[..., None], close_console: bool = False) -> None: @@ -136,11 +139,13 @@ def initialize(): except: console.log("[red]Error with loading github.") + def restart_script(): """Riavvia lo script con gli stessi argomenti della riga di comando.""" print("\nπŸ”„ Riavvio dello script...\n") python = sys.executable - os.execv(python, [python] + sys.argv) # Riavvia lo stesso script con gli stessi argomenti + os.execv(python, [python] + sys.argv) + def force_exit(): """Forza la chiusura dello script in qualsiasi contesto.""" @@ -160,23 +165,24 @@ def force_exit(): print("⚑ Arresto del loop asyncio...") loop.stop() except RuntimeError: - pass # Se non c'Γ¨ un loop asyncio attivo, ignora l'errore + pass # 3️⃣ Esce con sys.exit(), se fallisce usa os._exit() try: print("βœ… Uscita con sys.exit(0)") sys.exit(0) except SystemExit: - pass # Se viene intercettato da un try/except, ignora + pass print("🚨 Uscita forzata con os._exit(0)") - os._exit(0) # Se sys.exit() non funziona, esce forzatamente + os._exit(0) + def main(script_id): if TELEGRAM_BOT: - bot = get_bot_instance() - bot.send_message(f"🏁 Avviato script {script_id}", None) + bot = get_bot_instance() + bot.send_message(f"🏁 Avviato script {script_id}", None) start = time.time() @@ -291,41 +297,36 @@ def main(script_id): if TELEGRAM_BOT: - # Mappa delle emoji per i colori - emoji_map = { - "yellow": "🟑", # Giallo - "red": "πŸ”΄", # Rosso - "blue": "πŸ”΅", # Blu - "green": "🟒" # Verde - } + # Mappa delle emoji per i colori + emoji_map = { + "yellow": "🟑", # Giallo + "red": "πŸ”΄", # Rosso + "blue": "πŸ”΅", # Blu + "green": "🟒" # Verde + } - # Display the category legend in a single line - category_legend_str = "Categorie: \n" + " | ".join([ - f"{emoji_map.get(color, 'βšͺ')} {category.capitalize()}" - for category, color in color_map.items() - ]) + # Display the category legend in a single line + category_legend_str = "Categorie: \n" + " | ".join([ + f"{emoji_map.get(color, 'βšͺ')} {category.capitalize()}" + for category, color in color_map.items() + ]) - # Costruisci il messaggio con le emoji al posto dei colori - prompt_message = "Inserisci il sito:\n" + "\n".join( - [f"{key}: {emoji_map[color_map[label[1]]]} {label[0]}" for key, label in choice_labels.items()] - ) + # Costruisci il messaggio con le emoji al posto dei colori + prompt_message = "Inserisci il sito:\n" + "\n".join( + [f"{key}: {emoji_map[color_map[label[1]]]} {label[0]}" for key, label in choice_labels.items()] + ) - console.print(f"\n{prompt_message}") + console.print(f"\n{prompt_message}") - # Chiedi la scelta all'utente con il bot Telegram - category = bot.ask( - "select_provider", - f"{category_legend_str}\n\n{prompt_message}", - None # Passiamo la lista delle chiavi come scelte - ) + # Chiedi la scelta all'utente con il bot Telegram + category = bot.ask( + "select_provider", + f"{category_legend_str}\n\n{prompt_message}", + None # Passiamo la lista delle chiavi come scelte + ) else: - - #console.print(f"\n{prompt_message}") - - # Ask the user for input - category = msg.ask(prompt_message, choices=list(choice_labels.keys()), default="0", show_choices=False, show_default=False) - + category = msg.ask(prompt_message, choices=list(choice_labels.keys()), default="0", show_choices=False, show_default=False) # Run the corresponding function based on user input if category in input_to_function: @@ -338,18 +339,14 @@ def main(script_id): console.print("[red]Invalid category.") if CLOSE_CONSOLE: - restart_script() # Riavvia lo script invece di uscire - # Riavvia lo script - # Chiude il processo attuale e avvia una nuova istanza dello script - """ subprocess.Popen([sys.executable] + sys.argv) - sys.exit() """ + restart_script() # Riavvia lo script invece di uscire else: - force_exit() # Usa la funzione per chiudere sempre + force_exit() # Usa la funzione per chiudere sempre - if TELEGRAM_BOT: - bot.send_message(f"Chiusura in corso", None) - # Delete script_id - script_id = get_session() - if script_id != "unknown": - deleteScriptId(script_id) - \ No newline at end of file + if TELEGRAM_BOT: + bot.send_message(f"Chiusura in corso", None) + + # Delete script_id + script_id = get_session() + if script_id != "unknown": + deleteScriptId(script_id) diff --git a/Test/Download/HLS.py b/Test/Download/HLS.py index 2fc1598..ec35166 100644 --- a/Test/Download/HLS.py +++ b/Test/Download/HLS.py @@ -20,6 +20,6 @@ from StreamingCommunity.Lib.Downloader import HLS_Downloader start_message() logger = Logger() print("Return: ", HLS_Downloader( - output_filename="test.mp4", - m3u8_playlist="https://acdn.ak-stream-videoplatform.sky.it/hls/2024/11/21/968275/master.m3u8" + output_path="test.mp4", + m3u8_url="https://acdn.ak-stream-videoplatform.sky.it/hls/2024/11/21/968275/master.m3u8" ).start()) \ No newline at end of file diff --git a/Test/Util/ffmpegVersion.py b/Test/Util/ffmpegVersion.py new file mode 100644 index 0000000..1fce934 --- /dev/null +++ b/Test/Util/ffmpegVersion.py @@ -0,0 +1,29 @@ +# 05.02.25 + +# Fix import +import sys +import os +src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +sys.path.append(src_path) + + + +from StreamingCommunity.Util.ffmpeg_installer import FFMPEGDownloader + + +def test_ffmpeg_downloader(): + + # Create an instance of the downloader + downloader = FFMPEGDownloader() + + # Check if the download method works and fetches the executables + ffmpeg, ffprobe, ffplay = downloader.download() + + # Output the destination paths + print(f"FFmpeg path: {ffmpeg}") + print(f"FFprobe path: {ffprobe}") + print(f"FFplay path: {ffplay}") + + +if __name__ == "__main__": + test_ffmpeg_downloader() \ No newline at end of file diff --git a/Test/Util/oss.py b/Test/Util/osPath.py similarity index 100% rename from Test/Util/oss.py rename to Test/Util/osPath.py diff --git a/config.json b/config.json index e547b1f..5b555ba 100644 --- a/config.json +++ b/config.json @@ -3,15 +3,15 @@ "debug": false, "log_file": "app.log", "log_to_file": false, - "show_message": false, + "show_message": true, "clean_console": true, - "root_path": "/home/giuseppepiccolo/Develop/docker/jellyfin/media/", + "root_path": "Video", "movie_folder_name": "Movie", "serie_folder_name": "Serie", "anime_folder_name": "Anime", - "map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)", + "map_episode_name": "S%(season)E%(episode)_%(episode_name)", "config_qbit_tor": { - "host": "192.168.5.172", + "host": "192.168.5.99", "port": "8080", "user": "admin", "pass": "adminadmin" @@ -19,10 +19,10 @@ "add_siteName": false, "disable_searchDomain": false, "not_close": false, - "telegram_bot": true + "telegram_bot": false }, "REQUESTS": { - "timeout": 30, + "timeout": 20, "max_retry": 8, "proxy_start_min": 0.1, "proxy_start_max": 0.5 @@ -35,7 +35,7 @@ "specific_list_audio": [ "ita" ], - "merge_subs": false, + "merge_subs": true, "specific_list_subtitles": [ "eng", "spa" @@ -79,7 +79,7 @@ "domain": "so" }, "cb01new": { - "domain": "mobi" + "domain": "media" }, "1337xx": { "domain": "to" diff --git a/requirements.txt b/requirements.txt index 21212e1..fbf4453 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,6 @@ pycryptodomex googlesearch-python fake-useragent qbittorrent-api -python-qbittorrent -pyTelegramBotAPI -Pillow \ No newline at end of file +python-qbittorrent +Pillow +pyTelegramBotAPI \ No newline at end of file diff --git a/test_run.py b/test_run.py index 0ca8a16..abe28dc 100644 --- a/test_run.py +++ b/test_run.py @@ -2,14 +2,20 @@ import sys from StreamingCommunity.run import main -from StreamingCommunity.HelpTg.request_manager import RequestManager -from StreamingCommunity.HelpTg.session import set_session +from StreamingCommunity.Util._jsonConfig import config_manager +from StreamingCommunity.TelegramHelp.request_manager import RequestManager +from StreamingCommunity.TelegramHelp.session import set_session # Svuoto il file -request_manager = RequestManager() -request_manager.clear_file() -script_id = sys.argv[1] if len(sys.argv) > 1 else "unknown" +TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') -set_session(script_id) +if TELEGRAM_BOT: + request_manager = RequestManager() + request_manager.clear_file() + script_id = sys.argv[1] if len(sys.argv) > 1 else "unknown" -main(script_id) \ No newline at end of file + set_session(script_id) + main(script_id) + +else: + main(0) \ No newline at end of file