4d280b5239
- Added new video players: Vidzy, LuLuvid, Uqload - Added new anime site: French-Manga - Added new series sites category with FS7 - Updated documentation to reflect three-tier architecture (anime sites → series sites → video players) - Added BaseSeriesSite interface documentation - Added "Adding New Series Site" section - Updated test organization with test_french_manga.py Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
"""Vidzy video hosting service downloader"""
|
|
import logging
|
|
from typing import Optional
|
|
from .base import BaseVideoPlayer
|
|
from bs4 import BeautifulSoup
|
|
from app.utils import sanitize_filename
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class VidzyDownloader(BaseVideoPlayer):
|
|
"""
|
|
Downloader for Vidzy video hosting service.
|
|
|
|
Vidzy is a video hosting platform used by various anime streaming sites.
|
|
"""
|
|
|
|
def can_handle(self, url: str) -> bool:
|
|
"""Check if this downloader can handle the given URL"""
|
|
return "vidzy" in url.lower()
|
|
|
|
async def get_download_link(
|
|
self,
|
|
url: str,
|
|
target_filename: Optional[str] = None
|
|
) -> tuple[str, str]:
|
|
"""
|
|
Extract direct download link and filename from Vidzy URL.
|
|
|
|
Args:
|
|
url: The Vidzy video player URL
|
|
target_filename: Optional filename override
|
|
|
|
Returns:
|
|
Tuple of (download_url, filename)
|
|
"""
|
|
try:
|
|
logger.info(f"Fetching Vidzy URL: {url}")
|
|
|
|
# Fetch the page
|
|
response = await self.client.get(url)
|
|
response.raise_for_status()
|
|
html = response.text
|
|
|
|
soup = BeautifulSoup(html, 'lxml')
|
|
|
|
# Method 1: Look for video source in <video> tag
|
|
video_tag = soup.find('video')
|
|
if video_tag and video_tag.get('src'):
|
|
download_url = video_tag['src']
|
|
logger.info(f"Found video source from <video> tag")
|
|
else:
|
|
# Method 2: Look for source in <source> tag
|
|
source_tag = soup.find('source')
|
|
if source_tag and source_tag.get('src'):
|
|
download_url = source_tag['src']
|
|
logger.info(f"Found video source from <source> tag")
|
|
else:
|
|
# Method 3: Look for video URL in JavaScript
|
|
# Vidzy often stores the video URL in a JavaScript variable
|
|
scripts = soup.find_all('script')
|
|
for script in scripts:
|
|
if script.string:
|
|
# Look for patterns like 'file:"URL"' or 'file: "URL"'
|
|
import re
|
|
patterns = [
|
|
r'file\s*:\s*["\']([^"\']+\.mp4[^"\']*)["\']',
|
|
r'source\s*:\s*["\']([^"\']+\.mp4[^"\']*)["\']',
|
|
r'videoUrl\s*:\s*["\']([^"\']+)["\']',
|
|
r'"url"\s*:\s*["\']([^"\']+\.mp4[^"\']*)["\']',
|
|
]
|
|
for pattern in patterns:
|
|
match = re.search(pattern, script.string)
|
|
if match:
|
|
download_url = match.group(1)
|
|
logger.info(f"Found video source from JavaScript")
|
|
break
|
|
if 'download_url' in locals():
|
|
break
|
|
|
|
if 'download_url' not in locals():
|
|
raise ValueError("Could not find video URL in page")
|
|
|
|
# Ensure URL is absolute
|
|
if not download_url.startswith('http'):
|
|
if download_url.startswith('//'):
|
|
download_url = 'https:' + download_url
|
|
else:
|
|
from urllib.parse import urljoin
|
|
download_url = urljoin(url, download_url)
|
|
|
|
# Generate filename
|
|
if target_filename:
|
|
filename = sanitize_filename(target_filename)
|
|
else:
|
|
# Try to extract filename from URL
|
|
filename = download_url.split('/')[-1].split('?')[0]
|
|
if not filename or len(filename) < 5:
|
|
filename = "vidzy_video.mp4"
|
|
filename = sanitize_filename(filename)
|
|
|
|
# Ensure .mp4 extension
|
|
if not filename.endswith('.mp4'):
|
|
filename += '.mp4'
|
|
|
|
logger.info(f"Successfully extracted Vidzy download link: {filename}")
|
|
return download_url, filename
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error extracting Vidzy download link: {e}")
|
|
raise ValueError(f"Failed to extract download link from Vidzy: {str(e)}")
|