first version
This commit is contained in:
commit
1bc13e7cb0
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/.idea/*
|
||||
/aniworld/bin/*
|
||||
/aniworld/lib/*
|
||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
FROM ubuntu:latest
|
||||
|
||||
# Install dependencies
|
||||
RUN apt update && apt install -y \
|
||||
openvpn \
|
||||
python3-pip \
|
||||
protonvpn-cli \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies if you have a requirements file
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the Python script and monitoring script
|
||||
COPY main.py /main.py
|
||||
COPY vpn_monitor.sh /vpn_monitor.sh
|
||||
|
||||
# Ensure scripts are executable
|
||||
RUN chmod +x /main.py /vpn_monitor.sh
|
||||
|
||||
# Entry point: Start VPN and monitor status
|
||||
CMD ["bash", "/vpn_monitor.sh"]
|
||||
89
Loader.py
Normal file
89
Loader.py
Normal file
@ -0,0 +1,89 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
from aniworld.models import Anime
|
||||
from aniworld.config import PROVIDER_HEADERS, INVALID_PATH_CHARS
|
||||
from aniworld.parser import arguments
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
filename="download.log",
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
# Read timeout from environment variable, default to 600 seconds (10 minutes)
|
||||
timeout = int(os.getenv("DOWNLOAD_TIMEOUT", 600))
|
||||
|
||||
def download(anime: Anime): # pylint: disable=too-many-branches
|
||||
for episode in anime:
|
||||
sanitized_anime_title = ''.join(
|
||||
char for char in anime.title if char not in INVALID_PATH_CHARS
|
||||
)
|
||||
|
||||
if episode.season == 0:
|
||||
output_file = (
|
||||
f"{sanitized_anime_title} - "
|
||||
f"Movie {episode.episode:02} - "
|
||||
f"({anime.language}).mp4"
|
||||
)
|
||||
else:
|
||||
output_file = (
|
||||
f"{sanitized_anime_title} - "
|
||||
f"S{episode.season:02}E{episode.episode:03} - "
|
||||
f"({anime.language}).mp4"
|
||||
)
|
||||
|
||||
output_path = os.path.join(anime.output_directory, output_file)
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
logging.info(f"Preparing to download: {output_path}")
|
||||
|
||||
command = [
|
||||
"yt-dlp",
|
||||
episode.get_direct_link(anime.provider, anime.language),
|
||||
"--fragment-retries", "infinite",
|
||||
"--concurrent-fragments", "4",
|
||||
"-o", output_path,
|
||||
"--quiet",
|
||||
"--no-warnings",
|
||||
"--progress"
|
||||
]
|
||||
|
||||
if anime.provider in PROVIDER_HEADERS:
|
||||
for header in PROVIDER_HEADERS[anime.provider]:
|
||||
command.extend(["--add-header", header])
|
||||
|
||||
if arguments.only_command:
|
||||
logging.info(
|
||||
f"{anime.title} - S{episode.season}E{episode.episode} - ({anime.language}): "
|
||||
f"{' '.join(str(item) if item is not None else '' for item in command)}"
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
|
||||
logging.info(f"Starting download to {output_path}...")
|
||||
subprocess.run(command, check=True, timeout=timeout)
|
||||
logging.info(f"Download completed: {output_path}")
|
||||
except subprocess.TimeoutExpired:
|
||||
logging.error(f"Download timed out after {timeout} seconds: {' '.join(str(item) for item in command)}")
|
||||
except subprocess.CalledProcessError:
|
||||
logging.error(f"Error running command: {' '.join(str(item) for item in command)}")
|
||||
except KeyboardInterrupt:
|
||||
logging.warning("Download interrupted by user.")
|
||||
output_dir = os.path.dirname(output_path)
|
||||
is_empty = True
|
||||
|
||||
for file_name in os.listdir(output_dir):
|
||||
if re.search(r'\.(part|ytdl|part-Frag\d+)$', file_name):
|
||||
os.remove(os.path.join(output_dir, file_name))
|
||||
else:
|
||||
is_empty = False
|
||||
|
||||
if is_empty or not os.listdir(output_dir):
|
||||
os.rmdir(output_dir)
|
||||
logging.info(f"Removed empty download directory: {output_dir}")
|
||||
|
||||
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.
5
aniworld/pyvenv.cfg
Normal file
5
aniworld/pyvenv.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
home = /usr/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.12.3
|
||||
executable = /usr/bin/python3.12
|
||||
command = /usr/bin/python3 -m venv /mnt/d/repo/AniWorld/aniworld
|
||||
98
main.py
Normal file
98
main.py
Normal file
@ -0,0 +1,98 @@
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
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(
|
||||
filename="loader.log",
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
class MatchNotFoundError(Exception):
|
||||
"""Custom exception raised when the pattern match is not found."""
|
||||
pass
|
||||
|
||||
class Loader:
|
||||
def __init__(self, basePath: str):
|
||||
self.directory = basePath
|
||||
logging.info(f"Initialized Loader with base path: {self.directory}")
|
||||
|
||||
def __find_mp4_files(self):
|
||||
logging.info("Scanning for .mp4 files")
|
||||
for root, dirs, files in os.walk(self.directory):
|
||||
mp4_files = [file for file in files if file.endswith('.mp4')]
|
||||
if mp4_files:
|
||||
relative_path = os.path.relpath(root, self.directory)
|
||||
root_folder_name = relative_path.split(os.sep)[0]
|
||||
logging.debug(f"Found {len(mp4_files)} .mp4 files in {root_folder_name}")
|
||||
yield root_folder_name, mp4_files
|
||||
|
||||
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 __check_and_generate_key(self, folder_name: str):
|
||||
folder_path = os.path.join(self.directory, folder_name)
|
||||
key_file = os.path.join(folder_path, 'key')
|
||||
|
||||
if os.path.exists(key_file):
|
||||
with open(key_file, 'r') as file:
|
||||
key = file.read().strip()
|
||||
logging.debug(f"Key found for folder '{folder_name}': {key}")
|
||||
return key
|
||||
else:
|
||||
key = search_anime(self.__remove_year(folder_name))
|
||||
with open(key_file, 'w') as file:
|
||||
file.write(key)
|
||||
logging.info(f"Generated new key for folder '{folder_name}': {key}")
|
||||
return key
|
||||
|
||||
def __GetEpisodeAndSeason(self, filename: str):
|
||||
pattern = r'S(\d{2})E(\d{2})'
|
||||
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 season, 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 LoadMissing(self):
|
||||
logging.info("Starting process to load missing episodes")
|
||||
result = self.__find_mp4_files()
|
||||
|
||||
for folder, mp4_files in result:
|
||||
try:
|
||||
key = self.__check_and_generate_key(folder)
|
||||
missings = self.__GetMissingEpisodesAndSeason(key, mp4_files)
|
||||
|
||||
for season, missing_episodes in missings:
|
||||
folder_path = os.path.join(self.directory, folder, f"Season {season}")
|
||||
|
||||
for episode in missing_episodes:
|
||||
anime = Anime(
|
||||
episode_list=[
|
||||
Episode(slug=key, season=season, episode=episode)
|
||||
],
|
||||
language="German Dub",
|
||||
output_directory=folder_path
|
||||
)
|
||||
logging.info(f"Downloading episode {episode} of season {season} for anime {key}")
|
||||
download(anime)
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing folder '{folder}': {e}")
|
||||
continue
|
||||
|
||||
# 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()
|
||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
aniworld
|
||||
requests
|
||||
beautifulsoup4
|
||||
lxml
|
||||
python-dotenv
|
||||
42
vpn_monitor.sh
Normal file
42
vpn_monitor.sh
Normal file
@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Try connecting to ProtonVPN with retries
|
||||
MAX_RETRIES=5
|
||||
RETRY_DELAY=10 # seconds
|
||||
TRIES=0
|
||||
|
||||
while [[ $TRIES -lt $MAX_RETRIES ]]; do
|
||||
echo "Attempting to connect to ProtonVPN (try #$((TRIES+1)))..."
|
||||
|
||||
protonvpn-cli c -r
|
||||
|
||||
# Check if the connection was successful
|
||||
if protonvpn-cli status | grep -q "Connected"; then
|
||||
echo "VPN connected successfully!"
|
||||
break
|
||||
fi
|
||||
|
||||
echo "VPN connection failed, retrying in $RETRY_DELAY seconds..."
|
||||
sleep $RETRY_DELAY
|
||||
((TRIES++))
|
||||
done
|
||||
|
||||
# If the connection still failed after retries, exit
|
||||
if ! protonvpn-cli status | grep -q "Connected"; then
|
||||
echo "Failed to establish VPN connection after $MAX_RETRIES attempts. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start the main Python script in the background
|
||||
python3 /main.py &
|
||||
MAIN_PID=$!
|
||||
|
||||
# Monitor VPN connection
|
||||
while true; do
|
||||
if ! protonvpn-cli status | grep -q "Connected"; then
|
||||
echo "VPN disconnected! Stopping main.py..."
|
||||
kill $MAIN_PID
|
||||
exit 1
|
||||
fi
|
||||
sleep 5 # Check every 5 seconds
|
||||
done
|
||||
Loading…
x
Reference in New Issue
Block a user