Compare commits
No commits in common. "22ee445b7eb12f6eed8d23f9d161a2fefdfd00d1" and "aade02d7634788b0da861fa5f088aff66fe709d1" have entirely different histories.
22ee445b7e
...
aade02d763
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,4 +0,0 @@
|
|||||||
[submodule "AniWorld-Downloader"]
|
|
||||||
path = AniWorld-Downloader
|
|
||||||
url = https://github.com/lukaspupkalipinski/AniWorld-Downloader.git
|
|
||||||
branch = next
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Subproject commit 94e332f37b3f037d175e579ea370ab2be90534e4
|
|
||||||
@ -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)
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ def download(anime: Anime): # pylint: disable=too-many-branches
|
|||||||
"yt-dlp",
|
"yt-dlp",
|
||||||
episode.get_direct_link(anime.provider, anime.language),
|
episode.get_direct_link(anime.provider, anime.language),
|
||||||
"--fragment-retries", "infinite",
|
"--fragment-retries", "infinite",
|
||||||
#"--concurrent-fragments", "4",
|
"--concurrent-fragments", "4",
|
||||||
"-o", output_path,
|
"-o", output_path,
|
||||||
"--quiet",
|
"--quiet",
|
||||||
"--no-warnings"
|
"--no-warnings"
|
||||||
BIN
aniworld-3.0.2-py3-none-any.whl
Normal file
BIN
aniworld-3.0.2-py3-none-any.whl
Normal file
Binary file not shown.
@ -1,12 +1,14 @@
|
|||||||
|
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
|
from aniworld.common import get_season_episode_count, get_movie_episode_count
|
||||||
from aniworld.search import search_anime
|
from aniworld.search import search_anime
|
||||||
from src.Loader import download
|
from Loader import download
|
||||||
|
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
@ -22,17 +24,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)
|
||||||
|
|
||||||
@ -165,23 +167,26 @@ 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}")
|
||||||
|
|
||||||
for folder, mp4_files in result:
|
# Using ThreadPoolExecutor to run downloads in parallel
|
||||||
try:
|
with ThreadPoolExecutor(max_workers=2) as executor: # Adjust number of workers as needed
|
||||||
key = self.__check_and_generate_key(folder)
|
for folder, mp4_files in result:
|
||||||
missings = self.__GetMissingEpisodesAndSeason(key, mp4_files)
|
try:
|
||||||
for season, missing_episodes in missings:
|
key = self.__check_and_generate_key(folder)
|
||||||
logging.info(f"Missing episodes for {key}\nSeason {str(season)}: Episodes: " + ",".join(f"{''.join(str(v))}" for v in missing_episodes))
|
missings = self.__GetMissingEpisodesAndSeason(key, mp4_files)
|
||||||
for episode in missing_episodes:
|
for season, missing_episodes in missings:
|
||||||
download_episode(folder, season, episode, key)
|
logging.info(f"Missing episodes for {key}\nSeason {str(season)}: Episodes: " + ",".join(f"{''.join(str(v))}" for v in missing_episodes))
|
||||||
except NoKeyFoundException as nkfe:
|
for episode in missing_episodes:
|
||||||
noKeyFound_logger.error(f"Error processing folder '{folder}': {nkfe}")
|
executor.submit(download_episode, folder, season, episode, key)
|
||||||
except Exception as e:
|
#download_episode(folder, season, episode, key)
|
||||||
error_logger.error(f"Unexpected error processing folder '{folder}': {e} \n {traceback.format_exc()}")
|
except NoKeyFoundException as nkfe:
|
||||||
continue
|
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
|
||||||
|
|
||||||
# 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()
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
aniworld
|
||||||
requests
|
requests
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
lxml
|
lxml
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
|
|
||||||
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
|
|
||||||
@ -1,160 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
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()
|
|
||||||
47
src/Serie.py
47
src/Serie.py
@ -1,47 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
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()
|
|
||||||
Loading…
x
Reference in New Issue
Block a user