CREATE TABLE IF NOT EXISTS source (
id INTEGER PRIMARY KEY AUTOINCREMENT,
imdb_id TEXT NOT NULL,
provider TEXT NOT NULL,
hoster TEXT NOT NULL,
quality TEXT,
url TEXT NOT NULL,
direct INTEGER DEFAULT 0,
language TEXT DEFAULT 'de',
info TEXT DEFAULT '',
priority INTEGER DEFAULT 100,
prio_hoster INTEGER DEFAULT 100,
-- Pipeline: Resolve-Ergebnis (NULL = noch nicht resolved)
resolved_url TEXT DEFAULT NULL,
-- NULL = noch nicht resolved
-- '' = Resolve fehlgeschlagen
-- URL = aufgeloeste Stream-URL
-- Probe-Daten (NULL = noch nicht geprobt)
probe_width INTEGER,
probe_height INTEGER,
probe_bitrate TEXT,
probe_audio_lang TEXT,
probe_has_audio INTEGER DEFAULT 1,
probe_duration TEXT,
probe_codec TEXT,
probe_failed INTEGER DEFAULT 0, -- 0=offen, 1=echt failed/done, 2=Label-Skip (reversibel)
probe_at INTEGER,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
UNIQUE(imdb_id, provider, hoster, quality)
);CREATE TABLE IF NOT EXISTS movie_status (
imdb_id TEXT PRIMARY KEY,
scan_at INTEGER,
scan_source_count INTEGER DEFAULT 0,
probe_at INTEGER,
probe_count INTEGER DEFAULT 0,
probe_ok_count INTEGER DEFAULT 0
);CREATE TABLE IF NOT EXISTS movie (
imdb_id TEXT PRIMARY KEY,
tmdb_id INTEGER,
title TEXT NOT NULL,
originaltitle TEXT DEFAULT '',
year TEXT DEFAULT '',
added_at INTEGER NOT NULL, -- Wann in DB eingefuegt
source TEXT DEFAULT 'discover', -- 'discover', 'trailer', 'play'
priority INTEGER DEFAULT 0 -- 0=normal, 1=trailer (hoehere Prio)
);source: Woher der Film kam (discover=TMDB Trend/Neu, trailer=Trailer-Klick, play=User-Klick)priority: Trailer-Filme bekommen priority=1, werden zuerst gescanntadded_at: Fuer Sortierung (neueste zuerst bei gleicher Prioritaet)add_movie(): INSERT OR IGNORE — bekannte Filme nicht ueberschreibenadd_movie_priority(): ON CONFLICT DO UPDATE SET priority, source — fuer Trailer
Siehe DB-DRIVEN.md fuer Details.
CREATE TABLE IF NOT EXISTS movie_provider (
imdb_id TEXT NOT NULL,
provider TEXT NOT NULL,
called_at INTEGER NOT NULL,
hits INTEGER DEFAULT 0, -- NULL = in-flight, 0 = kein Treffer, >0 = Anzahl Quellen
PRIMARY KEY (imdb_id, provider)
);hits IS NULL: Scan dispatcht aber noch kein Ergebnis (in-flight)hits = 0: Provider aufgerufen, keine Quellen gefundenhits > 0: Provider hat N Quellen gefunden
CREATE TABLE IF NOT EXISTS provider (
name TEXT PRIMARY KEY,
priority INTEGER DEFAULT 1,
call_count INTEGER DEFAULT 0,
hit_count INTEGER DEFAULT 0,
total_duration_ms INTEGER DEFAULT 0,
last_seen_at INTEGER NOT NULL,
created_at INTEGER NOT NULL,
-- Medientyp: 'movie' (nur Filme), 'series' (nur Serien), 'all' (beides)
mediatype TEXT DEFAULT 'all',
-- Domain-Health: 'ok', 'cf_blocked', 'dead', 'unknown'
domain_status TEXT DEFAULT 'unknown'
);mediatypewird aus_MEDIATYPE_MAPoderscraper.mediatypeAttribut gesetzt (viasync_providers())- Serien-Provider (
burningseries,aniworld,serienstream) werden beim Movie-Precache uebersprungen - Movie-Provider (
einschalten,netzkino) wuerden bei eventuellem Serien-Precache uebersprungen - Default
'all'= bedient beides
- Serien-Provider (
domain_statuswird vonservice.pynach dem Domain-Check geschrieben (viaupdate_domain_status())'dead'Provider werden immer uebersprungen'cf_blocked'Provider nur uebersprungen wenn KEIN FlareSolverr konfiguriert'unknown'und'ok'Provider bleiben aktiv
Siehe PROVIDER-STATS.md fuer Score-Formel und Tracking.
CREATE TABLE IF NOT EXISTS hoster (
name TEXT PRIMARY KEY,
last_used_at INTEGER DEFAULT 0,
call_count INTEGER DEFAULT 0,
ok_count INTEGER DEFAULT 0,
fail_count INTEGER DEFAULT 0,
busy_since INTEGER DEFAULT NULL, -- NULL = frei, timestamp = Resolve in-flight
stream_type TEXT DEFAULT NULL -- NULL = unbekannt, 'mp4'/'hls'/'dash' = gelernt
);busy_sincewird beidispatch_probe()gesetzt (vor Resolve-Dispatch)- Timeout: 20s (Resolve braucht bis zu 15s)
- Timestamp wird NIE geloescht — bleibt als Beweis, wird bei naechstem Dispatch ueberschrieben
stream_typewird beim ersten erfolgreichen MediaInfo-Probe gelernt- Danach: Typ-Erkennung (8KB peek) ueberspringen, direkt zum richtigen Prober
- Spart ~200-400ms pro Probe
Raeumt movie_provider auf wenn Sources geloescht werden (z.B. TTL-Ablauf).
Wenn die letzte Source eines Providers fuer einen Film geloescht wird, wird der Provider-Eintrag entfernt.
Dadurch wird is_fully_scanned() und get_phase() automatisch aktualisiert.
CREATE TRIGGER IF NOT EXISTS source_deleted_cleanup
AFTER DELETE ON source
BEGIN
DELETE FROM movie_provider
WHERE imdb_id = OLD.imdb_id AND provider = OLD.provider
AND NOT EXISTS (
SELECT 1 FROM source WHERE imdb_id = OLD.imdb_id
AND provider = OLD.provider AND expires_at > strftime('%s','now')
);
END;Phase wird nie gespeichert sondern per SQL VIEW live berechnet.
Die VIEW wird bei jedem _ensure_table() neu erstellt (DROP + CREATE).
CREATE VIEW IF NOT EXISTS v_movie_phase AS
SELECT m.imdb_id, m.tmdb_id, m.title, m.originaltitle, m.year,
m.priority, m.added_at, m.source AS movie_source,
CASE
-- NUR Film-faehige Provider zaehlen (mediatype IN ('movie','all'))
-- Serien-Provider werden ignoriert, blockieren keine Phase
WHEN sources=0 AND alle_film_provider_aufgerufen THEN 'done'
WHEN sources=0 THEN '1a'
WHEN hd_sources > 0 THEN 'done'
WHEN unprobed_sources > 0 THEN '1b'
WHEN NOT alle_film_provider_aufgerufen THEN '2'
ELSE 'done'
END AS phase,
source_count, unprobed_count, hd_count
FROM movie m
LEFT JOIN (...aggregierte Source-Counts...) ...;- Keine Sources + alle Provider →
done - Keine Sources + Provider uebrig →
1a - HD gefunden →
done(VOR unprobed-Check!) - Ungeprobte Sources (excl.
probe_failed) →1b - Kein HD + Provider uebrig →
2 - Alle Provider, kein HD →
done
Kritisch: HD-Check MUSS vor unprobed-Check kommen. Ein Film mit HD-Source ist fertig, auch wenn noch ungeprobte Sources existieren.
source_count: Alle Sources mitexpires_at > nowunprobed_count: Sources mitprobe_at IS NULL AND probe_failed = 0hd_count: Sources mitprobe_height >= 720 AND probe_failed = 0
# Alle scanbaren Filme (eine einzige Query statt N+1)
get_scannable_movies() # SELECT * FROM v_movie_phase WHERE phase != 'done'
# Phase fuer einzelnen Film
get_phase(imdb_id) # SELECT phase FROM v_movie_phase WHERE imdb_id = ?CREATE INDEX IF NOT EXISTS idx_source_imdb ON source(imdb_id);
CREATE INDEX IF NOT EXISTS idx_source_expires ON source(expires_at);
CREATE INDEX IF NOT EXISTS idx_source_probe ON source(imdb_id, probe_height);
CREATE INDEX IF NOT EXISTS idx_movie_tmdb ON movie(tmdb_id);
-- Covering-Index: alle 3 LEFT JOINs in v_movie_phase als Index-Only-Scan
CREATE INDEX IF NOT EXISTS idx_source_phase ON source(imdb_id, expires_at, probe_at, probe_failed, probe_height);idx_source_phase: Covering-Index fuer die VIEW-Subqueries. SQLite kann Source-Count, Unprobed-Count und HD-Count komplett aus dem Index lesen ohne diesource-Tabelle zu oeffnen.
Migrationen laufen automatisch beim Import von sourcecacheDB.py:
- JSON → Relational:
source_cache+scan_status→source+movie_status called_providers + phase— entfernt (Legacy, movie_provider + v_movie_phase ersetzen beides)- hoster Stats:
call_count,ok_count,fail_countSpalten - hoster busy_since:
busy_since INTEGER DEFAULT NULL - hoster stream_type:
stream_type TEXT DEFAULT NULL(Pipeline) - source resolved_url:
resolved_url TEXT DEFAULT NULL(Pipeline) - movie_provider hits:
hits INTEGER DEFAULT 0 - Probe-Reset: Sources ohne Dauer (alte Probes) werden zurueckgesetzt
- movie-Tabelle: Persistente Filmliste +
v_movie_phaseVIEW +idx_movie_tmdbIndex - provider mediatype:
mediatype TEXT DEFAULT 'all'(Medientyp-Filter) - provider domain_status:
domain_status TEXT DEFAULT 'unknown'(Domain-Health)
Vollstaendige Liste aller 22 Settings: siehe variables_and_constants.md.
DB-relevante Settings:
| ID | Typ | Default | Beschreibung |
|---|---|---|---|
precache.ttl |
slider (1-30) | 14 | Cache-Gueltigkeit in Tagen |
precache.scan_cooldown |
slider (24-168) | 72 | Scan-Cooldown in Stunden |
precache.max_movies |
slider (100-2000) | 1000 | Max. Filme in DB |
| Wert | Bedeutung | Reversibel? |
|---|---|---|
0 |
Offen (noch nicht geprobt oder zurueckgesetzt) | — |
1 |
Echt fehlgeschlagen oder nach Probe bestaetigt + Skip | Nein (permanent) |
2 |
Per Provider-Label uebersprungen (probe=off, HD-Label gefunden) | Ja (reset_label_skipped()) |
get_sources()zeigtprobe_failed IN (0, 2)— label-skipped Sources sind sichtbarget_dispatchable_*()nutztprobe_failed = 0— nur offene Sources werden dispatchedreset_label_skipped(): setzt alle 2er auf 0 wenn Probing aktiviert wird- VIEW
v_movie_phase:probe_failed = 0fuer unprobed/hd Counts — 2er zaehlen weder als unprobed noch als HD