From 94866b68f3e876d03a060e7a6b01b50466ba37bb Mon Sep 17 00:00:00 2001 From: axtrat Date: Thu, 11 Dec 2025 13:02:40 +0100 Subject: [PATCH 1/3] fix(animeunity): patch missing video info due to VixCloud changes VixCloud's window.video object no longer provides 'quality' and 'filename' fields, causing a KeyError. This fix updates the extraction logic. --- .../provider/anime/animeunity/constants.py | 3 +++ .../provider/anime/animeunity/provider.py | 26 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/viu_media/libs/provider/anime/animeunity/constants.py b/viu_media/libs/provider/anime/animeunity/constants.py index 8421a8d..7552685 100644 --- a/viu_media/libs/provider/anime/animeunity/constants.py +++ b/viu_media/libs/provider/anime/animeunity/constants.py @@ -11,4 +11,7 @@ REPLACEMENT_WORDS = {"Season ": "", "Cour": "Part"} # Server Specific AVAILABLE_VIDEO_QUALITY = ["1080", "720", "480"] VIDEO_INFO_REGEX = re.compile(r"window.video\s*=\s*(\{[^\}]*\})") +VIDEO_INFO_CLEAN_REGEX = re.compile(r'(? 720 + info["quality"] = int(quality_match.group(1)[:-1]) + else: + info["quality"] = 0 # Fallback + yield map_to_server(episode, info, params.translation_type) From 83ad67a4a86bcf174bf5ae49a45cb91810591ac2 Mon Sep 17 00:00:00 2001 From: axtrat Date: Thu, 11 Dec 2025 13:19:39 +0100 Subject: [PATCH 2/3] refactor(animeunity): reorganize extraction logic and update mapper --- .../provider/anime/animeunity/extractor.py | 50 +++++++++++++++++++ .../libs/provider/anime/animeunity/mappers.py | 6 ++- .../provider/anime/animeunity/provider.py | 31 +----------- 3 files changed, 57 insertions(+), 30 deletions(-) create mode 100644 viu_media/libs/provider/anime/animeunity/extractor.py diff --git a/viu_media/libs/provider/anime/animeunity/extractor.py b/viu_media/libs/provider/anime/animeunity/extractor.py new file mode 100644 index 0000000..b5d37bc --- /dev/null +++ b/viu_media/libs/provider/anime/animeunity/extractor.py @@ -0,0 +1,50 @@ +import logging + +from .constants import ( + DOWNLOAD_FILENAME_REGEX, + DOWNLOAD_URL_REGEX, + QUALITY_REGEX, + VIDEO_INFO_CLEAN_REGEX, + VIDEO_INFO_REGEX, +) + +logger = logging.getLogger(__name__) + + +def extract_server_info(html_content: str, episode_title: str) -> dict | None: + """ + Extracts server information from the VixCloud/AnimeUnity embed page. + Handles extraction from both window.video object and download URL. + """ + video_info = VIDEO_INFO_REGEX.search(html_content) + download_url_match = DOWNLOAD_URL_REGEX.search(html_content) + + if not (download_url_match and video_info): + return None + + info_str = VIDEO_INFO_CLEAN_REGEX.sub(r'"\1"', video_info.group(1)) + + # Use eval context for JS constants + ctx = {"null": None, "true": True, "false": False} + try: + info = eval(info_str, ctx) + except Exception as e: + logger.error(f"Failed to parse JS object: {e}") + return None + + download_url = download_url_match.group(1) + info["link"] = download_url + + # Extract metadata from download URL if missing in window.video + if filename_match := DOWNLOAD_FILENAME_REGEX.search(download_url): + info["name"] = filename_match.group(1) + else: + info["name"] = f"{episode_title} - Unknown" + + if quality_match := QUALITY_REGEX.search(download_url): + # "720p" -> 720 + info["quality"] = int(quality_match.group(1)[:-1]) + else: + info["quality"] = 0 # Fallback + + return info diff --git a/viu_media/libs/provider/anime/animeunity/mappers.py b/viu_media/libs/provider/anime/animeunity/mappers.py index e3b5a34..cd52b86 100644 --- a/viu_media/libs/provider/anime/animeunity/mappers.py +++ b/viu_media/libs/provider/anime/animeunity/mappers.py @@ -99,7 +99,11 @@ def map_to_server( translation_type=MediaTranslationType(translation_type), mp4=True, ) - for quality in AVAILABLE_VIDEO_QUALITY + for quality in sorted( + list(set(AVAILABLE_VIDEO_QUALITY + [str(info["quality"])])), + key=lambda x: int(x), + reverse=True, + ) if int(quality) <= info["quality"] ], episode_title=episode.title, diff --git a/viu_media/libs/provider/anime/animeunity/provider.py b/viu_media/libs/provider/anime/animeunity/provider.py index 3976545..fed1b47 100644 --- a/viu_media/libs/provider/anime/animeunity/provider.py +++ b/viu_media/libs/provider/anime/animeunity/provider.py @@ -8,15 +8,11 @@ from ..types import Anime, AnimeEpisodeInfo, SearchResult, SearchResults from ..utils.debug import debug_provider from .constants import ( ANIMEUNITY_BASE, - DOWNLOAD_FILENAME_REGEX, - DOWNLOAD_URL_REGEX, MAX_TIMEOUT, - QUALITY_REGEX, REPLACEMENT_WORDS, TOKEN_REGEX, - VIDEO_INFO_CLEAN_REGEX, - VIDEO_INFO_REGEX, ) +from .extractor import extract_server_info from .mappers import ( map_to_anime_result, map_to_search_result, @@ -161,33 +157,10 @@ class AnimeUnity(BaseAnimeProvider): video_response = self.client.get(url=response.text.strip(), timeout=MAX_TIMEOUT) video_response.raise_for_status() - video_info = VIDEO_INFO_REGEX.search(video_response.text) - download_url_match = DOWNLOAD_URL_REGEX.search(video_response.text) - if not (download_url_match and video_info): + if not (info := extract_server_info(video_response.text, episode.title)): logger.error(f"Failed to extract video info for episode {episode.id}") return None - info_str = VIDEO_INFO_CLEAN_REGEX.sub(r'"\1"', video_info.group(1)) - - # Use eval context for JS constants - ctx = {"null": None, "true": True, "false": False} - info = eval(info_str, ctx) - - download_url = download_url_match.group(1) - info["link"] = download_url - - # Extract metadata from download URL if missing in window.video - if filename_match := DOWNLOAD_FILENAME_REGEX.search(download_url): - info["name"] = filename_match.group(1) - else: - info["name"] = f"{episode.title} - Unknown" - - if quality_match := QUALITY_REGEX.search(download_url): - # "720p" -> 720 - info["quality"] = int(quality_match.group(1)[:-1]) - else: - info["quality"] = 0 # Fallback - yield map_to_server(episode, info, params.translation_type) From 5fe59e1ddbb41c800232c12cf3ba5415d712bfcf Mon Sep 17 00:00:00 2001 From: axtrat Date: Sat, 13 Dec 2025 21:25:12 +0100 Subject: [PATCH 3/3] fix: fixed pyright error --- viu_media/libs/provider/anime/animeunity/extractor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viu_media/libs/provider/anime/animeunity/extractor.py b/viu_media/libs/provider/anime/animeunity/extractor.py index b5d37bc..672e602 100644 --- a/viu_media/libs/provider/anime/animeunity/extractor.py +++ b/viu_media/libs/provider/anime/animeunity/extractor.py @@ -11,7 +11,7 @@ from .constants import ( logger = logging.getLogger(__name__) -def extract_server_info(html_content: str, episode_title: str) -> dict | None: +def extract_server_info(html_content: str, episode_title: str | None) -> dict | None: """ Extracts server information from the VixCloud/AnimeUnity embed page. Handles extraction from both window.video object and download URL. @@ -39,7 +39,7 @@ def extract_server_info(html_content: str, episode_title: str) -> dict | None: if filename_match := DOWNLOAD_FILENAME_REGEX.search(download_url): info["name"] = filename_match.group(1) else: - info["name"] = f"{episode_title} - Unknown" + info["name"] = f"{episode_title or 'Unknown'}" if quality_match := QUALITY_REGEX.search(download_url): # "720p" -> 720