# DebridStream FireTV — Complete Image Pipeline

APK: `app-firetv-release.apk` (21MB), v3.3.1 (build 140), package `com.debridstream.tv`
Decompiled: 2026-05-30, jadx 1.5.1, 11,453 classes, 54 errors (0.5%)

## Image Normalizer (the critical piece)

**File**: `h3/u.java` (33 lines, unobfuscated)

```java
public static final S4.m f15367a = new S4.m("/(medium|large|full)/");   // regex: non-TMDB sizes
public static final S4.m f15368b = new S4.m("/(medium|large|thumb)/");  // regex: alt match
public static final S4.m f15369c = new S4.m("/t/p/[^/]+/");             // regex: TMDB path segment

public static final String a(String url, boolean isBackdrop) {
    if (url == null || url.isBlank()) return "";
    
    if (url.contains("image.tmdb.org")) {
        // Replace /t/p/{ANY_SIZE}/ with /t/p/w780/ (backdrop) or /t/p/w500/ (poster)
        return f15369c.replace(url, "/t/p/" + (isBackdrop ? "w780" : "w500") + "/");
    }
    // Non-TMDB: backdrop → passthrough, poster → /thumb/ replacement
    return isBackdrop ? url : f15367a.replace(url, "/thumb/");
}
```

**When it runs**: ONLY at JSON creation time (catalog data boundary), NOT at Coil display time. It is called explicitly in:
- `W/c.java:905` — TMDB catalog `toMeta()` → JSON
- `j3/C1809J.java:217,221,265,272,...` — Trakt/addon catalog → JSON
- `j3/C1838y.java:389,397,434,438,...` — Another catalog source → JSON

**What it handles**: TMDB path replacement, null/blank, non-TMDB CDN size replacement
**What it does NOT handle**: w342 (uses w500), query param stripping, http→https upgrade, AniList/Kitsu specific patterns

## Data Flow

```
TMDB/Trakt/Addon API → h3.m meta object
    .g = poster_path   (e.g. "/abc.jpg" or full URL)
    .h = backdrop_path
        ↓
W/c.m(meta) → JSON
    posterUrl  = h3.u.a(meta.g, isBackdrop=false)   ← NORMALIZED HERE
    backdropUrl = h3.u.a(meta.h, isBackdrop=true)
        ↓
C0682l0.d(JSON) → HomeMetaItem
    posterUrl = jo.optString("posterUrl")   ← PASSED THROUGH AS-IS
        ↓
AbstractC1548f3.java (Compose card composable)
    ImageRequest.Builder(context)
        .data(item.getPosterUrl())           ← RAW STRING, NO RE-NORMALIZATION
        .memoryCacheKey(item.getPosterUrl())
        .size(384, 576)
        .crossfade(false)
        .allowHardware(true)
        .build()
        ↓
Coil → MemoryCache(10%) → DiskCache(100MB) → OkHttp (4 max, 8 per host)
```

## Coil Config (DebridStreamApp.java:69-202)

- MemoryCache: 10% heap, weak refs enabled
- DiskCache: `coil_cache` dir, 100MB
- OkHttp: 4 maxRequests, 8 maxPerHost, `okhttp_http_cache`
- crossfade: false
- allowHardware: true
- allowRgb565: true

## Key Files for Porting to NexStream

| Reference File | What to Port |
|---|---|
| `h3/u.java` (33 lines) | Complete normalizer — replace S4.m regex with kotlin.text.Regex |
| `h3/m.java` (132 lines) | Meta data class (tmdbId, type, title, posterUrl, backdropUrl, logoUrl) |
| `W/c.java:901-946` | `toMeta()` JSON builder — calls normalizer at data boundary |
| `W2/C0682l0.java:122-199` | JSON→HomeMetaItem factory — no normalization, just passthrough |
| `i3/AbstractC1548f3.java:544-567` | Coil ImageRequest.Builder for catalog cards |
| `DebridStreamApp.java:69-202` | Coil configuration |
| `i3/n8.java:15787-15799` | Detail screen poster handling (uses `original` size, NOT the normalizer) |

## NexStream Gaps vs. This Reference

| Aspect | DebridStream | NexStream Target |
|---|---|---|
| Normalizer location | At JSON creation (data boundary) | Should match — apply once, not at display |
| TMDB row poster size | w500 | w342 (faster loads, smaller downloads) |
| TMDB backdrop size | w780 | w780 (same) |
| Trakt poster source | Trakt CDN → normalized | TMDB ID → TMDB poster (more reliable) |
| Addon posters | Passthrough if unrecognized | Same |
| Cache key stability | Just the URL string | Strip query params before cache key |
| Crossfade | false | false |
