MediaInfo: fetch titles, synopses and artwork from public metadata APIs - #37
Merged
Conversation
…ata APIs
VuIO indexed media purely from local sources: symphonia read audio tags, and
video files got no metadata at all — the scanner gates extraction on
`audio/`, so a movie was a filename with no title, synopsis, year or artwork.
Cover art existed only for audio, was re-read from disk on every request, and
was never cached.
This adds a MediaInfo section to the dashboard's Admin tab with a button that
looks the whole library up against public metadata services, and feeds the
results through to the dashboard, to DLNA ContentDirectory, and to
/media/{id}/cover.
Ten providers. Five answer without an account and are on by default — TVmaze,
MusicBrainz (with Cover Art Archive for artwork), Jikan, AniList and Kitsu.
Five more are listed but idle until a credential is saved: TMDb, OMDb,
Discogs, Last.fm and Genius.
Notable decisions:
- Credentials live in the `secrets` table, not config.toml. Under Docker the
configuration is built from environment variables and the admin API refuses
to write the file, so a token kept there would be unsettable in exactly the
deployment most likely to need one.
- Fetched records go in their own `mediainfo` table rather than `media_tags`,
which is cleared and rewritten on every rescan. What a provider said is not
re-derivable from the file, so it has to survive a scan; it still goes when
the file does, via the cascade.
- Matches are scored 0-100 and only shown above the configured threshold.
Weaker ones are stored and listed in the Admin tab for review rather than
used, because relabelling a film called Arrival as "Dead on Arrival" is
worse than showing the filename.
- Requests are paced per provider to each publisher's documented limit.
MusicBrainz enforces one request per second per IP at the server, which
makes it the slowest path by design rather than by accident.
- reqwest 0.13 is behind the default-on `mediainfo` feature. http_client.rs
is left alone: it is deliberately cleartext with no TLS, redirects or name
resolution, and all three are required here.
Schema goes to v3; the migration only adds a table, so an upgraded file
behaves exactly as before until someone presses Fetch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
VuIO indexes media purely from local sources. Symphonia reads audio tags, but video files get no metadata at all —
media/scanner.rsgates extraction onaudio/, so a movie or episode is a filename with no title, synopsis, year or artwork. Cover art exists only for audio, is re-read from disk on every request, and is never cached.ideas/ideas.txt:34has named this for a while ("Online Metadata Providers Integration"); nothing for it existed in the codebase.What
A MediaInfo section in the Admin tab: per-provider credential fields, a Fetch media info for entire library button, live progress with a cancel button, and a review list of uncertain matches. Results flow through to the dashboard, to DLNA ContentDirectory, and to
/media/{id}/cover.Ten providers. Five need no account and are on by default; five are listed but idle until you save a credential.
Design decisions worth reviewing
secretstable, notconfig.toml. This is functional, not stylistic: under Docker the config is env-derived andPOST /api/admin/configreturns 409, so a token in the file would be unsettable in exactly the deployment most likely to need one. It also keeps tokens out of files people paste into bug reports. A test asserts the status endpoint never echoes a stored token back through any key.mediainfotable, notmedia_tags.media_tagsis cleared and rewritten on every rescan, because it holds what the file claims and the file is the authority on that. This holds what somebody else said, which no amount of re-reading the file reproduces.ON DELETE CASCADEstill cleans up when the file goes. There's a test for it.min_confidenceis filtered out of browse and DIDL, and listed in the Admin tab instead.reqwest 0.13behind the default-onmediainfofeature.http_client.rsis untouched — it is deliberately cleartext with no TLS, redirects or name resolution, and all three are required here.--no-default-featuresbuilds clean with the feature off.Verified against the live APIs
A real fetch over a 4-file library:
A SOAP
Browsereturns real titles,<dc:description>,<upnp:genre>and<upnp:albumArtURI>, and/media/{id}/coverreturns actual JPEGs for video — that endpoint returned 404 for video before this change.Two bugs the live run caught and this PR fixes: low-confidence matches were being displayed, and episode titles lost their series name ("Breakage" rather than "Breaking Bad — Breakage").
Known limitation
Non-anime movies have no free source. TVmaze is TV-only and Jikan/AniList/Kitsu are anime, so a film can only score a spurious match against the key-free set — which is why Arrival lands in the flagged list rather than being trusted. Movies work once a TMDb or OMDb key is saved. This is inherent to the free-provider set.
Testing
455 tests pass. New coverage: filename parser and confidence scorer, all ten provider response parsers against recorded JSON fixtures (no network in tests), repository round-trips, a v1→v3 migration assertion, rescan-survival, the credential-leak assertion, and endpoint tests via
tower::oneshot.