# SEL-Style Stream Filtering & Sorting Patterns for Android TV

## Source

Adapted from [Tam-Taro/SEL-Filtering-and-Sorting](https://github.com/Tam-Taro/SEL-Filtering-and-Sorting) configs for AIOStreams. These are **config/SEL-expression patterns** — the core execution engine lives in the AIOStreams addon. This reference translates the filtering/sorting concepts into patterns usable in an Android TV app.

## Three-Stage Filter Pipeline

```
Stream[] → ISE (Include/Bypass) → ESE (Exclude/Filter) → PSE (Prefer/Rank) → Sorted[]
```

### Stage 1: ISE (Included Stream Expressions) — Emergency Bypass
Runs first. Streams matching these expressions bypass subsequent filters entirely.

Patterns:
- **SeaDex matched releases**: Known best releases bypass all filters
- **Library content**: User's personal media library
- **Zero cached fallback**: If zero debrid-cached streams exist, pass everything through
- **Low diversity bypass**: If a digital-release category has <15 results, don't filter them

### Stage 2: ESE (Excluded Stream Expressions) — Hard Filters
The main filtering engine. Applied top-to-bottom, each filter removes streams.

**Sub-stage 2a: Bitrate & Seeders**
```typescript
// For each resolution+quality combination, remove streams below a bitrate floor
// For P2P streams, use dynamic seeder threshold: q2 → q1 → 10th percentile → keep min 5
```

**Sub-stage 2b: Content Quality**
```typescript
// Remove "4K" anime when no genuine 4K source exists
// Remove upscaled 4K when 1080p Bluray REMUX exists but no real 4K
// Remove streams with SEL score below -50 when >10 such streams exist
```

**Sub-stage 2c: Quality/Resolution Slicing** (core filtering)
For each quality × resolution pair, keep top N streams after sorting:
```typescript
// Standard: keep top 3 per combination
// Extended: keep top 6 per combination
[
  "Bluray REMUX 2160p", "Bluray REMUX 1080p (keep 0)",
  "Bluray 2160p", "Bluray 1080p", "Bluray 720p",
  "WEB-DL 2160p", "WEB-DL 1080p", "WEB-DL 720p",
  "WEBRip 1080p", "WEBRip 720p",
  "HDTV 1080p", "HDTV 720p",
  "HDRip 1080p", "DVDRip",
]
```

**Sub-stage 2d: Final Limit**
```typescript
// If >12 high-quality streams, drop HDRip/DVDRip/HDTV/CAM/TS/TC/SCR
// If >15 known-resolution streams, drop 720p/576p/480p from non-debrid
```

### Stage 3: PSE (Preferred Stream Expressions) — Ranking Boosts
These don't remove streams — they REORDER them within their quality/resolution tier:

```typescript
// Tier 1 — Best: cached library + SeaDex + debrid + cached usenet
// Tier 2 — Good: cached usenet + stremio-usenet + http + p2p  
// Tier 3 — Acceptable: uncached usenet
// Tier 4 — Last: uncached debrid
```

## Metadata Extraction from Stream Names

AIOStreams/Parsing addons populate metadata from the stream `name` (title) field. Parse with regex:

```typescript
interface StreamMetadata {
  resolution: string;     // "2160p", "1080p", "720p", "576p", "480p", "Unknown"
  quality: string;        // "Bluray REMUX", "Bluray", "WEB-DL", "WEBRip", "HDTV", "HDRip", "DVDRip", "CAM", "TS", "TC", "SCR"
  visualTags: string[];   // ["DV", "HDR", "HDR10", "HDR10+", "SDR", "HLG", "IMAX"]
  encode: string;         // "x265", "x264", "AV1", "XviD"
  audioTags: string[];    // ["DTS", "TrueHD", "Atmos", "FLAC", "DD", "DD+", "AAC"]
  languages: string[];    // ["English", "Japanese", "Multi"]
  subtitleLanguages: string[];
  size: number;           // bytes
  bitrate: number;        // bps
  seeders: number;
  isCached: boolean;
  isLibrary: boolean;
  isSeaDexMatched: boolean;
  streamExpressionScore: number; // -100 to 100 from regex/pattern matching
}
```

### Regex patterns for parsing

```typescript
const RESOLUTION_RE = /2160p|1080p|720p|576p|480p|360p|240p|144p/gi;
const QUALITY_RE = /BluRay|WEB-DL|WEBRip|HDTV|HDRip|DVDRip|REMUX|CAM|TS|TC|SCR/gi;
const VISUAL_RE = /\b(DV|HDR10?\+?|HLG|SDR|IMAX)\b/gi;
const ENCODE_RE = /\b(x265|x264|AV1|XViD)\b/gi;
const AUDIO_RE = /\b(DTS[\-\s]?HD[\-\s]?(MA|HR)?|TrueHD|Atmos|FLAC|DD[\+]?|AAC)\b/gi;
```

### Canonical stream title format
```
[4K] [Bluray] Release.Group.Name.2025.2160p.BluRay.DV.HDR10.x265-Framestor
```

## Scoring-Based Ranking

```typescript
function streamSortScore(
  stream: StreamMetadata,
  prefs: UserFilters
): number {
  let score = 0;

  // 1. Resolution weight (largest multiplier)
  score += RESOLUTION_WEIGHTS[stream.resolution] * 1_000_000;

  // 2. Quality weight
  score += QUALITY_WEIGHTS[stream.quality] * 100_000;

  // 3. Library boost (configurable)
  if (stream.isLibrary) score += prefs.libraryBoost * 10_000;

  // 4. SeaDex boost
  if (stream.isSeaDeXMatched) score += 50_000;

  // 5. Cached boost
  if (stream.isCached) score += 20_000;

  // 6. Visual tag boost (DV > HDR > SDR)
  if (stream.visualTags.includes("DV")) score += 5_000;

  // 7. Language boost (configurable)
  if (prefs.preferredLanguages.some(l => stream.languages.includes(l))) {
    score += prefs.languageBoost * 500;
  }

  // 8. Audio boost (Atmos > TrueHD > DTS)
  if (stream.audioTags.includes("Atmos")) score += 1_000;
  
  // 9. Encode preference (x265 > x264 > AV1)
  if (stream.encode === "x265") score += 500;

  // 10. Seeders (P2P only)
  if (stream.seeders) score += Math.min(stream.seeders, 100) * 10;

  return score;
}
```

### Resolution weights
```
2160p = 7, 1440p = 6, 1080p = 5, 720p = 4, 576p = 3, 480p = 2, 360p = 1, Unknown = 0
```

### Quality weights
```
Bluray REMUX = 7, Bluray = 6, WEB-DL = 5, WEBRip = 4, HDTV = 3, 
HDRip = 2, DVDRip = 1, CAM/TS/TC/SCR = 0
```

## Quality/Resolution slicing (top-N per group)

```typescript
interface QualityResGroup {
  quality: string;
  resolution: string;
  limit: number; // how many to keep
}

const DEFAULT_GROUPS: QualityResGroup[] = [
  { quality: "Bluray REMUX", resolution: "2160p", limit: 3 },
  { quality: "Bluray REMUX", resolution: "1080p", limit: 0 }, // keep none
  { quality: "Bluray", resolution: "2160p", limit: 3 },
  { quality: "Bluray", resolution: "1080p", limit: 3 },
  { quality: "Bluray", resolution: "720p", limit: 3 },
  { quality: "WEB-DL", resolution: "2160p", limit: 3 },
  { quality: "WEB-DL", resolution: "1080p", limit: 3 },
  { quality: "WEB-DL", resolution: "720p", limit: 3 },
  { quality: "WEBRip", resolution: "1080p", limit: 3 },
  { quality: "WEBRip", resolution: "720p", limit: 3 },
  { quality: "HDTV", resolution: "1080p", limit: 3 },
  { quality: "HDTV", resolution: "720p", limit: 3 },
];

function sliceTopN(
  streams: StreamMetadata[],
  groups: QualityResGroup[],
): StreamMetadata[] {
  const result: StreamMetadata[] = [];

  for (const group of groups) {
    const matching = streams
      .filter(s => s.quality === group.quality && s.resolution === group.resolution)
      .sort((a, b) => streamSortScore(b, DEFAULT_FILTERS) - streamSortScore(a, DEFAULT_FILTERS));

    result.push(...matching.slice(0, group.limit));
  }

  return result;
}
```

## Dynamic quality filtering

When high-quality results are abundant, drop lower tiers:

```typescript
function applyDynamicLimits(
  streams: StreamMetadata[],
): StreamMetadata[] {
  const hqStreams = streams.filter(s =>
    QUALITY_WEIGHTS[s.quality] >= 5 // Bluray, WEB-DL
  );

  if (hqStreams.length > 12) {
    // Drop HDRip, DVDRip, HDTV, CAM, TS, TC, SCR
    return streams.filter(s =>
      QUALITY_WEIGHTS[s.quality] >= 3
    );
  }

  const knownRes = streams.filter(s => s.resolution !== "Unknown");
  if (knownRes.length > 15) {
    // Drop 720p, 576p, 480p from non-debrid
    return streams.filter(s =>
      !(["720p", "576p", "480p"].includes(s.resolution) && !s.isCached)
    );
  }

  return streams;
}
```

## Caching strategy

| Data type | TTL | Notes |
|-----------|-----|-------|
| Stream results | 30-60s | Cached per metaId (movie/tt0133093) |
| Addon health | 5-15 min | Cached per addon ID |
| Filter/sort config | 1+ hours | Cached per user profile |
| Remote filter rules | 1 hour | Synced from URL if available |

## Passthrough mechanism

Allow configurable "passthrough" rules that let specific high-value streams bypass standard filters:

```typescript
interface PassthroughRule {
  condition: (stream: StreamMetadata) => boolean;
  maxCount: number; // how many streams to pass through
  bypassStages: ("ISE" | "ESE" | "PSE")[];
}
```

Common passthrough cases:
- **Language passthrough**: Show N streams in preferred language regardless of quality
- **Subtitle passthrough**: Bypass if embedded subtitles in user's language
- **Visual tag passthrough**: DV or DV+HDR streams bypass filters (up to 5)
- **Top-1 pin**: Best single stream per Resolution or Quality/Resolution gets pinned to top
- **Mobile backup**: Low-bitrate streams pinned to bottom (not removed)
