# 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
