diff --git a/README.md b/README.md index d493b3a..d604d1a 100644 --- a/README.md +++ b/README.md @@ -69,50 +69,6 @@ python3 update.py You can change some behaviors by tweaking the configuration file. -```json -{ - "DEFAULT": { - "debug": false, - "get_info": false, - "show_message": true, - "clean_console": true, - "get_moment_title": false, - "root_path": "videos", - "movies_folder_name": "Movies", - "series_folder_name": "Series", - "anime_folder_name": "Anime", - "not_close": false, - "swith_anime": false - }, - "SITE": { - "streaming_site_name": "streamingcommunity", - "streaming_domain": "forum", - "anime_site_name": "animeunity", - "anime_domain": "to" - }, - "M3U8": { - "tdqm_workers": 20, - "tqdm_progress_timeout": 10, - "minimum_ts_files_in_folder": 15, - "download_percentage": 1, - "requests_timeout": 5, - "enable_time_quit": false, - "tqdm_show_progress": false, - "cleanup_tmp_folder": true - }, - "M3U8_OPTIONS": { - "download_audio": true, - "download_subtitles": true, - "specific_list_audio": [ - "ita" - ], - "specific_list_subtitles": [ - "eng" - ], - "map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)", - } -} -``` #### Options @@ -140,6 +96,8 @@ You can change some behaviors by tweaking the configuration file. | minimum_ts_files_in_folder | 15 | The minimum number of .ts files expected in a folder. | 10 | | download_percentage | 1 | The percentage of download completion required to consider the download complete. | 0.95 | | requests_timeout | 5 | The timeout duration for HTTP requests in seconds. | 10 | +| use_openssl | false | Indicates whether OpenSSL should be utilized for encryption during the conversion of TS files with key and IV. | true | +| use_codecs | false | Specifies whether specific codecs (e.g., h264 for video, AAC for audio) should be used for converting TS files to MP4. | true | | enable_time_quit | false | Whether to enable quitting the download after a certain time period. | true | | tqdm_show_progress | false | Whether to show progress during downloads or not.**May slow down your PC** | true | | cleanup_tmp_folder | true | Whether to clean up temporary folders after processing or not. | false | @@ -159,13 +117,13 @@ You can change some behaviors by tweaking the configuration file. * Windows: `C:\\MyLibrary\\Folder` or `\\\\MyServer\\MyLibrary` (if you want to use a network folder). * Linux/MacOS: `Desktop/MyLibrary/Folder` -## Tutorial -For a detailed walkthrough, refer to the [video tutorial](https://www.youtube.com/watch?v=Ok7hQCgxqLg&ab_channel=Nothing) - #### 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 ->NOTE: You don't need to add .mp4 at the end \ No newline at end of file +>NOTE: You don't need to add .mp4 at the end + +## Tutorial +For a detailed walkthrough, refer to the [video tutorial](https://www.youtube.com/watch?v=Ok7hQCgxqLg&ab_channel=Nothing) diff --git a/Src/Lib/FFmpeg/util/helper.py b/Src/Lib/FFmpeg/util/helper.py index 40ec688..060945e 100644 --- a/Src/Lib/FFmpeg/util/helper.py +++ b/Src/Lib/FFmpeg/util/helper.py @@ -19,7 +19,7 @@ from Src.Util.config import config_manager # Variable DEBUG_MODE = config_manager.get_bool("DEFAULT", "debug") DEBUG_FFMPEG = "debug" if DEBUG_MODE else "error" -USE_CODECS = False +USE_CODECS = config_manager.get_bool("M3U8", "use_codecs") diff --git a/Src/Lib/FFmpeg/util/installer.py b/Src/Lib/FFmpeg/util/installer.py index 7df70df..bcb7002 100644 --- a/Src/Lib/FFmpeg/util/installer.py +++ b/Src/Lib/FFmpeg/util/installer.py @@ -146,17 +146,20 @@ def move_ffmpeg_exe_to_top_level(install_dir: str) -> None: raise def add_install_dir_to_environment_path(install_dir: str) -> None: - ''' + """ Add the install directory to the environment PATH variable. Args: install_dir (str): The directory to be added to the environment PATH variable. - ''' + """ install_dir = os.path.abspath(os.path.join(install_dir, 'bin')) set_env_path(install_dir) def download_ffmpeg(): + """ + Main function to donwload ffmpeg and add to win path + """ # Get FFMPEG download URL ffmpeg_url = get_ffmpeg_download_url() @@ -189,13 +192,16 @@ def download_ffmpeg(): logging.info(f'Adding {install_dir} to environment PATH variable') add_install_dir_to_environment_path(install_dir) -def check_ffmpeg(): +def check_ffmpeg() -> bool: """ Check if FFmpeg is installed and available on the system PATH. This function checks if FFmpeg is installed and available on the system PATH. If FFmpeg is found, it prints its version. If not found, it attempts to download FFmpeg and add it to the system PATH. + + Returns: + bool: If ffmpeg is present or not """ console.print("[green]Checking FFmpeg...") @@ -209,6 +215,8 @@ def check_ffmpeg(): if show_version: get_version() + return True + except: try: @@ -225,3 +233,5 @@ def check_ffmpeg(): console.print("[red]Unable to download or add FFmpeg to the PATH.[/red]") console.print(f"Error: {e}") raise + + return False \ No newline at end of file diff --git a/Src/Util/_tmpConfig.py b/Src/Util/_tmpConfig.py new file mode 100644 index 0000000..d860714 --- /dev/null +++ b/Src/Util/_tmpConfig.py @@ -0,0 +1,188 @@ +# 11.04.24 + + +import os +import tempfile +import configparser +import logging +import json +from typing import Union, List + + +# Costant +repo_name = "StreamingCommunity_api" +config_file_name = f"{repo_name}_config.ini" + + +class ConfigError(Exception): + """ + Exception raised for errors related to configuration management. + """ + def __init__(self, message: str): + """ + Initialize ConfigError with the given error message. + + Args: + message (str): The error message. + """ + self.message = message + super().__init__(self.message) + logging.error(self.message) + + +class ConfigManager: + """ + Class to manage configuration settings using a config file. + """ + def __init__(self, config_file_name: str = config_file_name, defaults: dict = None): + """ + Initialize ConfigManager. + + Args: + config_file_name (str, optional): The name of the configuration file. Default is 'config.ini'. + defaults (dict, optional): A dictionary containing default values for variables. Default is None. + """ + self.config_file_path = os.path.join(tempfile.gettempdir(), config_file_name) + self.config = configparser.ConfigParser() + self.logger = logging.getLogger(__name__) + + # Create config file if it doesn't exist + if not os.path.exists(self.config_file_path): + with open(self.config_file_path, 'w') as config_file: + if defaults: + for section, options in defaults.items(): + if not self.config.has_section(section): + self.config.add_section(section) + for key, value in options.items(): + self.config.set(section, key, str(value)) + self.config.write(config_file) + + # Read existing config + self.config.read(self.config_file_path) + + def _check_section_and_key(self, section: str, key: str) -> None: + """ + Check if the given section and key exist in the configuration file. + + Args: + section (str): The section in the config file. + key (str): The key of the variable. + + Raises: + ConfigError: If the section or key does not exist. + """ + if not self.config.has_section(section): + raise ConfigError(f"Section '{section}' does not exist in the configuration file.") + if not self.config.has_option(section, key): + raise ConfigError(f"Key '{key}' does not exist in section '{section}'.") + + def get_int(self, section: str, key: str, default: Union[int, None] = None) -> Union[int, None]: + """ + Get the value of a variable from the config file as an integer. + + Args: + section (str): The section in the config file. + key (str): The key of the variable. + default (int, optional): Default value if the variable doesn't exist. Default is None. + + Returns: + int or None: Value of the variable as an integer or default value. + """ + try: + self._check_section_and_key(section, key) + return int(self.config.get(section, key)) + except (ConfigError, ValueError): + return default + + def get_string(self, section: str, key: str, default: Union[str, None] = None) -> Union[str, None]: + """ + Get the value of a variable from the config file as a string. + + Args: + section (str): The section in the config file. + key (str): The key of the variable. + default (str, optional): Default value if the variable doesn't exist. Default is None. + + Returns: + str or None: Value of the variable as a string or default value. + """ + try: + self._check_section_and_key(section, key) + return self.config.get(section, key) + except ConfigError: + return default + + def get_bool(self, section: str, key: str, default: Union[bool, None] = None) -> Union[bool, None]: + """ + Get the value of a variable from the config file as a boolean. + + Args: + section (str): The section in the config file. + key (str): The key of the variable. + default (bool, optional): Default value if the variable doesn't exist. Default is None. + + Returns: + bool or None: Value of the variable as a boolean or default value. + """ + try: + self._check_section_and_key(section, key) + return self.config.getboolean(section, key) + except ConfigError: + return default + + def get_list(self, section: str, key: str, default: Union[List, None] = None) -> Union[List, None]: + """ + Get the value of a variable from the config file as a list. + + Args: + section (str): The section in the config file. + key (str): The key of the variable. + default (List, optional): Default value if the variable doesn't exist. Default is None. + + Returns: + List or None: Value of the variable as a list or default value. + """ + try: + self._check_section_and_key(section, key) + value = self.config.get(section, key) + return json.loads(value) + except (ConfigError, json.JSONDecodeError): + return default + + def add_variable(self, section: str, key: str, value: Union[int, str, bool, List]) -> None: + """ + Add or update a variable in the config file. + + Args: + section (str): The section in the config file. + key (str): The key of the variable. + value (int, str, bool, List): The value of the variable. + """ + if not self.config.has_section(section): + self.config.add_section(section) + self.config.set(section, key, str(value)) + with open(self.config_file_path, 'w') as config_file: + self.config.write(config_file) + self.logger.info(f"Added or updated variable '{key}' in section '{section}'") + + def reload_config(self) -> None: + """ + Reload the configuration from the file. + """ + self.config.clear() + self.config.read(self.config_file_path) + self.logger.info("Configuration file reloaded") + + +# Output +defaults = { + 'Requirements': { + 'ffmpeg': False + }, + 'Backup' : { + 'path': False + } +} + +temp_config_manager = ConfigManager(defaults=defaults) + diff --git a/Src/Util/_win32.py b/Src/Util/_win32.py index bf01902..f48d140 100644 --- a/Src/Util/_win32.py +++ b/Src/Util/_win32.py @@ -1,9 +1,5 @@ # 07.04.24 -# to do -# run somehwere backup -# add config to trace if ffmpeg is install, using config in local or temp - import platform import os import logging @@ -98,16 +94,23 @@ def backup_path(): original_path = get_env("Path") try: - script_dir = os.path.dirname(__file__) + + # Create backup dir + script_dir = os.path.join(os.path.expanduser("~"), "Backup") + os.makedirs(script_dir, exist_ok=True) + backup_file = os.path.join(script_dir, "path_backup.txt") - with open(backup_file, "w") as f: - for path in original_path.split("\n"): - if len(path) > 3: - f.write(f"{path}; \n") + # Check if backup file exist + if not os.path.exists(backup_file): - logging.info("Backup of PATH variable created.") - print("Backup of PATH variable created.") + with open(backup_file, "w") as f: + for path in original_path.split("\n"): + if len(path) > 3: + f.write(f"{path}; \n") + + logging.info("Backup of PATH variable created.") + print("Backup of PATH variable created.") except Exception as e: logging.error(f"Failed to create backup of PATH variable: {e}") diff --git a/Src/Util/mapper.py b/Src/Util/mapper.py index 1a7dd21..9066d7c 100644 --- a/Src/Util/mapper.py +++ b/Src/Util/mapper.py @@ -1,9 +1,17 @@ +# 10.04.24 + + +# Internal utilities from Src.Lib.Unidecode import transliterate from Src.Util.config import config_manager +from Src.Api.Class.EpisodeType import Episode + +# Config map_episode = config_manager.get('M3U8_OPTIONS', 'map_episode_name') -def map_episode_title(tv_name: str, episode, number_season: int): + +def map_episode_title(tv_name: str, episode: Episode, number_season: int): """ Maps the episode title to a specific format. @@ -15,9 +23,11 @@ def map_episode_title(tv_name: str, episode, number_season: int): Returns: str: The mapped episode title. """ + map_episode_temp = map_episode map_episode_temp = map_episode_temp.replace("%(tv_name)", tv_name) map_episode_temp = map_episode_temp.replace("%(season)", str(number_season).zfill(2)) map_episode_temp = map_episode_temp.replace("%(episode)", str(episode.number).zfill(2)) map_episode_temp = map_episode_temp.replace("%(episode_name)", episode.name) + return transliterate(map_episode_temp) \ No newline at end of file diff --git a/config.json b/config.json index 5830763..222049d 100644 --- a/config.json +++ b/config.json @@ -27,6 +27,7 @@ "download_percentage": 1, "requests_timeout": 5, "use_openssl": false, + "use_codecs": false, "enable_time_quit": false, "tqdm_show_progress": false, "cleanup_tmp_folder": true diff --git a/run.py b/run.py index 07074b1..6dfc76e 100644 --- a/run.py +++ b/run.py @@ -19,13 +19,15 @@ from Src.Api import ( from Src.Util.message import start_message from Src.Util.console import console, msg from Src.Util.config import config_manager +from Src.Util._tmpConfig import temp_config_manager +from Src.Util._win32 import backup_path from Src.Util.os import remove_folder, remove_file from Src.Upload.update import update as git_update from Src.Lib.FFmpeg import check_ffmpeg from Src.Util.logger import Logger -# Variable +# Config DEBUG_MODE = config_manager.get_bool("DEFAULT", "debug") DEBUG_GET_ALL_INFO = config_manager.get_bool('DEFAULT', 'get_info') SWITCH_TO = config_manager.get_bool('DEFAULT', 'swith_anime') @@ -40,6 +42,7 @@ def initialize(): # Get system where script is run run_system = platform.system() + # Enable debug with info if DEBUG_MODE: @@ -54,20 +57,38 @@ def initialize(): console.log("Install python version > 3.11") sys.exit(0) + # Removing temporary folder remove_folder("tmp") remove_file("debug.log") start_message() + # Attempting GitHub update try: git_update() except Exception as e: console.print(f"[blue]Req github [white]=> [red]Failed: {e}") + # Checking ffmpeg availability ( only win ) if run_system == 'Windows': - check_ffmpeg() + + # Check if backup of path exist + if not temp_config_manager.get_bool('Backup', 'path'): + + # Make backup of init path + backup_path() + temp_config_manager.add_variable('Backup', 'path', True) + + # Check if tmp config ffmpeg is present + if not temp_config_manager.get_bool('Requirements', 'ffmpeg'): + output_ffmpeg = check_ffmpeg() + + # If ffmpeg is present is win systems change config + if output_ffmpeg: + temp_config_manager.add_variable('Requirements', 'ffmpeg', True) + def main(): """ @@ -111,6 +132,7 @@ def main(): # End console.print("\n[red]Done") + def main_switch(): """ Main function for anime unity