Files
ohm_streaming/.sisyphus/plans/anime-sama-player-fallback.md
2026-02-28 09:22:57 +00:00

651 lines
23 KiB
Markdown

# 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
- [x] 1. Add video URL validation helper method
**What to do**:
- Add `_test_video_url(url: str) -> bool` method 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 methods
- `app/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.txt
```
**Evidence 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`
---
- [x] 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 pattern
- `app/downloaders/anime_sites/animesama.py:280-320` - SendVid extraction pattern
- `app/downloaders/anime_sites/animesama.py:250-280` - Sibnet extraction pattern
- `app/downloaders/anime_sites/animesama.py:402-445` - Lpayer extraction pattern
- `app/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 "
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