Skip to content

About

Seerr mod: IMDB rating and vote count filters for Discovery page

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

seerr-imdb-mod

Aggiunge a Seerr due filtri che l'upstream non offre: voto IMDb e numero di voti IMDb, come slider nella pagina Scopri, sopra i filtri TMDB esistenti.

Il mod è un wrapper Docker: parte dall'immagine ufficiale di Seerr, ricostruisce il frontend con gli slider aggiunti e applica una patch al backend compilato all'avvio del container.

Stato: verificato su Seerr v3.4.1, 12 route di discover filtrate.


Indice


Come funziona

Browser: slider IMDb → query params imdbRatingGte / imdbRatingLte /
                       imdbVoteCountGte / imdbVoteCountLte
   │
   ▼
seerr-api.yml          i 4 parametri sono dichiarati su 12 endpoint, altrimenti
                       express-openapi-validator risponde 400 prima di tutto
   │
   ▼
imdb-route-wrapper.js  middleware Express agganciato alle 12 route
   │  ├─ se nessun filtro IMDb è attivo → passa oltre, costo zero
   │  ├─ spinge a TMDB un pre-filtro (vote_count >= 100, anno >= 1965)
   │  ├─ rimappa la pagina frontend su una finestra di 10 pagine TMDB
   │  └─ se i risultati non bastano, chiede altre pagine via HTTP loopback
   │
   ▼
imdb-filter.js         risoluzione TMDB → IMDb e lookup del voto
   │  ├─ cache locale  tmdb_cache.db      (SQLite, read-write)
   │  ├─ mediaInfo di Seerr, quando presente
   │  └─ TMDB /external_ids, in parallelo e con concorrenza limitata
   │
   ▼
imdb_ratings.db        dataset pubblico IMDb (read-only, ~1,7 M titoli)

Le due fasi del filtraggio

1. Pre-filtro su TMDB (economico). Quando un filtro IMDb è attivo, il middleware aggiunge alla query TMDB voteCountGte=100 e una data minima (1965-01-01). Sono titoli che non possono realisticamente soddisfare una soglia IMDb, e scartarli a monte evita di risolvere centinaia di id per nulla. È una scelta deliberata di prestazioni: restringe il bacino prima che qualsiasi lookup IMDb avvenga. Entrambi i valori sono configurabili.

Un caso merita una nota, perché la calibrazione è controintuitiva. Con sortBy=vote_average.asc TMDB restituisce intere pagine di titoli esattamente al valore del floor richiesto. Un floor molto sotto la soglia IMDb produce quindi pagine che non possono contenere nulla: misurato sul catalogo reale, con soglia IMDb 7.5 un floor TMDB di 6,5 mette 0 candidati su 20 sopra soglia, 7,0 ne mette 2, 7,5 ne mette 14. In quel caso il floor TMDB viene quindi allineato alla soglia IMDb invece di seguirla a distanza.

2. Filtro IMDb (autorevole). Per ogni candidato si risolve l'id IMDb e si legge averageRating / numVotes dal dataset. Un titolo viene scartato se:

Motivo Significato
no_imdb_id TMDB non espone un id IMDb per quel titolo
no_rating il titolo non ha una riga nel dataset IMDb
rating voto fuori dall'intervallo richiesto
votes numero di voti fuori dall'intervallo richiesto

no_rating merita una nota. Il dataset IMDb contiene ogni titolo con più di qualche voto, quindi una riga mancante significa davvero che il titolo sta sotto il minimo dello slider (che parte da 100 voti), non che i nostri dati siano incompleti. Questo è vero solo se il dataset è fresco: è esattamente il punto in cui la versione precedente falliva, escludendo film con 300.000 voti IMDb perché il database locale era fermo da mesi.

Compensazione delle pagine

Una pagina di 20 risultati TMDB, dopo il filtro, può ridursi a 2. Il middleware recupera quindi altre pagine TMDB, in parallelo a gruppi di 3, risolvendo gli id IMDb di tutto il gruppo in un unico batch, finché non ha di nuovo 20 risultati o esaurisce la finestra.

Due dettagli che valgono più di quanto sembri:

Le pagine extra si chiedono alla stessa route, via HTTP su loopback, con un header x-imdb-bypass, invece di ricostruire a mano i parametri TMDB. Così le pagine compensate usano esattamente la stessa semantica della prima, incluse le opzioni che una lista scritta a mano dimenticherebbe (era il caso di certification, certificationCountry e certificationMode). Il ramo bypass del middleware riapplica lo stesso pre-filtro dall'altro lato, quindi le pagine compensate restano coerenti con la prima.

L'allargamento si ferma presto quando non rende. Alcune combinazioni sono intrinsecamente sparse — i titoli "upcoming" non hanno ancora voti IMDb — e continuare a scandagliare la finestra non aggiunge nulla. Dopo due blocchi consecutivi che non producono risultati utili la scansione si interrompe:

Route Prima Dopo
/movies/studio/2 8,5 s 0,7 s
/movies/upcoming 13,0 s 0,3 s
/tv/upcoming 14,5 s 0,6 s

Stessi risultati restituiti, solo senza il lavoro inutile.

Paginazione

La pagina frontend N copre le pagine TMDB [10(N-1)+1 .. 10N], quindi pagine consecutive non si sovrappongono. TMDB rifiuta page oltre 500: il numero di pagine esposto è quindi limitato a 50 e le pagine successive rispondono vuote invece che con un errore 500.

totalResults resta la dimensione del bacino di candidati: nessuno può sapere quanti titoli soddisfino una soglia IMDb senza percorrere l'intero catalogo.


Perché è fatto così

Tre decisioni non ovvie, ognuna conseguenza di un bug reale.

Il dataset è read-only e viene sostituito con mv

imdb_ratings.db contiene solo i rating. L'app lo apre in sola lettura; imdb-sync.sh ne costruisce uno nuovo e lo pubblica con una mv atomica. Il file è in modalità delete, quindi non ha file -wal/-shm che possano sopravvivere allo swap.

La versione precedente teneva rating e cache nello stesso file, in modalità WAL, e lo sostituiva con mv: il -wal della generazione precedente restava sotto lo stesso nome e veniva riapplicato al file nuovo. SQLite risponde database disk image is malformed. È successo davvero, il 2026-09-16.

Sostituire invece la tabella in place non è una alternativa valida: SQLite richiede un lock esclusivo per i cambi di schema, quindi i lettori concorrenti ricevono SQLITE_BUSY e lo swap stesso può fallire con database is locked.

La cache vive in un file separato

tmdb_cache.db è scritto dall'app e non viene mai sostituito. È questa separazione che rende sicuro lo swap descritto sopra.

La cache ha chiave primaria composita (tmdbId, mediaType): gli id TMDB di film e serie sono namespace distinti, quindi un film e una serie possono condividere lo stesso id numerico. Con la chiave singola di prima si sovrascrivevano a vicenda, producendo voti IMDb sbagliati. Verificato: per l'id 155 la cache distingue correttamente tt0468569 (film) da tt0115082 (serie).

La cache viene popolata in anticipo dal warmer, perché l'API discover di TMDB non restituisce mai imdb_id: senza warm-up ogni titolo mai visto costa una chiamata extra.

La patch è idempotente e fallisce in modo onesto

patches/patch-seerr.js verifica che tutte e 12 le route attese esistano e siano patchate. Un patch parziale è un fallimento, non un avviso. I backup sono creati una sola volta, quindi rieseguire la patch non può sovrascrivere l'originale con un file già patchato.

La versione precedente, a ogni riavvio, contava zero route da patchare su un container già patchato, usciva con codice 1, e l'entrypoint annunciava "starting Seerr without IMDB mod" — su un'app perfettamente funzionante. Il suo rollback aveva lo stesso difetto e non poteva ripristinare nulla.


Struttura

.
├── Dockerfile                      wrapper: pin della release Seerr + build frontend
├── docker-compose.yml              servizio seerr + servizio tools/warmup
├── entrypoint.sh                   installa i moduli, applica la patch, avvia Seerr
├── imdb-sync.sh                    scarica il dataset IMDb e pubblica imdb_ratings.db
├── imdb-warmup.js                  pre-popola la cache TMDB → IMDb
├── update.sh                       promozione verificata a una nuova release Seerr
├── patches/
│   └── patch-seerr.js              patcha dist/routes/discover.js e seerr-api.yml
├── server-modules/
│   ├── imdb-filter.js              accesso ai DB, risoluzione id, semantica filtro
│   └── imdb-route-wrapper.js       middleware Express + compensazione pagine
├── overrides/                      sorgenti frontend (upstream + slider IMDb)
│   ├── constants.ts
│   └── FilterSlideover/index.tsx
├── docs/                           design e note di architettura
└── imdb-data/                      volumi persistenti (gitignored)
    ├── imdb_ratings.db             ~48 MB, read-only per l'app
    └── tmdb_cache.db               cache TMDB → IMDb, read-write

I file in overrides/ sono il contenuto upstream del tag più le sole aggiunte IMDb: update.sh verifica a ogni aggiornamento che restino un superset pulito, perché un override non più allineato reverebbe silenziosamente il lavoro upstream.


Operazioni

Aggiornare il dataset IMDb

docker exec seerr /imdb-sync.sh

Nessun riavvio necessario: lo swap è atomico e i lettori non vengono mai bloccati. Il cron notturno (0 3 * * *) lo esegue già.

Pre-popolare la cache

cd /mnt/user/appdata/seerr-imdb-mod
docker compose --profile tools run --rm warmup
docker compose --profile tools run --rm warmup --budget=5000      # più corto
docker compose --profile tools run --rm warmup --budget=40000 --genres

Il warmer è cache-first: le voci già note non costano richieste, quindi le esecuzioni successive convergono e diventano rapide. ~18 richieste/s, quindi 20.000 lookup ≈ 18 minuti.

Cron settimanale: 0 5 * * 0.

Verificare lo stato

docker logs seerr | head -20                      # versione, patch, freschezza dataset
docker exec seerr sqlite3 "file:/imdb-data/imdb_ratings.db?mode=ro" \
  "SELECT key, value FROM meta;"
docker exec seerr sqlite3 /imdb-data/tmdb_cache.db \
  "SELECT mediaType, COUNT(*) FROM tmdb_to_imdb GROUP BY mediaType;"

Configurazione

Variabili d'ambiente del servizio seerr:

Variabile Default Significato
IMDB_DEBUG 0 1 logga una riga per pagina filtrata: tempi, candidati, motivi di scarto
IMDB_WINDOW 10 pagine TMDB per pagina frontend (determina il massimo di 50 pagine)
IMDB_CHUNK 3 pagine TMDB richieste in parallelo per riempire una pagina
IMDB_MAX_EMPTY_CHUNKS 2 blocchi consecutivi a resa zero prima di fermare l'allargamento
IMDB_MIN_VOTE_COUNT 100 pre-filtro TMDB sul numero di voti
IMDB_MIN_YEAR 1965 pre-filtro TMDB sull'anno
IMDB_TMDB_CONCURRENCY 12 chiamate /external_ids TMDB in parallelo
IMDB_DB_PATH /imdb-data/imdb_ratings.db dataset rating
IMDB_CACHE_PATH /imdb-data/tmdb_cache.db cache TMDB → IMDb

IMDB_DEBUG=1 è lo strumento principale per capire un filtro che sembra sbagliato:

[imdb-filter] /api/v1/discover/movies page=1 tmdb=1-4 in=80 kept=20
  reasons={"no_imdb_id":0,"no_rating":0,"rating":34,"votes":0} 27ms

80 candidati esaminati, 20 tenuti, 34 scartati per voto, 0 per dato mancante.


Aggiornare Seerr

/mnt/user/appdata/seerr-imdb-mod/update.sh

Lo script è una promozione verificata, non un rebuild cieco:

  1. cerca l'ultima release upstream;
  2. verifica che gli override frontend siano ancora superset puliti dei sorgenti di quel tag;
  3. costruisce l'immagine candidata;
  4. verifica che la patch trovi e patchi ancora tutte e 12 le route;
  5. solo allora promuove, aggiorna il pin e ricrea il container.

Ogni fallimento lascia il container corrente in esecuzione. Cron giornaliero: 30 4 * * *.

Bump manuale: cambiare ARG SEERR_VERSION in Dockerfile e SEERR_VERSION in docker-compose.yml, poi docker compose build && docker compose up -d.


Diagnostica

Gli slider non filtrano nulla. Controllare il log di avvio: se la patch è fallita, discover.js viene ripristinato e Seerr parte senza mod. docker logs seerr | head -15. Verificare anche che il dataset esista: docker exec seerr ls -la /imdb-data/.

Il filtro scarta troppi titoli. Confrontare syncedAt con la data odierna. Un dataset vecchio fa sembrare "senza voto" film che hanno centinaia di migliaia di voti.

?imdbRatingGte= risponde 400 "Unknown query parameter". La patch di seerr-api.yml non ha coperto quella route: eseguire docker exec seerr node /patches/patch-seerr.js e leggere l'output.

Prima richiesta lenta su una pagina profonda. Normale a cache fredda. Il warmer è la risposta; IMDB_DEBUG=1 mostra quanti candidati sono stati risolti.

database disk image is malformed sul DB IMDb. Non deve più accadere: il file è read-only per l'app e viene pubblicato con mv in modalità delete. Se succede, eliminare imdb-data/imdb_ratings.db* e rieseguire /imdb-sync.sh.

About

Seerr mod: IMDB rating and vote count filters for Discovery page

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages