# Streambert Anime Detection and AniList Metadata Switch: A Technical Deep Dive

> Explore Streambert's anime detection and AniList metadata switch. It uses TMDB and AniList data for seamless anime playback and richer metadata. Dive into the technical details.

- Repository: [true_lock/streambert](https://github.com/truelockmc/streambert)
- Tags: deep-dive
- Published: 2026-05-21

---

**Streambert automatically detects anime content by inspecting TMDB metadata for Japanese origin and animation genres, then switches to AniList for richer metadata and routes playback through anime-specific sources.**

The **truelockmc/streambert** repository implements an intelligent content routing system that distinguishes anime from Western media. This Streambert anime detection AniList metadata switch ensures users receive culturally accurate metadata and appropriate streaming sources when browsing Japanese animation.

## How Streambert Detects Anime Content

### The Detection Logic in [`src/utils/api.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/api.js)

At the heart of the detection system lies the `isAnimeContent()` function located in **[`src/utils/api.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/api.js)**. This utility performs a three-factor validation against TMDB API responses to classify titles:

```js
// src/utils/api.js
export const isAnimeContent = (item, details) => {
  const d = details || item;
  const lang = d.original_language;
  const countries = d.origin_country || [];
  const genreIds = d.genre_ids || (d.genres || []).map(g => g.id);
  const hasAnimation = genreIds.includes(16);
  return hasAnimation && (lang === "ja" || countries.includes("JP"));
};

```

The function evaluates three specific TMDB attributes:
- **`original_language`** – Must equal `"ja"` (Japanese)
- **`origin_country`** – Must include `"JP"` (Japan)
- **`genre_ids`** – Must contain ID `16` (Animation genre)

Only when all conditions return true does Streambert classify the entry as anime, triggering the subsequent metadata switch.

## Switching from TMDB to AniList Metadata

### The AniList GraphQL Integration

Once `isAnimeContent()` returns `true`, Streambert abandons TMDB for descriptive metadata and queries the **AniList GraphQL API**. The request wrapper and query definitions reside alongside the detection logic in [`src/utils/api.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/api.js):

```js
// src/utils/api.js
const ANILIST_API = "https://graphql.anilist.co";
const ANILIST_QUERY = `...`;   // GraphQL query omitted for brevity
export const fetchAnilistData = async (title, type = "ANIME", tmdbId = null) => { ... };

```

The `fetchAnilistData()` function accepts the original TMDB title (or TMDB ID) and returns enriched metadata including clean descriptions, specialized genres, ratings, and season structures that better reflect anime cataloging standards.

### Caching Strategy for Performance

To minimize redundant network requests and respect AniList rate limits, Streambert implements a two-tier caching system:

- **AniList cache** – Stored under the key `streambert_anilistCache` in `localStorage`. Loaded once via `getAnilistCache()` and flushed after writes through `flushAnilistCache()`. Entries automatically expire after **7 days**.
- **Episode-group cache** – Separate storage for TMDB episode groups under `streambert_episodeGroupCache`, sharing the same TTL and lazy-load patterns.

This caching layer ensures snappy UI performance when users repeatedly browse the same anime series.

## Default Source Selection for Anime Playback

Streambert routes playback through different streaming sources based on the detection results. The constants defined immediately after the detection logic map these defaults:

```js
// src/utils/api.js
export const ANIME_DEFAULT_SOURCE = "allmanga";
export const NON_ANIME_DEFAULT_SOURCE = "vidsrc";

```

When content passes the anime detection check, the UI selects `ANIME_DEFAULT_SOURCE` (`"allmanga"`); otherwise, it falls back to `NON_ANIME_DEFAULT_SOURCE` (`"vidsrc"`). The actual media URLs for the AllManga source are generated by the `getSourceUrl()` function in the same file, while the underlying AllManga fetching logic lives in **[`src/ipc/allmanga.js`](https://github.com/truelockmc/streambert/blob/main/src/ipc/allmanga.js)**.

## Implementation Workflow

The typical integration flow when a user selects a title follows this sequence:

1. **TMDB Fetch** – The UI component (in **[`src/pages/TVPage.jsx`](https://github.com/truelockmc/streambert/blob/main/src/pages/TVPage.jsx)** or **[`src/pages/MoviePage.jsx`](https://github.com/truelockmc/streambert/blob/main/src/pages/MoviePage.jsx)**) retrieves base metadata from TMDB.
2. **Content Classification** – `isAnimeContent(item)` evaluates the response.
3. **Metadata Enrichment** – If anime is detected, the system calls `fetchAnilistData(item.title, "ANIME", item.id)` to fetch specialized metadata.
4. **Rendering & Playback** – The UI renders AniList data (descriptions, genres, ratings) and configures playback using the AllManga source.

### Practical Implementation Example

Below is a simplified React component demonstrating the detection and switch workflow:

```jsx
import { isAnimeContent, fetchAnilistData, getSourceUrl } from '../utils/api';

async function loadTitleDetails(tmdbItem) {
  const anime = isAnimeContent(tmdbItem);
  let metadata = tmdbItem; // fallback to TMDB

  if (anime) {
    const anilist = await fetchAnilistData(
      tmdbItem.title?.original_name || tmdbItem.name,
      'ANIME',
      tmdbItem.id
    );
    if (anilist) metadata = anilist;
  }

  // Choose playback source
  const sourceId = anime ? 'allmanga' : 'vidsrc';
  const playbackUrl = getSourceUrl(sourceId, 'movie', tmdbItem.id);

  return { metadata, playbackUrl, anime };
}

```

### Debugging the Cache

During development, you can manually clear the AniList cache to force fresh metadata fetches:

```js
// In browser dev tools console
localStorage.removeItem('streambert_anilistCache');

```

## Summary

- **Detection criteria** – Streambert identifies anime through the `isAnimeContent()` function by checking for Japanese language (`"ja"`), Japanese origin country (`"JP"`), and Animation genre ID `16` in TMDB responses.
- **Metadata switching** – Upon detection, the system invokes `fetchAnilistData()` from [`src/utils/api.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/api.js) to query the AniList GraphQL API, replacing TMDB descriptions with anime-specific metadata.
- **Source routing** – Anime content automatically uses the `"allmanga"` source via `ANIME_DEFAULT_SOURCE`, while non-anime defaults to `"vidsrc"`.
- **Performance optimization** – A 7-day TTL cache stored in `localStorage` under `streambert_anilistCache` prevents redundant API calls and accelerates repeat visits.
- **Implementation files** – Core logic resides in [`src/utils/api.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/api.js), with UI integration in [`src/pages/TVPage.jsx`](https://github.com/truelockmc/streambert/blob/main/src/pages/TVPage.jsx) and [`src/pages/MoviePage.jsx`](https://github.com/truelockmc/streambert/blob/main/src/pages/MoviePage.jsx), and IPC handling in [`src/ipc/allmanga.js`](https://github.com/truelockmc/streambert/blob/main/src/ipc/allmanga.js).

## Frequently Asked Questions

### How does Streambert determine if a title is anime?

Streambert evaluates three TMDB fields in the `isAnimeContent()` function: the `original_language` must be `"ja"` (Japanese), `origin_country` must include `"JP"`, and `genre_ids` must contain `16` (Animation). All three conditions must be satisfied for the title to be classified as anime according to the source code in [`src/utils/api.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/api.js).

### What metadata does AniList provide that TMDB doesn't?

AniList provides anime-specific categorization including precise season structures, native Japanese titles with proper romanization, specialized genre tags relevant to anime culture, and community-driven ratings that align with anime fan expectations. This metadata is retrieved via the `fetchAnilistData()` function when the anime detection trigger activates.

### Where is the AniList cache stored?

The AniList cache persists in the browser's `localStorage` under the key `streambert_anilistCache`. The system loads this cache once at startup using `getAnilistCache()` and writes updates through `flushAnilistCache()`. Entries automatically expire after seven days to ensure metadata freshness.

### Can I manually clear the AniList cache for debugging?

Yes. Open your browser's developer tools console and execute `localStorage.removeItem('streambert_anilistCache')`. This forces Streambert to fetch fresh metadata from AniList on the next anime title request, which is useful when verifying API responses or troubleshooting metadata inconsistencies.