feat: Add series TV support with Vidzy HLS downloads and duplicate prevention
Major improvements: - Series TV support via FS7 provider with dedicated search endpoint - Vidzy downloader now uses Playwright for JS obfuscation and ffmpeg for HLS streams - Episode filenames properly named (Series Title - Episode X) instead of master.m3u8.mp4 - Duplicate download prevention: checks existing tasks before creating new ones - Removed host preference system in favor of intelligent URL-based detection Technical changes: - Vidzy: Added Playwright extraction and M3U8→MP4 conversion with ffmpeg - FS7: Episodes now use pipe format (video_url|series_url|episode_title) - DownloadManager: Extract target_filename from pipe URL and prevent duplicates - UI: New Series tab with search, recommendations, and releases sections - Anime-Sama: Removed hardcoded host preferences, uses site's URL order Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -89,6 +89,10 @@ class FS7Downloader(BaseSeriesSite):
|
||||
continue
|
||||
title = text
|
||||
|
||||
# Clean up title: remove "affiche" suffix and clean extra whitespace
|
||||
title = re.sub(r'\s+affiche$', '', title, flags=re.IGNORECASE).strip()
|
||||
title = re.sub(r'\s+', ' ', title) # Normalize whitespace
|
||||
|
||||
# Extract cover image
|
||||
img = item.find('img')
|
||||
cover_image = img.get('src', '') if img else ''
|
||||
@@ -135,6 +139,12 @@ class FS7Downloader(BaseSeriesSite):
|
||||
soup = BeautifulSoup(html, 'lxml')
|
||||
episodes = []
|
||||
|
||||
# Get series title for episode naming
|
||||
title_elem = soup.find('h1')
|
||||
series_title = title_elem.get_text(strip=True) if title_elem else "Series"
|
||||
# Clean up title: remove "affiche" suffix
|
||||
series_title = re.sub(r'\s+affiche$', '', series_title, flags=re.IGNORECASE).strip()
|
||||
|
||||
# FS7 stores episode data in JavaScript div elements
|
||||
# Format: <div data-ep="1" data-vidzy="..." data-uqload="..." data-netu="..." data-voe="..."></div>
|
||||
episode_divs = soup.find_all('div', attrs={'data-ep': True})
|
||||
@@ -144,17 +154,28 @@ class FS7Downloader(BaseSeriesSite):
|
||||
|
||||
# Try different video players in order of preference
|
||||
video_url = None
|
||||
host_name = None
|
||||
for player in ['data-vidzy', 'data-uqload', 'data-voe', 'data-netu']:
|
||||
player_url = div.get(player, '').strip()
|
||||
if player_url:
|
||||
video_url = player_url
|
||||
logger.debug(f"Found episode {ep_num} on {player}")
|
||||
# Extract host name from attribute name
|
||||
host_name = player.replace('data-', '').title()
|
||||
logger.debug(f"Found episode {ep_num} on {host_name}")
|
||||
break
|
||||
|
||||
if video_url and ep_num:
|
||||
# Create episode title for filename
|
||||
episode_title = f"{series_title} - Episode {ep_num}"
|
||||
|
||||
# Use pipe-separated format: video_url|anime_url|episode_title
|
||||
combined_url = f"{video_url}|{anime_url}|{episode_title}"
|
||||
|
||||
episodes.append({
|
||||
'episode': ep_num,
|
||||
'url': video_url
|
||||
'url': combined_url,
|
||||
'title': episode_title,
|
||||
'host': host_name or 'Unknown'
|
||||
})
|
||||
|
||||
# Sort by episode number
|
||||
@@ -193,6 +214,9 @@ class FS7Downloader(BaseSeriesSite):
|
||||
title = soup.find('h1')
|
||||
title = title.get_text(strip=True) if title else "Unknown"
|
||||
|
||||
# Clean up title: remove "affiche" suffix
|
||||
title = re.sub(r'\s+affiche$', '', title, flags=re.IGNORECASE).strip()
|
||||
|
||||
# Extract description/synopsis
|
||||
description_elem = soup.find('div', class_='full-text')
|
||||
description = description_elem.get_text(strip=True) if description_elem else ""
|
||||
|
||||
Reference in New Issue
Block a user