Video enrichment: wire new workers into Manage Workers modal + Settings

- Manage Workers modal: register fanart.tv / OpenSubtitles / YouTube Votes (RYD)
  / SponsorBlock in the worker rail (cards + animations + pause/resume come free
  via the shared .em-* design); add the 'video' entity kind.
- Settings: fanart.tv + OpenSubtitles API-key fields (with Test buttons) and a
  no-key 'YouTube Extras' toggle frame (RYD + SponsorBlock on/off).
- API /enrichment/config now reads/writes the two keys + the two toggles; a key
  change rebuilds the engine so the worker turns on immediately.
This commit is contained in:
BoulderBadgeDad 2026-06-17 23:31:33 -07:00
parent 278ba47519
commit 21a1e5ddbb
4 changed files with 93 additions and 3 deletions

View file

@ -50,6 +50,11 @@ def register_routes(bp):
"tmdb_api_key": db.get_setting("tmdb_api_key") or "",
"tvdb_api_key": db.get_setting("tvdb_api_key") or "",
"omdb_api_key": db.get_setting("omdb_api_key") or "",
# Backfill-worker keys (free, optional) + no-key toggles.
"fanart_api_key": db.get_setting("fanart_api_key") or "",
"opensubtitles_api_key": db.get_setting("opensubtitles_api_key") or "",
"ryd_enabled": (db.get_setting("ryd_enabled") or "1") == "1",
"sponsorblock_enabled": (db.get_setting("sponsorblock_enabled") or "1") == "1",
"billboard_autoplay": (db.get_setting("billboard_autoplay") or "1") == "1",
"watch_region": (db.get_setting("watch_region") or "US").upper(),
})
@ -81,6 +86,12 @@ def register_routes(bp):
put_key("tmdb_api_key")
put_key("tvdb_api_key")
put_key("fanart_api_key")
put_key("opensubtitles_api_key")
# No-key worker on/off toggles (read live by the worker — no rebuild needed).
for flag in ("ryd_enabled", "sponsorblock_enabled"):
if flag in body:
db.set_setting(flag, "1" if body.get(flag) else "0")
if "billboard_autoplay" in body:
db.set_setting("billboard_autoplay", "1" if body.get("billboard_autoplay") else "0")
if "watch_region" in body:

View file

@ -5464,6 +5464,62 @@
<button class="test-button" type="button" data-video-test-service="omdb">Test OMDb</button>
</div>
</div>
<div class="api-service-frame stg-service" data-video-service="fanart">
<div class="stg-service-header" onclick="toggleStgService(this)">
<span class="stg-service-dot" style="color: #e4509a;"></span>
<h4 class="service-title">fanart.tv (Artwork)</h4>
<span class="stg-service-chevron"></span>
</div>
<div class="stg-service-body">
<div class="form-group">
<label>API Key:</label>
<input type="password" id="fanart-api-key" placeholder="fanart.tv API Key">
</div>
<div class="callback-info">
<div class="callback-help">Get a free key from <a href="https://fanart.tv/get-an-api-key/" target="_blank" style="color: #e4509a;">fanart.tv</a></div>
<div class="callback-help">Clear logos, clearart, banners, posters &amp; backgrounds (gap-fills missing art).</div>
</div>
<button class="test-button" type="button" data-video-test-service="fanart">Test fanart.tv</button>
</div>
</div>
<div class="api-service-frame stg-service" data-video-service="opensubtitles">
<div class="stg-service-header" onclick="toggleStgService(this)">
<span class="stg-service-dot" style="color: #22a079;"></span>
<h4 class="service-title">OpenSubtitles (Subtitles)</h4>
<span class="stg-service-chevron"></span>
</div>
<div class="stg-service-body">
<div class="form-group">
<label>API Key:</label>
<input type="password" id="opensubtitles-api-key" placeholder="OpenSubtitles API Key">
</div>
<div class="callback-info">
<div class="callback-help">Get a free key from <a href="https://www.opensubtitles.com/en/consumers" target="_blank" style="color: #22a079;">OpenSubtitles</a> (register a consumer)</div>
<div class="callback-help">Records which subtitle languages exist per title.</div>
</div>
<button class="test-button" type="button" data-video-test-service="opensubtitles">Test OpenSubtitles</button>
</div>
</div>
<div class="api-service-frame stg-service" data-video-service="ytextras">
<div class="stg-service-header" onclick="toggleStgService(this)">
<span class="stg-service-dot" style="color: #ef4444;"></span>
<h4 class="service-title">YouTube Extras (No Key)</h4>
<span class="stg-service-chevron"></span>
</div>
<div class="stg-service-body">
<label class="vid-pref-row">
<input type="checkbox" id="video-ryd-enabled" checked>
<span>Return YouTube Dislike (like/dislike estimates)</span>
</label>
<label class="vid-pref-row">
<input type="checkbox" id="video-sponsorblock-enabled" checked>
<span>SponsorBlock (crowd-sourced segments)</span>
</label>
<div class="callback-info">
<div class="callback-help">Free community APIs — no key needed. Enrich cached YouTube videos in the background.</div>
</div>
</div>
</div>
</div>
<!-- Server Connection — mirrors the music server picker, scoped
to Plex/Jellyfin. The toggle BOTH selects the active video

View file

@ -20,6 +20,11 @@
{ id: 'omdb', name: 'OMDb', color: '#f5c518', rgb: '245, 197, 24', kinds: ['movie', 'show'] },
// The YouTube date enricher — no per-kind match queue; its own simple panel.
{ id: 'youtube', name: 'YouTube Dates', color: '#ff3b3b', rgb: '255, 59, 59', kinds: [], glyph: '▶' },
// Backfill workers — enrich already-identified items by id.
{ id: 'fanart', name: 'fanart.tv', color: '#e4509a', rgb: '228, 80, 154', kinds: ['movie', 'show'], glyph: '🎨' },
{ id: 'opensubtitles', name: 'OpenSubtitles', color: '#22a079', rgb: '34, 160, 121', kinds: ['movie', 'show'], glyph: '💬' },
{ id: 'ryd', name: 'YouTube Votes', color: '#ef4444', rgb: '239, 68, 68', kinds: ['video'], glyph: '👍' },
{ id: 'sponsorblock', name: 'SponsorBlock', color: '#00b4a0', rgb: '0, 180, 160', kinds: ['video'], glyph: '⏭' },
];
function workerDef(id) {
@ -34,8 +39,8 @@
tmdb: 'https://www.themoviedb.org/assets/2/v4/logos/v2/blue_square_2-d537fb228cf3ded904ef09b136fe3fec72548ebc1fea3fbbd1ad9e36364db38b.svg',
tvdb: 'https://www.svgrepo.com/show/443500/brand-tvdb.svg',
};
var GLYPH = { movie: '🎬', show: '📺', episode: '🎞️' };
var KIND_LABEL = { movie: 'Movies', show: 'Shows', episode: 'Episodes' };
var GLYPH = { movie: '🎬', show: '📺', episode: '🎞️', video: '▶' };
var KIND_LABEL = { movie: 'Movies', show: 'Shows', episode: 'Episodes', video: 'Videos' };
var state = {
open: false, selected: 'tmdb', statuses: {}, breakdown: null,

View file

@ -212,6 +212,14 @@
if (t && d.tmdb_api_key != null) t.value = d.tmdb_api_key;
if (v && d.tvdb_api_key != null) v.value = d.tvdb_api_key;
if (o && d.omdb_api_key != null) o.value = d.omdb_api_key;
var fa = document.getElementById('fanart-api-key');
if (fa && d.fanart_api_key != null) fa.value = d.fanart_api_key;
var sub = document.getElementById('opensubtitles-api-key');
if (sub && d.opensubtitles_api_key != null) sub.value = d.opensubtitles_api_key;
var ryd = document.getElementById('video-ryd-enabled');
if (ryd && d.ryd_enabled != null) ryd.checked = !!d.ryd_enabled;
var sb = document.getElementById('video-sponsorblock-enabled');
if (sb && d.sponsorblock_enabled != null) sb.checked = !!d.sponsorblock_enabled;
var ap = document.getElementById('video-billboard-autoplay');
if (ap && d.billboard_autoplay != null) ap.checked = !!d.billboard_autoplay;
var wr = document.getElementById('video-watch-region');
@ -237,12 +245,20 @@
var t = document.getElementById('tmdb-api-key');
var v = document.getElementById('tvdb-api-key');
var o = document.getElementById('omdb-api-key');
var fa = document.getElementById('fanart-api-key');
var sub = document.getElementById('opensubtitles-api-key');
var ryd = document.getElementById('video-ryd-enabled');
var sb = document.getElementById('video-sponsorblock-enabled');
return fetch(CONFIG_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({
tmdb_api_key: t ? t.value : '', tvdb_api_key: v ? v.value : '',
omdb_api_key: o ? o.value : '',
fanart_api_key: fa ? fa.value : '',
opensubtitles_api_key: sub ? sub.value : '',
ryd_enabled: ryd ? ryd.checked : true,
sponsorblock_enabled: sb ? sb.checked : true,
})
}).then(function () { if (!silent) toast('API keys saved', 'success'); })
.catch(function () { /* ignore */ });
@ -284,7 +300,9 @@
selects[i].addEventListener('change', function () { save(); });
}
// Enrichment keys save on blur/change (turns the workers on).
['tmdb-api-key', 'tvdb-api-key', 'omdb-api-key'].forEach(function (id) {
['tmdb-api-key', 'tvdb-api-key', 'omdb-api-key',
'fanart-api-key', 'opensubtitles-api-key',
'video-ryd-enabled', 'video-sponsorblock-enabled'].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.addEventListener('change', function () { saveKeys(); });
});