# WB Video Upload Analysis

Methodology and results for measuring how many users upload our generated videos to their Wildberries product cards.

## Goal

Determine the **upload conversion rate**: what percentage of successfully generated videos are actually uploaded by sellers to their WB product listings.

## Methodology

### Step 1: Identify Target Products

Query the production database for all unique product IDs with successful generations by real users (excluding admin/test accounts):

```sql
SELECT DISTINCT t.product_id
FROM transactions t
WHERE t.type = 'generation'
  AND t.product_id IS NOT NULL AND t.product_id != ''
  AND t.user_id NOT IN (100000001, 100000002, 100000003)  -- admin/test
  AND NOT EXISTS (
      SELECT 1 FROM transactions r
      WHERE r.type = 'refund' AND r.task_id = t.task_id
  )
```

**Result**: 452 unique products (as of Feb 16, 2026).

### Step 2: Check Video Presence via Sellematics

Script: `scripts/check_wb_videos.py`

For each product, query the Sellematics API (`/api/v1/products`) which tracks WB product content including video count and video URLs.

**Result**: 221 out of 452 products have video on WB (per Sellematics data).

### Step 3: Date Correlation Analysis

Script: `scripts/check_wb_videos_full.py`

For each product with video, determine whether the video was uploaded AFTER our generation (i.e., it's likely ours) or was already present before.

**Data source**: Sellematics content history API (`POST /api/v1/archive/product/content`) returns a time series of content snapshots with timestamps, tracking when the video count changes from 0 to 1.

**Classification rules**:
- **CONFIRMED_OURS**: Video count went 0→1 within 3 days of our generation
- **LIKELY_OURS**: Video count went 0→1 within 3-7 days of our generation
- **PRE_EXISTING**: Video was already present before our generation date
- **UNCLEAR**: Insufficient history data or ambiguous timing

All WB videos are also downloaded to `/mnt/video/wb_{product_id}/downloaded-video/` for duration analysis.

### Step 4: Verify "No Video" Products via card.json

Script: `scripts/check_wb_no_video_verify.py`

Sellematics data can be stale. To catch videos that Sellematics missed, we verify "no video" products by fetching `card.json` directly from WB CDN:

1. Get the product's image URL from Sellematics to determine the correct basket host (e.g., `basket-32.wbbasket.ru`)
2. Fetch `https://{basket_host}/vol{V}/part{P}/{nm}/info/ru/card.json`
3. Check `media.has_video` field

**Result**: Found 27 additional products with video that Sellematics missed.

### Step 5: Duration Fingerprinting

For products where Sellematics had no video URL (but card.json confirmed video exists), video URLs were obtained via Playwright page scraping or direct CDN probing.

**Key discovery — WB re-encodes uploaded videos**, consistently changing duration:

| Our video duration | WB CDN duration | Classification |
|-------------------|-----------------|----------------|
| 8.000s            | ~7.0s (6.9-7.05) | OURS (8s pattern) |
| 10.0s             | ~7.5s (7.5-7.6)  | OURS (10s pattern) |
| —                 | <6.5s           | NOT OURS |

This duration fingerprint was validated against 45 products with both date and duration data: **91% agreement** between the two signals.

### Step 6: Playwright Fallback

Script: `scripts/fetch_wb_videos_playwright.py`

For products where Sellematics had no video URL, Playwright loads the WB product page to extract `videonme-basket-*.wbbasket.ru` video URLs from the DOM or page source. Downloads and classifies by duration.

**Note**: WB aggressively blocks automated requests. Best results with:
- Fresh browser context per request
- `wait_until="domcontentloaded"` (not `networkidle`)
- 3+ second delays between requests
- webdriver property override

## WB Video URL Structure

### Images
```
https://basket-{N}.wbbasket.ru/vol{V}/part{P}/{nm}/images/big/1.webp
```
Where: `V = nm // 100000`, `P = nm // 1000`, basket N determined by WB's internal mapping.

### Videos
```
https://videonme-basket-{N}.wbbasket.ru/vol{V}/part{P}/{nm}/mp4/360p/1.mp4
```
**Important**: Video vol/part/basket are on a DIFFERENT mapping than images. The video vol is NOT `nm // 100000` — it's an internal WB video catalog ID. Video URLs cannot be constructed from the product ID alone; they must come from Sellematics or page scraping.

### card.json (metadata, no video URL)
```
https://basket-{N}.wbbasket.ru/vol{V}/part{P}/{nm}/info/ru/card.json
→ {"media": {"has_video": true, "photo_count": 4}}
```

## Results (Feb 16, 2026)

### All-Time

| Metric | Count | % of total |
|--------|-------|------------|
| Products generated (excl admins) | 452 | 100% |
| Products with video on WB | 247 | 54.6% |
| **Video is OURS** | **78** | **17.3%** |
| — Confirmed by date (≤3d) | 51 | |
| — Likely by date (3-7d) | 7 | |
| — Confirmed by duration (Sellematics lag) | 20 | |
| Pre-existing video | 164 | |
| No video on WB | 187 | |

**Conservative estimate** (only date+duration double-confirmed): 61/452 = **13.5%**

### Last Week (Feb 9-16)

| Metric | Count | % of total |
|--------|-------|------------|
| Products generated (excl admins) | 379 | 100% |
| **Video is OURS** | **63** | **16.6%** |
| — Confirmed by date | 43 | |
| — Likely by date | 1 | |
| — Confirmed by duration | 19 | |

### Key Insight

**~1 in 6 users** who generate a video actually upload it to their WB product card.

## Data Storage

Downloaded WB videos are stored alongside generated videos:
```
/mnt/video/wb_{product_id}/
├── {task_id}/videos/video.mp4          # Our generated video
└── downloaded-video/{datetime}/
    ├── video_wb_360p.mp4               # Downloaded WB video
    ├── video_1_360p.mp4                # Alternative naming (full analysis)
    ├── comparison_log.json             # Duration comparison
    └── download_log.json               # Full analysis metadata
```

## Scripts

| Script | Purpose |
|--------|---------|
| `check_wb_videos.py` | Step 1-2: Query DB + check Sellematics for video presence |
| `check_wb_videos_sample.py` | Step 3 prototype: Download sample of 20, analyze dates |
| `check_wb_videos_full.py` | Step 3: Full date correlation analysis + download all |
| `check_wb_no_video_verify.py` | Step 4: Verify "no video" via card.json |
| `fetch_wb_videos_playwright.py` | Step 6: Playwright-based video URL extraction |

All scripts require:
- Sellematics tunnel running (`sudo systemctl status sellematics-tunnel`)
- Access to prod database (`/mnt/video/videobot_prod.db`)
- `ffprobe` for duration analysis
- Playwright (for `fetch_wb_videos_playwright.py` only)

## Limitations

1. **Sellematics lag**: Content data can be hours/days behind reality. Mitigated by card.json verification.
2. **Date correlation false positives**: 4 products (9%) show date match but wrong duration — user may have uploaded and later replaced the video, or timing was coincidental.
3. **Duration fingerprint edge cases**: WB duration of 6.757s (between patterns) is ambiguous — classified as UNCLEAR.
4. **Replaced videos**: If a user uploaded our video then later replaced it with their own, we cannot detect this from current data alone.
5. **WB anti-bot**: Playwright scraping is unreliable — WB blocks after 1-2 requests from the same context.
