Compare commits

..

No commits in common. "862de2f9d29ae775e7fc3dd4a0ff8d44f8a4c1a9" and "ad617847443df67bc34273b9e7b5f8e303a8c238" have entirely different histories.

4 changed files with 14 additions and 106 deletions

View File

@ -1,56 +0,0 @@
import os
import hashlib
from collections import defaultdict
def compute_hash(filepath, chunk_size=8192):
sha256 = hashlib.sha256()
try:
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(chunk_size), b''):
sha256.update(chunk)
except Exception as e:
print(f"Error reading {filepath}: {e}")
return None
return sha256.hexdigest()
def find_duplicates(root_dir):
size_dict = defaultdict(list)
# Step 1: Group files by size
for dirpath, _, filenames in os.walk(root_dir):
for file in filenames:
if file.lower().endswith('.mp4'):
filepath = os.path.join(dirpath, file)
try:
size = os.path.getsize(filepath)
size_dict[size].append(filepath)
except Exception as e:
print(f"Error accessing {filepath}: {e}")
# Step 2: Within size groups, group by hash
duplicates = defaultdict(list)
for size, files in size_dict.items():
if len(files) < 2:
continue
hash_dict = defaultdict(list)
for file in files:
file_hash = compute_hash(file)
if file_hash:
hash_dict[file_hash].append(file)
for h, paths in hash_dict.items():
if len(paths) > 1:
duplicates[h].extend(paths)
return duplicates
# Example usage
if __name__ == "__main__":
folder_to_scan = "\\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien"
dupes = find_duplicates(folder_to_scan)
for hash_val, files in dupes.items():
print(f"\nDuplicate group (hash: {hash_val}):")
for f in files:
print(f" {f}")

View File

@ -1,5 +1,6 @@
import os import os
import re import re
import subprocess
import logging import logging
import json import json
import requests import requests
@ -15,7 +16,6 @@ from urllib3.util.retry import Retry
from src.Loaders.Loader import Loader from src.Loaders.Loader import Loader
from src.Loaders.Providers import Providers from src.Loaders.Providers import Providers
from yt_dlp import YoutubeDL from yt_dlp import YoutubeDL
import shutil
# Read timeout from environment variable, default to 600 seconds (10 minutes) # Read timeout from environment variable, default to 600 seconds (10 minutes)
timeout = int(os.getenv("DOWNLOAD_TIMEOUT", 600)) timeout = int(os.getenv("DOWNLOAD_TIMEOUT", 600))
@ -79,13 +79,6 @@ class AniworldLoader(Loader):
self._EpisodeHTMLDict = {} self._EpisodeHTMLDict = {}
self.Providers = Providers() self.Providers = Providers()
def ClearCache(self):
self._KeyHTMLDict = {}
self._EpisodeHTMLDict = {}
def RemoveFromCache(self):
self._EpisodeHTMLDict = {}
def Search(self, word: str) -> list: def Search(self, word: str) -> list:
search_url = f"{self.ANIWORLD_TO}/ajax/seriesSearch?keyword={quote(word)}" search_url = f"{self.ANIWORLD_TO}/ajax/seriesSearch?keyword={quote(word)}"
anime_list = self.fetch_anime_list(search_url) anime_list = self.fetch_anime_list(search_url)
@ -146,6 +139,7 @@ class AniworldLoader(Loader):
return languageCode in languages return languageCode in languages
def Download(self, baseDirectory: str, serieFolder: str, season: int, episode: int, key: str, language: str = "German Dub") -> bool: def Download(self, baseDirectory: str, serieFolder: str, season: int, episode: int, key: str, language: str = "German Dub") -> bool:
sanitized_anime_title = ''.join( sanitized_anime_title = ''.join(
char for char in self.GetTitle(key) if char not in self.INVALID_PATH_CHARS char for char in self.GetTitle(key) if char not in self.INVALID_PATH_CHARS
@ -164,24 +158,17 @@ class AniworldLoader(Loader):
f"({language}).mp4" f"({language}).mp4"
) )
folderPath = os.path.join(os.path.join(baseDirectory, serieFolder), f"Season {season}") output_path = os.path.join(os.path.join(baseDirectory, serieFolder), output_file)
output_path = os.path.join(folderPath, output_file)
os.makedirs(os.path.dirname(output_path), exist_ok=True) os.makedirs(os.path.dirname(output_path), exist_ok=True)
# Get the system-designated temp directory
temp_dir = "./Temp/"
os.makedirs(os.path.dirname(temp_dir), exist_ok=True)
temp_Path = os.path.join(temp_dir, output_file)
for provider in self.SUPPORTED_PROVIDERS: for provider in self.SUPPORTED_PROVIDERS:
link, header = self._get_direct_link_from_provider(season, episode, key, language) link, header = self._get_direct_link_from_provider(season, episode, key, language)
ydl_opts = { ydl_opts = {
'fragment_retries': float('inf'), 'fragment_retries': float('inf'),
'outtmpl': temp_Path, 'outtmpl': output_path,
'quiet': True, 'quiet': True,
'no_warnings': True, 'no_warnings': True,
'progress_with_newline': False, 'progress_with_newline': True
} }
if header: if header:
@ -189,12 +176,7 @@ class AniworldLoader(Loader):
with YoutubeDL(ydl_opts) as ydl: with YoutubeDL(ydl_opts) as ydl:
ydl.download([link]) ydl.download([link])
if (os.path.exists(temp_Path)):
shutil.copy(temp_Path, output_path)
os.remove(temp_Path)
break break
self.ClearCache()
def GetSiteKey(self) -> str: def GetSiteKey(self) -> str:
@ -221,7 +203,7 @@ class AniworldLoader(Loader):
return self._KeyHTMLDict[key] return self._KeyHTMLDict[key]
def _GetEpisodeHTML(self, season: int, episode: int, key: str): def _GetEpisodeHTML(self, season: int, episode: int, key: str):
if key in self._EpisodeHTMLDict: if key in self._EpisodeHTMLDict:
return self._EpisodeHTMLDict[(key, season, episode)] return self._EpisodeHTMLDict[key]
link = ( link = (
@ -229,8 +211,8 @@ class AniworldLoader(Loader):
f"staffel-{season}/episode-{episode}" f"staffel-{season}/episode-{episode}"
) )
html = self.session.get(link, timeout=self.DEFAULT_REQUEST_TIMEOUT) html = self.session.get(link, timeout=self.DEFAULT_REQUEST_TIMEOUT)
self._EpisodeHTMLDict[(key, season, episode)] = html self._EpisodeHTMLDict[key] = html
return self._EpisodeHTMLDict[(key, season, episode)] return self._EpisodeHTMLDict[key]
def _get_provider_from_html(self, season: int, episode: int, key: str) -> dict: def _get_provider_from_html(self, season: int, episode: int, key: str) -> dict:
""" """

View File

@ -6,7 +6,6 @@ import SerieList
import SerieScanner import SerieScanner
from src.Loaders.Loaders import Loaders from src.Loaders.Loaders import Loaders
from src.Serie import Serie from src.Serie import Serie
import time
# Configure logging # Configure logging
logging.basicConfig(level=logging.FATAL, format='%(asctime)s - %(levelname)s - %(funcName)s - %(message)s') logging.basicConfig(level=logging.FATAL, format='%(asctime)s - %(levelname)s - %(funcName)s - %(message)s')
@ -56,8 +55,7 @@ class SeriesApp:
print(f"{i}. {serie}") print(f"{i}. {serie}")
def search(self, words :str) -> list: def search(self, words :str) -> list:
loader = self.Loaders.GetLoader(key="aniworld.to") return AniWorldLoader.search_anime(words)
return loader.Search(words)
def get_user_selection(self): def get_user_selection(self):
"""Handle user input for selecting series.""" """Handle user input for selecting series."""
@ -93,20 +91,6 @@ class SeriesApp:
bar = "@" * filled_length + "-" * (length - filled_length) bar = "@" * filled_length + "-" * (length - filled_length)
return f"[{bar}] {current} / {total}" return f"[{bar}] {current} / {total}"
def retry(self, func, max_retries=3, delay=2, *args, **kwargs):
for attempt in range(1, max_retries + 1):
try:
func(*args, **kwargs)
return True
except Exception as e:
print(f"Attempt {attempt} failed: {e}")
if attempt == max_retries:
print("All attempts failed.")
else:
print(f"Retrying in {delay} seconds...\n")
time.sleep(delay)
return False
def download_series(self, series): def download_series(self, series):
"""Simulate the downloading process with a progress bar.""" """Simulate the downloading process with a progress bar."""
total_downloaded = 0 total_downloaded = 0
@ -123,7 +107,7 @@ class SeriesApp:
loader = self.Loaders.GetLoader(key="aniworld.to") loader = self.Loaders.GetLoader(key="aniworld.to")
if loader.IsLanguage(season, episode, serie.key): if loader.IsLanguage(season, episode, serie.key):
print(f"\ndownload {serie.folder} {season} {episode}\n") print(f"\ndownload {serie.folder} {season} {episode}\n")
self.retry(loader.Download, 3, 1, self.directory_to_search, serie.folder, season, episode, serie.key) loader.Download(self.directory_to_search, serie.folder, season, episode, serie.key)
downloaded += 1 downloaded += 1
total_downloaded += 1 total_downloaded += 1
@ -158,6 +142,7 @@ class SeriesApp:
index = int(selection) - 1 index = int(selection) - 1
if 0 <= index < len(results): if 0 <= index < len(results):
chosen_name = results[index] chosen_name = results[index]
self.List.add(Serie(chosen_name["link"], chosen_name["name"], "aniworld.to", chosen_name["link"], {})) self.List.add(Serie(chosen_name["link"], chosen_name["name"], "aniworld.to", chosen_name["link"], {}))
return return
else: else:
@ -165,7 +150,6 @@ class SeriesApp:
except ValueError: except ValueError:
print("Invalid input. Try again.") print("Invalid input. Try again.")
def run(self): def run(self):
"""Main function to run the app.""" """Main function to run the app."""
while True: while True:

View File

@ -31,12 +31,10 @@ class SerieScanner:
if (serie != None and not self.is_null_or_whitespace(serie.key)): if (serie != None and not self.is_null_or_whitespace(serie.key)):
missings, site = self.__GetMissingEpisodesAndSeason(serie.key, mp4_files) missings, site = self.__GetMissingEpisodesAndSeason(serie.key, mp4_files)
serie.episodeDict = missings serie.episodeDict = missings
serie.folder = folder
serie.save_to_file(os.path.join(os.path.join(self.directory, folder), 'data')) serie.save_to_file(os.path.join(os.path.join(self.directory, folder), 'data'))
if (serie.key in self.folderDict): if folder not in self.folderDict:
logging.ERROR(f"dublication found: {serie.key}"); self.folderDict[folder] = []
pass self.folderDict[folder] = serie
self.folderDict[serie.key] = serie
noKeyFound_logger.info(f"Saved Serie: '{str(serie)}'") noKeyFound_logger.info(f"Saved Serie: '{str(serie)}'")
except NoKeyFoundException as nkfe: except NoKeyFoundException as nkfe:
NoKeyFoundException.error(f"Error processing folder '{folder}': {nkfe}") NoKeyFoundException.error(f"Error processing folder '{folder}': {nkfe}")