23 KiB
Anime-Sama Player Fallback System
TL;DR
Quick Summary: Implement automatic player fallback for Anime-Sama downloads to handle cases where the detected player fails
Deliverables:
get_download_link_with_fallback()method in AnimeSamaDownloader- Player success validation via chunk download test
- Player caching for performance optimization
Estimated Effort: Medium Parallel Execution: NO - sequential implementation Critical Path: Test implementation → AnimeSamaDownloader update → Integration testing
Context
Original Request
User requested a new feature for Anime-Sama provider: ability to change video player on the site. When a player (like Lpayer) fails, the downloader should automatically test different players until finding one that works.
Interview Summary
Key Decisions:
- Mode: Automatic - if Lpayer fails, try VidMoly, SendVid, Sibnet, etc. automatically
- Success Criterion: Download test (download first 10KB chunk to verify URL works)
- Priority Order: VidMoly → SendVid → Sibnet → Lpayer
Technical Requirements:
- Test video URL by downloading small chunk (10KB)
- If successful, consider player working
- Cache working player per anime/series for future episodes
- Automatic retry without user intervention
Work Objectives
Core Objective
Implement automatic player fallback in Anime-Sama downloader to handle failed extractions by trying alternative players sequentially.
Concrete Deliverables
AnimeSamaDownloader.get_download_link_with_fallback()- Main fallback method_test_video_url()- Helper to validate video URL by downloading chunk- Player priority list with caching mechanism
- Updated
get_download_link()to use fallback by default
Definition of Done
- Fallback method tries players in priority order
- Video URL validated before returning (10KB download test)
- Working player cached per anime for performance
- All existing Anime-Sama functionality preserved
Must Have
- Players tested sequentially: VidMoly → SendVid → Sibnet → Lpayer
- Success detection via HTTP 200 + valid data download (10KB chunk)
- Cache mechanism to avoid re-testing for same anime
- Automatic integration with existing download flow
Must NOT Have (Guardrails)
- NO frontend changes required (backend-only implementation)
- NO manual player selection via API (automatic only)
- NO changes to other anime sites (Anime-Sama only)
- NO breaking changes to existing Anime-Sama functionality
Verification Strategy (MANDATORY)
ZERO HUMAN INTERVENTION — ALL verification is agent-executed. No exceptions.
Test Decision
- Infrastructure exists: YES
- Automated tests: Tests-after (unit tests for fallback logic)
- Framework: pytest
QA Policy
Every task MUST include agent-executed QA scenarios (see TODO template below).
- Unit Tests: pytest with mocked HTTP clients
- Integration Tests: Test with real Anime-Sama URLs
- Edge Cases: All players failing, first player working, cache invalidation
Execution Strategy
Sequential Implementation
Since this is a focused feature on a single file, implementation will be sequential:
Step 1: Add URL validation helper (can test independently)
→ _test_video_url(url) method
Step 2: Implement fallback logic
→ get_download_link_with_fallback() method
Step 3: Integrate with existing flow
→ Update get_download_link() to use fallback
Step 4: Add unit tests
→ Test fallback logic and URL validation
Step 5: Integration testing
→ Test with real Anime-Sama URLs (Frieren S2 E1)
Dependency Matrix
- 1: — 2
- 2: — 3
- 3: — 4, 5
- 4: — 5
- 5: Final
Agent Dispatch Summary
- 1:
quick- Helper method - 2:
quick- Main fallback logic - 3:
quick- Integration - 4:
quick- Unit tests - 5:
quick- Integration testing
TODOs
-
1. Add video URL validation helper method
What to do:
- Add
_test_video_url(url: str) -> boolmethod to AnimeSamaDownloader - Download first 10KB of video using self.client
- Return True if HTTP 200 and valid data received, False otherwise
- Include timeout handling (10 seconds for 10KB)
- Log validation results for debugging
Must NOT do:
- Download entire video
- Change existing player extraction logic
Recommended Agent Profile:
Category:
quick- Reason: Simple helper method, focused task
- Skills: None needed
- Skills Evaluated but Omitted:
- No additional skills needed
Parallelization:
- Can Run In Parallel: NO - Sequential
- Parallel Group: Sequential
- Blocks: Task 2
- Blocked By: None
References (CRITICAL):
The executor has NO context from your interview. References are their ONLY guide. Each reference must answer: "What should I look at and WHY?"
Pattern References (existing code to follow):
app/downloaders/anime_sites/animesama.py:120-150- Existing video URL extraction methodsapp/downloaders/anime_sites/animesama.py:402-445- Existing Lplayer extraction pattern
API/Type References (contracts to implement against):
httpx.AsyncClient.stream()- For downloading chunks efficiently
External References (libraries and frameworks):
- httpx docs:
https://www.python-httpx.org/advanced/#streaming-responses- Chunked downloads
WHY Each Reference Matters:
- Existing extraction methods show how video URLs are currently handled
- httpx streaming allows efficient chunk download without loading full video
Acceptance Criteria:
AGENT-EXECUTABLE VERIFICATION ONLY — No human action permitted. Every criterion MUST be verifiable by running a command or using a tool.
_test_video_url()method added to AnimeSamaDownloader- Downloads first 10KB chunk with 10s timeout
- Returns True if HTTP 200 and data > 0 bytes
- Returns False if timeout, error, or empty response
- Logs validation results (success/failure)
QA Scenarios (MANDATORY — task is INCOMPLETE without these):
Scenario: Valid video URL returns 200 OK Tool: Bash (python3) Preconditions: Mock a video URL that returns 200 with data Steps: 1. python3 -c "from app.downloaders.anime_sites.animesama import AnimeSamaDownloader; d = AnimeSamaDownloader(); result = d._test_video_url('https://example.com/video.mp4'); print(f'Result: {result}')" Expected Result: Returns True Evidence: .sisyphus/evidence/task-1-valid-url.txt Scenario: Invalid video URL times out Tool: Bash (python3) Preconditions: Mock a video URL that times out Steps: 1. python3 -c "from app.downloaders.anime_sites.animesama import AnimeSamaDownloader; d = AnimeSamaDownloader(); result = d._test_video_url('https://httpbin.org/delay/20'); print(f'Result: {result}')" Expected Result: Returns False (timeout) Evidence: .sisyphus/evidence/task-1-timeout-url.txtEvidence to Capture:
- Each evidence file named: task-{N}-{scenario-slug}.txt
- Contains test results with True/False output
Commit: YES
- Message:
feat(anime-sama): add video URL validation helper method - Files:
app/downloaders/anime_sites/animesama.py
- Add
-
2. Implement player fallback logic with priority list
What to do:
- Add
get_download_link_with_fallback(url, target_filename=None, anime_page_url=None, episode_title=None)method - Define player priority list: ['vidmoly', 'sendvid', 'sibnet', 'lpayer']
- For each player in priority order:
- Try existing extraction methods (_extract_from_vidmoly, etc.)
- If extraction succeeds, validate URL with _test_video_url()
- If validation succeeds, return (video_url, filename)
- Add player caching:
self._working_players = {}dict to cache working player per anime URL - If cached player exists for anime, try it first
- Log each attempted player with success/failure
Must NOT do:
- Modify existing extract_from* methods
- Break existing Anime-Sama download flow
Recommended Agent Profile:
Category:
quick- Reason: Sequential logic implementation, clear requirements
- Skills: None needed
- Skills Evaluated but Omitted:
- No additional skills needed
Parallelization:
- Can Run In Parallel: NO - Depends on Task 1
- Parallel Group: Sequential
- Blocks: Task 3
- Blocked By: Task 1
References (CRITICAL):
Pattern References (existing code to follow):
app/downloaders/anime_sites/animesama.py:95-170- VidMoly extraction patternapp/downloaders/anime_sites/animesama.py:280-320- SendVid extraction patternapp/downloaders/anime_sites/animesama.py:250-280- Sibnet extraction patternapp/downloaders/anime_sites/animesama.py:402-445- Lpayer extraction patternapp/downloaders/anime_sites/animesama.py:117-120- Player detection logic
WHY Each Reference Matters:
- Existing extraction methods show the interface each player uses
- Player detection logic shows how to identify which player URL to extract
- Need to understand the signature of each extraction method
Acceptance Criteria:
get_download_link_with_fallback()method added- Player priority list defined: vidmoly → sendvid → sibnet → lpayer
- Tries each player in order if previous fails
- Validates video URL with _test_video_url() before returning
- Caches working player per anime_page_url
- Logs each player attempt (success/failure)
- Returns (video_url, filename) on first success
- Raises exception if all players fail
QA Scenarios (MANDATORY — task is INCOMPLETE without these):
Scenario: First player (VidMoly) works Tool: Bash (python3) Preconditions: Mock VidMoly URL that passes validation Steps: 1. python3 -c " - Add
from app.downloaders.anime_sites.animesama import AnimeSamaDownloader d = AnimeSamaDownloader()
Mock _test_video_url to return True
original_test = d._test_video_url d._test_video_url = lambda url: True
Call fallback
video_url, filename = d.get_download_link_with_fallback('test_url|anime_page|Ep1', episode_title='Episode 1') print(f'Video URL: {video_url[:50] if video_url else None}') print(f'Filename: {filename}') " Expected Result: Returns VidMoly URL, logs "VidMoly player succeeded" Evidence: .sisyphus/evidence/task-2-first-works.txt
Scenario: First player fails, second works Tool: Bash (python3) Preconditions: Mock VidMoly to fail, SendVid to succeed Steps: 1. python3 -c " from app.downloaders.anime_sites.animesama import AnimeSamaDownloader d = AnimeSamaDownloader() call_count = [0] def mock_extract(*args, **kwargs): call_count[0] += 1 if call_count[0] == 1: # VidMoly call raise Exception('VidMoly failed') elif call_count[0] == 2: # SendVid call return ('https://sendvid.com/video.mp4', 'sendvid_video.mp4')
Mock extraction methods
d._extract_from_vidmoly = lambda *a, **kw: mock_extract(*a, **kw) d._extract_from_sendvid = lambda *a, **kw: mock_extract(*a, **kw) d._test_video_url = lambda url: True
Call fallback
video_url, filename = d.get_download_link_with_fallback('test_url|anime_page|Ep1') print(f'Video URL: {video_url}') print(f'Used player: {"SendVid" if "sendvid" in video_url else "Unknown"}') " Expected Result: Returns SendVid URL (VidMoly failed, SendVid succeeded) Evidence: .sisyphus/evidence/task-2-second-works.txt
Scenario: All players fail Tool: Bash (python3) Preconditions: Mock all extractions to fail Steps: 1. python3 -c " from app.downloaders.anime_sites.animesama import AnimeSamaDownloader d = AnimeSamaDownloader() def mock_fail(*args, **kwargs): raise Exception('Player failed')
Mock all extraction methods
d._extract_from_vidmoly = mock_fail d._extract_from_sendvid = mock_fail d._extract_from_sibnet = mock_fail d._extract_from_lpayer = mock_fail
Call fallback
try: video_url, filename = d.get_download_link_with_fallback('test_url|anime_page|Ep1') print('ERROR: Should have raised exception') except Exception as e: print(f'Exception raised: {e}') " Expected Result: Raises exception "All video players failed" Evidence: .sisyphus/evidence/task-2-all-fail.txt
**Evidence to Capture**:
- [ ] Each evidence file contains video URL and logs output
- [ ] Test confirms fallback logic works correctly
**Commit**: YES
- Message: `feat(anime-sama): add player fallback logic with priority retry`
- Files: `app/downloaders/anime_sites/animesama.py`
---
- [x] 3. Integrate fallback into existing get_download_link() method
**What to do**:
- Update `get_download_link()` to use `get_download_link_with_fallback()` by default
- Maintain backward compatibility: if direct video URL detected, skip fallback
- Pass anime_page_url and episode_title from pipe-separated URL format
- Keep existing player detection and direct extraction flow for simple cases
**Must NOT do**:
- Remove existing extraction methods
- Change existing player detection logic
**Recommended Agent Profile**:
> **Category**: `quick`
- Reason: Integration task, minimal changes
- **Skills**: None needed
- **Skills Evaluated but Omitted**:
- No additional skills needed
**Parallelization**:
- **Can Run In Parallel**: NO - Depends on Task 2
- **Parallel Group**: Sequential
- **Blocks**: Task 4, 5
- **Blocked By**: Task 2
**References** (CRITICAL):
**Pattern References** (existing code to follow):
- `app/downloaders/anime_sites/animesama.py:93-120` - Current get_download_link implementation
**WHY Each Reference Matters**:
- Need to understand current logic to integrate fallback without breaking it
- Player detection and pipe URL parsing must be preserved
**Acceptance Criteria**:
- [ ] `get_download_link()` calls `get_download_link_with_fallback()` for complex URLs
- [ ] Direct video URLs (no pipe format) skip fallback (performance)
- [ ] Pipe-separated URLs trigger fallback with anime_page_url and episode_title
- [ ] Existing Anime-Sama functionality preserved (VidMoly, SendVid, Sibnet, Lpayer)
- [ ] Backward compatible with existing download flow
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
Scenario: Pipe URL triggers fallback Tool: Bash (python3) Preconditions: Anime-Sama downloader with fallback method Steps: 1. python3 -c " from app.downloaders.anime_sites.animesama import AnimeSamaDownloader d = AnimeSamaDownloader()
Mock to test that fallback is called
fallback_called = [False] original_fallback = d.get_download_link_with_fallback def mock_fallback(*args, **kwargs): fallback_called[0] = True return original_fallback(*args, **kw) d.get_download_link_with_fallback = mock_fallback
Call with pipe URL
d.get_download_link('https://vidmoly.to/vid|https://anime-sama.si/cat/naruto/s1|Episode+1') print(f'Fallback called: {fallback_called[0]}') " Expected Result: Fallback method is called (True) Evidence: .sisyphus/evidence/task-3-pipe-url.txt
Scenario: Direct video URL skips fallback Tool: Bash (python3) Preconditions: Anime-Sama downloader with fallback method Steps: 1. python3 -c " from app.downloaders.anime_sites.animesama import AnimeSamaDownloader d = AnimeSamaDownloader()
Mock to test that fallback is NOT called
fallback_called = [False] def mock_fallback(*args, **kwargs): fallback_called[0] = True return ('https://video.mp4', 'video.mp4') d.get_download_link_with_fallback = mock_fallback
Call with direct URL (no pipe)
d.get_download_link('https://vidmoly.to/vid') print(f'Fallback called: {fallback_called[0]}') " Expected Result: Fallback method is NOT called (False) - direct extraction used Evidence: .sisyphus/evidence/task-3-direct-url.txt
**Evidence to Capture**:
- [ ] Evidence files show fallback called/not-called correctly
- [ ] Integration preserves existing functionality
**Commit**: YES
- Message: `feat(anime-sama): integrate fallback into get_download_link()`
- Files: `app/downloaders/anime_sites/animesama.py`
---
- [x] 4. Add unit tests for fallback logic
**What to do**:
- Create `tests/test_anime_sama_fallback.py`
- Test 1: Fallback tries players in priority order
- Test 2: Caching mechanism stores working player
- Test 3: All players failing raises exception
- Test 4: _test_video_url() returns True/False correctly
- Use pytest with mock_httpx_client fixture
**Must NOT do**:
- Make real HTTP requests in tests (use mocks)
- Test other anime sites (Anime-Sama only)
**Recommended Agent Profile**:
> **Category**: `quick`
- Reason: Unit tests are straightforward
- **Skills**: None needed
- **Skills Evaluated but Omitted**:
- No additional skills needed
**Parallelization**:
- **Can Run In Parallel**: NO - Depends on Task 3
- **Parallel Group**: Sequential
- **Blocks**: Task 5
- **Blocked By**: Task 3
**References** (CRITICAL):
**Test References** (testing patterns to follow):
- `tests/test_downloaders.py:40-70` - Mock pattern for downloaders
- `tests/conftest.py:40-50` - Mock HTTP client fixture
**WHY Each Reference Matters**:
- Mocking patterns show how to simulate HTTP responses without network calls
- Conftest fixtures provide reusable test setup
**Acceptance Criteria**:
- [ ] `tests/test_anime_sama_fallback.py` file created
- [ ] Test priority order: VidMoly → SendVid → Sibnet → Lpayer
- [ ] Test caching: working player reused for same anime
- [ ] Test _test_video_url: returns True/False correctly
- [ ] Test all players fail: exception raised
- [ ] All tests pass with pytest
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
Scenario: Run all fallback unit tests Tool: Bash Preconditions: Tests implemented in test_anime_sama_fallback.py Steps: 1. pytest tests/test_anime_sama_fallback.py -v --tb=short Expected Result: All tests pass Failure Indicators: Any test fails, pytest exit code non-zero Evidence: .sisyphus/evidence/task-4-tests-run.txt
**Evidence to Capture**:
- [ ] pytest output shows all tests passed
- [ ] Evidence file contains test summary
**Commit**: YES
- Message: `test(anime-sama): add unit tests for player fallback logic`
- Files: `tests/test_anime_sama_fallback.py`
---
- [x] 5. Integration testing with real Anime-Sama URLs
**What to do**:
- Test Frieren S2 E1 download with fallback enabled
- Verify that fallback tries multiple players if first fails
- Check logs to see which player succeeded
- Validate that downloaded video is playable
- Test with different Anime-Sama URLs to ensure general functionality
**Must NOT do**:
- Only test with Frieren (test variety)
- Modify production code during testing
**Recommended Agent Profile**:
> **Category**: `quick`
- Reason: Integration testing with real data
- **Skills**: `playwright` (may be needed for Lpayer)
- **Skills Evaluated but Omitted**:
- `git-master`: Not needed for testing
**Parallelization**:
- **Can Run In Parallel**: NO - Depends on Task 4
- **Parallel Group**: Sequential
- **Blocks**: Final Verification
- **Blocked By**: Task 4
**References** (CRITICAL):
**API/Type References** (contracts to implement against):
- `/api/anime/download` - Download endpoint
- `/api/downloads` - List downloads endpoint
**WHY Each Reference Matters**:
- Need to know how to trigger downloads and check status
**Acceptance Criteria**:
- [ ] Frieren S2 E1 download completes successfully
- [ ] Logs show multiple players tried if first fails
- [ ] Downloaded video file is valid (not empty, correct extension)
- [ ] Fallback logic works without errors
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
Scenario: Download Frieren S2 E1 with fallback Tool: Bash (curl) + Playwright Preconditions: Server running, fallback implemented Steps: 1. curl -s "http://localhost:3000/api/anime/episodes?url=https://anime-sama.si/catalogue/frieren-s1/vostfr/&lang=vostfr" | python3 -m json.tool 2. Extract first episode URL 3. curl -X POST "http://localhost:3000/api/anime/download" -H "Content-Type: application/json" -d '{"url": "EPISODE_URL|PAGE_URL|Episode+1"}' 4. curl -s "http://localhost:3000/api/downloads" | python3 -m json.tool 5. Wait for download to complete (status COMPLETED) 6. ls -lh downloads/Frieren*.mp4 2>&1 Expected Result: Download completes with status COMPLETED, video file exists with > 1MB Failure Indicators: Status FAILED, no video file, file size < 1MB Evidence: .sisyphus/evidence/task-5-frieren-download.txt
**Evidence to Capture**:
- [ ] Evidence file contains download status
- [ ] Video file exists and is playable
**Commit**: YES (if successful)
- Message: `test(anime-sama): verify fallback works with Frieren S2 E1`
- Files: `downloads/` (test artifacts)
---
## Final Verification Wave
- [ ] F1. **Unit Test Coverage** — `pytest`
Run pytest on anime-sama tests to ensure fallback logic is covered.
- Run: `pytest tests/test_anime_sama_fallback.py -v --cov=app.downloaders.anime_sites.animesama`
- Verify: All tests pass, coverage > 80% for new methods
Output: `Tests [N/N pass] | Coverage [%] | VERDICT`
- [ ] F2. **Real Download Test** — `curl` + `Bash`
Test actual download with Anime-Sama fallback enabled.
- Trigger: Download Frieren S2 E1 via API
- Verify: Download completes, fallback logs visible, file valid
Output: `Download [COMPLETE/FAILED] | Player [name] | File [size] | VERDICT`
- [ ] F3. **Log Analysis** — `Bash`
Check server logs for fallback behavior.
- Run: `tail -100 /tmp/uvicorn.log | grep -E "(LPAYER|fallback|player)"`
- Verify: Multiple player attempts logged when first fails
Output: `Attempts [N] | Success [True/False] | VERDICT`
- [ ] F4. **No Regressions** — `pytest`
Ensure existing Anime-Sama functionality still works.
- Run: `pytest tests/test_anime_sama.py -v -k "not fallback"`
- Verify: All existing tests pass
Output: `Tests [N/N pass] | VERDICT`
---
## Commit Strategy
- **1**: `feat(anime-sama): add video URL validation helper method` — `app/downloaders/anime_sites/animesama.py`
- **2**: `feat(anime-sama): add player fallback logic with priority retry` — `app/downloaders/anime_sites/animesama.py`
- **3**: `feat(anime-sama): integrate fallback into get_download_link()` — `app/downloaders/anime_sites/animesama.py`
- **4**: `test(anime-sama): add unit tests for player fallback logic` — `tests/test_anime_sama_fallback.py`
- **5**: `test(anime-sama): verify fallback works with real downloads` — `downloads/` (test artifacts)
---
## Success Criteria
### Verification Commands
```bash
# Unit tests
pytest tests/test_anime_sama_fallback.py -v
# Integration test
curl -X POST "http://localhost:3000/api/anime/download" \
-H "Content-Type: application/json" \
-d '{"url": "URL|PAGE|TITLE"}'
# Check logs
tail -50 /tmp/uvicorn.log | grep fallback
Final Checklist
- Fallback method tries players in priority order
- Video URLs validated before returning (10KB download test)
- Working player cached per anime for performance
- All unit tests pass
- Real download test succeeds
- No regressions in existing Anime-Sama functionality
- All "Must Have" present
- All "Must NOT Have" absent