# 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