This commit is contained in:
Lukas Pupka-Lipinski 2025-05-30 17:30:32 +02:00
parent cb6e74199b
commit 22ee445b7e
11 changed files with 325 additions and 13 deletions

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "AniWorld-Downloader"]
path = AniWorld-Downloader
url = https://github.com/lukaspupkalipinski/AniWorld-Downloader.git
branch = next

1
AniWorld-Downloader Submodule

@ -0,0 +1 @@
Subproject commit 94e332f37b3f037d175e579ea370ab2be90534e4

Binary file not shown.

View File

@ -1,4 +1,3 @@
aniworld
requests requests
beautifulsoup4 beautifulsoup4
lxml lxml

7
src/Exceptions.py Normal file
View File

@ -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

160
src/FolderLookup.py Normal file
View File

@ -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;

40
src/GlobalLogger.py Normal file
View File

@ -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()

View File

@ -11,7 +11,7 @@ from aniworld.parser import arguments
timeout = int(os.getenv("DOWNLOAD_TIMEOUT", 600)) timeout = int(os.getenv("DOWNLOAD_TIMEOUT", 600))
download_error_logger = logging.getLogger("DownloadErrors") 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_handler.setLevel(logging.ERROR)
download_error_logger.addHandler(download_error_handler) download_error_logger.addHandler(download_error_handler)

47
src/Serie.py Normal file
View File

@ -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

59
src/console.py Normal file
View File

@ -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()

View File

@ -1,14 +1,12 @@
import sys
import os import os
import traceback import traceback
import re import re
import logging import logging
from concurrent.futures import ThreadPoolExecutor
from collections import defaultdict from collections import defaultdict
from aniworld.models import Anime, Episode 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 aniworld.search import search_anime
from Loader import download from src.Loader import download
# Configure logging # Configure logging
@ -24,17 +22,17 @@ logging.getLogger('charset_normalizer').setLevel(logging.INFO)
logging.getLogger().setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO)
error_logger = logging.getLogger("ErrorLog") error_logger = logging.getLogger("ErrorLog")
error_handler = logging.FileHandler("errors.log") error_handler = logging.FileHandler("../errors.log")
error_handler.setLevel(logging.ERROR) error_handler.setLevel(logging.ERROR)
error_logger.addHandler(error_handler) error_logger.addHandler(error_handler)
noKeyFound_logger = logging.getLogger("NoKeyFound") noKeyFound_logger = logging.getLogger("NoKeyFound")
noKeyFound_handler = logging.FileHandler("NoKeyFound.log") noKeyFound_handler = logging.FileHandler("../NoKeyFound.log")
noKeyFound_handler.setLevel(logging.ERROR) noKeyFound_handler.setLevel(logging.ERROR)
noKeyFound_logger.addHandler(noKeyFound_handler) noKeyFound_logger.addHandler(noKeyFound_handler)
noGerFound_logger = logging.getLogger("noGerFound") noGerFound_logger = logging.getLogger("noGerFound")
noGerFound_handler = logging.FileHandler("noGerFound.log") noGerFound_handler = logging.FileHandler("../noGerFound.log")
noGerFound_handler.setLevel(logging.ERROR) noGerFound_handler.setLevel(logging.ERROR)
noGerFound_logger.addHandler(noGerFound_handler) noGerFound_logger.addHandler(noGerFound_handler)
@ -167,8 +165,6 @@ class Loader:
except Exception as e: except Exception as e:
logging.error(f"Error downloading episode {episode} of season {season} for anime {key}: {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: for folder, mp4_files in result:
try: try:
key = self.__check_and_generate_key(folder) key = self.__check_and_generate_key(folder)
@ -176,7 +172,6 @@ class Loader:
for season, missing_episodes in missings: 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)) 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: for episode in missing_episodes:
#executor.submit(download_episode, folder, season, episode, key)
download_episode(folder, season, episode, key) download_episode(folder, season, episode, key)
except NoKeyFoundException as nkfe: except NoKeyFoundException as nkfe:
noKeyFound_logger.error(f"Error processing folder '{folder}': {nkfe}") noKeyFound_logger.error(f"Error processing folder '{folder}': {nkfe}")
@ -186,7 +181,7 @@ class Loader:
# Read the base directory from an environment variable # 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", "\\\\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 = Loader(directory_to_search)
loader.LoadMissing() loader.LoadMissing()