diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2bc6a96 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "AniWorld-Downloader"] + path = AniWorld-Downloader + url = https://github.com/lukaspupkalipinski/AniWorld-Downloader.git + branch = next diff --git a/AniWorld-Downloader b/AniWorld-Downloader new file mode 160000 index 0000000..94e332f --- /dev/null +++ b/AniWorld-Downloader @@ -0,0 +1 @@ +Subproject commit 94e332f37b3f037d175e579ea370ab2be90534e4 diff --git a/aniworld-3.0.2-py3-none-any.whl b/aniworld-3.0.2-py3-none-any.whl deleted file mode 100644 index dbe6396..0000000 Binary files a/aniworld-3.0.2-py3-none-any.whl and /dev/null differ diff --git a/requirements.txt b/requirements.txt index 310e1a9..ae9fe84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -aniworld requests beautifulsoup4 lxml diff --git a/src/Exceptions.py b/src/Exceptions.py new file mode 100644 index 0000000..cd70bd6 --- /dev/null +++ b/src/Exceptions.py @@ -0,0 +1,7 @@ + +class NoKeyFoundException(Exception): + """Exception raised when an anime key cannot be found.""" + pass +class MatchNotFoundError(Exception): + """Exception raised when an anime key cannot be found.""" + pass \ No newline at end of file diff --git a/src/FolderLookup.py b/src/FolderLookup.py new file mode 100644 index 0000000..9192bc5 --- /dev/null +++ b/src/FolderLookup.py @@ -0,0 +1,160 @@ +import os +import re +import logging +from collections import defaultdict +import pickle +from Serie import Serie +import json +import traceback +from GlobalLogger import setupLogger, error_logger, noKeyFound_logger, noGerFound_logger +from Exceptions import NoKeyFoundException, MatchNotFoundError +import requests + +from aniworld.common import get_season_episode_count + +class FolderLookup: + def __init__(self, basePath: str): + self.directory = basePath + self.folderDict = dict[str, list[Serie]] + logging.info(f"Initialized Loader with base path: {self.directory}") + + self.__init() + def is_null_or_whitespace(self, s): + return s is None or s.strip() == "" + def __init(self): + logging.info("Starting process to load missing episodes") + result = self.__find_mp4_files() + + for folder, mp4_files in result: + try: + serie = self.__ReadDataFromFile(folder) + if (serie != None and not self.is_null_or_whitespace(serie.key)): + missings, site = self.__GetMissingEpisodesAndSeason(serie.key, mp4_files) + serie.episodeDict = missings + self.__SaveData(serie, folder) + if folder not in self.folderDict: + self.folderDict[folder] = [] + self.folderDict[folder].append(serie) + except NoKeyFoundException as nkfe: + noKeyFound_logger.error(f"Error processing folder '{folder}': {nkfe}") + except Exception as e: + error_logger.error(f"Unexpected error processing folder '{folder}': {e} \n {traceback.format_exc()}") + continue + + + def __find_mp4_files(self): + logging.info("Scanning for .mp4 files") + + for root_folder_name in os.listdir(self.directory): + + folder_data = defaultdict(list) # Dictionary to store MP4 files per folder + folder = os.path.join(self.directory, root_folder_name) + + logging.info(f"Processing folder: {root_folder_name}") + + # First pass: Scan all folders and collect MP4 file data + for root, dirs, files in os.walk(folder): + mp4_files = [file for file in files if file.endswith('.mp4')] + if mp4_files: + folder_data[root_folder_name].extend(mp4_files) + yield root_folder_name, folder_data[root_folder_name] + + for dir in self.__find_empty_folders(): + logging.info(f"Found no .mp4 files in {dir}") + yield dir, [] + + def __find_empty_folders(self): + """Yield folder names that do not contain any mp4 files in a given directory.""" + for folder in os.listdir(self.directory): + folder_path = os.path.join(self.directory, folder) + + if os.path.isdir(folder_path): # Ensure it's a directory + has_mp4 = any(file.endswith(".mp4") for file in os.listdir(folder_path)) + + if not has_mp4: + yield folder # Yield the folder name if no mp4 files found + + def __remove_year(self, input_string: str): + cleaned_string = re.sub(r'\(\d{4}\)', '', input_string).strip() + logging.debug(f"Removed year from '{input_string}' -> '{cleaned_string}'") + return cleaned_string + + def __ReadDataFromFile(self, folder_name: str): + folder_path = os.path.join(self.directory, folder_name) + key = None + key_file = os.path.join(folder_path, 'key') + serie_file = os.path.join(folder_path, 'data') + + if os.path.exists(key_file): + with open(key_file, 'r') as file: + logging.info(f"Key found for folder '{folder_name}': {key}") + key = file.read().strip() + return Serie(key, "", "aniworld.to" ,folder_name) + + if os.path.exists(serie_file): + with open(serie_file, "rb") as file: + logging.info(f"load serie_file from '{folder_name}': {serie_file}") + return pickle.load(file) + + return None + + def __SaveData(self, serie: Serie, folder_name: str): + """Saves a Serie object to a file using JSON.""" + + folder_path = os.path.join(self.directory, folder_name) + serie_file = os.path.join(folder_path, 'data') + with open(serie_file, "w", encoding="utf-8") as file: + json.dump(serie, file, indent=4) + + + def __GetEpisodeAndSeason(self, filename: str): + pattern = r'S(\d+)E(\d+)' + match = re.search(pattern, filename) + if match: + season = match.group(1) + episode = match.group(2) + logging.debug(f"Extracted season {season}, episode {episode} from '{filename}'") + return int(season), int(episode) + else: + logging.error(f"Failed to find season/episode pattern in '{filename}'") + raise MatchNotFoundError("Season and episode pattern not found in the filename.") + + def __GetEpisodesAndSeasons(self, mp4_files: []): + episodes_dict = {} + + for file in mp4_files: + season, episode = self.__GetEpisodeAndSeason(file) + + if season in episodes_dict: + episodes_dict[season].append(episode) + else: + episodes_dict[season] = [episode] + + return episodes_dict + + + def __GetMissingEpisodesAndSeason(self, key: str, mp4_files: []): + expected_dict = get_season_episode_count(key) # key season , value count of episodes + filedict = self.__GetEpisodesAndSeasons(mp4_files) + episodes_dict = {} + for season, expected_count in expected_dict.items(): + existing_episodes = filedict.get(season, []) + missing_episodes = [ep for ep in range(1, expected_count + 1) if ep not in existing_episodes] + + if missing_episodes: + episodes_dict[season] = [missing_episodes] + + return episodes_dict, "aniworld.to" + +#gg = FolderLookup("\\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien") + +base_url = f"https://aniworld.to/anime/stream/aico-incarnation" +headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "Accept": "*/*", + "Referer": "https://www.example.com" +} + +session = requests.Session() +response = requests.get(base_url, headers=headers, timeout=30) +c = response.content; \ No newline at end of file diff --git a/src/GlobalLogger.py b/src/GlobalLogger.py new file mode 100644 index 0000000..def22d1 --- /dev/null +++ b/src/GlobalLogger.py @@ -0,0 +1,40 @@ +import logging + +console_handler = None +error_logger = None +noKeyFound_logger = None +noGerFound_logger = None +def setupLogger(): + global console_handler, error_logger, noKeyFound_logger, noGerFound_logger + # Configure logging + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(funcName)s - %(message)s') + if (console_handler is None): + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + console_handler.setFormatter(logging.Formatter( + "%(asctime)s - %(levelname)s - %(funcName)s - %(message)s") + ) + logging.getLogger().addHandler(console_handler) + logging.getLogger("urllib3.connectionpool").setLevel(logging.INFO) + logging.getLogger('charset_normalizer').setLevel(logging.INFO) + logging.getLogger().setLevel(logging.INFO) + + if (error_logger is None): + error_logger = logging.getLogger("ErrorLog") + error_handler = logging.FileHandler("../errors.log") + error_handler.setLevel(logging.ERROR) + error_logger.addHandler(error_handler) + + if (noKeyFound_logger is None): + noKeyFound_logger = logging.getLogger("NoKeyFound") + noKeyFound_handler = logging.FileHandler("../NoKeyFound.log") + noKeyFound_handler.setLevel(logging.ERROR) + noKeyFound_logger.addHandler(noKeyFound_handler) + + if (noGerFound_logger is None): + noGerFound_logger = logging.getLogger("noGerFound") + noGerFound_handler = logging.FileHandler("../noGerFound.log") + noGerFound_handler.setLevel(logging.ERROR) + noGerFound_logger.addHandler(noGerFound_handler) + +setupLogger() \ No newline at end of file diff --git a/Loader.py b/src/Loader.py similarity index 97% rename from Loader.py rename to src/Loader.py index 2724f27..e7d3c98 100644 --- a/Loader.py +++ b/src/Loader.py @@ -11,7 +11,7 @@ from aniworld.parser import arguments timeout = int(os.getenv("DOWNLOAD_TIMEOUT", 600)) download_error_logger = logging.getLogger("DownloadErrors") -download_error_handler = logging.FileHandler("download_errors.log") +download_error_handler = logging.FileHandler("../download_errors.log") download_error_handler.setLevel(logging.ERROR) download_error_logger.addHandler(download_error_handler) diff --git a/src/Serie.py b/src/Serie.py new file mode 100644 index 0000000..90e2782 --- /dev/null +++ b/src/Serie.py @@ -0,0 +1,47 @@ +class Serie: + def __init__(self, key: str, name: str, site: str, folder: str): + self._key = key + self._name = name + self._site = site + self._folder = folder + self._episodeDict = dict[int, list[int]] + + @property + def key(self) -> str: + return self._key + + @key.setter + def key(self, value: str): + self._key = value + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = value + + @property + def site(self) -> str: + return self._site + + @site.setter + def site(self, value: str): + self._site = value + + @property + def folder(self) -> str: + return self._folder + + @folder.setter + def folder(self, value: str): + self._folder = value + + @property + def episodeDict(self) -> dict[int, list[int]]: + return self._episodeDict + + @episodeDict.setter + def episodeDict(self, value: dict[int, list[int]]): + self._episodeDict = value diff --git a/src/console.py b/src/console.py new file mode 100644 index 0000000..aa76169 --- /dev/null +++ b/src/console.py @@ -0,0 +1,59 @@ +import sys +import os +import traceback +import re +import logging +from concurrent.futures import ThreadPoolExecutor +from collections import defaultdict +from aniworld.models import Anime, Episode +from aniworld.common import get_season_episode_count, get_movie_episode_count +from aniworld.search import search_anime +from Loader import download + + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(funcName)s - %(message)s') +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.INFO) +console_handler.setFormatter(logging.Formatter( + "%(asctime)s - %(levelname)s - %(funcName)s - %(message)s") +) +logging.getLogger().addHandler(console_handler) +logging.getLogger("urllib3.connectionpool").setLevel(logging.INFO) +logging.getLogger('charset_normalizer').setLevel(logging.INFO) +logging.getLogger().setLevel(logging.INFO) + +error_logger = logging.getLogger("ErrorLog") +error_handler = logging.FileHandler("../errors.log") +error_handler.setLevel(logging.ERROR) +error_logger.addHandler(error_handler) + +noKeyFound_logger = logging.getLogger("NoKeyFound") +noKeyFound_handler = logging.FileHandler("../NoKeyFound.log") +noKeyFound_handler.setLevel(logging.ERROR) +noKeyFound_logger.addHandler(noKeyFound_handler) + +noGerFound_logger = logging.getLogger("noGerFound") +noGerFound_handler = logging.FileHandler("../noGerFound.log") +noGerFound_handler.setLevel(logging.ERROR) +noGerFound_logger.addHandler(noGerFound_handler) + +class NoKeyFoundException(Exception): + """Exception raised when an anime key cannot be found.""" + pass +class MatchNotFoundError(Exception): + """Exception raised when an anime key cannot be found.""" + pass + +class ConsoleLoader: + def __init__(self, basePath: str): + self.directory = basePath + logging.info(f"Initialized Loader with base path: {self.directory}") + + + +# Read the base directory from an environment variable +directory_to_search = os.getenv("ANIME_DIRECTORY", "\\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien") + +loader = Loader(directory_to_search) +loader.LoadMissing() \ No newline at end of file diff --git a/main.py b/src/main.py similarity index 92% rename from main.py rename to src/main.py index fdb8d17..01de9c5 100644 --- a/main.py +++ b/src/main.py @@ -1,14 +1,12 @@ -import sys import os import traceback import re import logging -from concurrent.futures import ThreadPoolExecutor from collections import defaultdict from aniworld.models import Anime, Episode -from aniworld.common import get_season_episode_count, get_movie_episode_count +from aniworld.common import get_season_episode_count from aniworld.search import search_anime -from Loader import download +from src.Loader import download # Configure logging @@ -24,17 +22,17 @@ logging.getLogger('charset_normalizer').setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO) error_logger = logging.getLogger("ErrorLog") -error_handler = logging.FileHandler("errors.log") +error_handler = logging.FileHandler("../errors.log") error_handler.setLevel(logging.ERROR) error_logger.addHandler(error_handler) noKeyFound_logger = logging.getLogger("NoKeyFound") -noKeyFound_handler = logging.FileHandler("NoKeyFound.log") +noKeyFound_handler = logging.FileHandler("../NoKeyFound.log") noKeyFound_handler.setLevel(logging.ERROR) noKeyFound_logger.addHandler(noKeyFound_handler) noGerFound_logger = logging.getLogger("noGerFound") -noGerFound_handler = logging.FileHandler("noGerFound.log") +noGerFound_handler = logging.FileHandler("../noGerFound.log") noGerFound_handler.setLevel(logging.ERROR) noGerFound_logger.addHandler(noGerFound_handler) @@ -167,8 +165,6 @@ class Loader: except Exception as e: logging.error(f"Error downloading episode {episode} of season {season} for anime {key}: {e}") - # Using ThreadPoolExecutor to run downloads in parallel - #with ThreadPoolExecutor(max_workers=1) as executor: # Adjust number of workers as needed for folder, mp4_files in result: try: key = self.__check_and_generate_key(folder) @@ -176,7 +172,6 @@ class Loader: for season, missing_episodes in missings: logging.info(f"Missing episodes for {key}\nSeason {str(season)}: Episodes: " + ",".join(f"{''.join(str(v))}" for v in missing_episodes)) for episode in missing_episodes: - #executor.submit(download_episode, folder, season, episode, key) download_episode(folder, season, episode, key) except NoKeyFoundException as nkfe: noKeyFound_logger.error(f"Error processing folder '{folder}': {nkfe}") @@ -186,7 +181,7 @@ class Loader: # Read the base directory from an environment variable directory_to_search = os.getenv("ANIME_DIRECTORY", "\\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien") -#directory_to_search = os.getenv("ANIME_DIRECTORY", "D:\sss") +#directory_to_search = os.getenv("ANIME_DIRECTORY", "D:\\sss") loader = Loader(directory_to_search) loader.LoadMissing() \ No newline at end of file