A custom metadata provider that supplies Dutch film descriptions from VPRO Cinema to Plex Media Server.
Note: As of v4.0.0, this provider only supports movies. TV series support has been removed because VPRO's data sources don't provide complete series metadata (seasons, episodes, episode descriptions), which caused Plex scanning failures.
See how it works.
- 🇳🇱 Dutch film reviews/descriptions from VPRO Cinema's database
- 🎬 Movies only (TV series not supported — see note above)
- 🔞 Kijkwijzer content ratings (Dutch age classification: AL, 6, 9, 12, 14, 16, 18)
- 🔍 Direct NPO POMS API access
- 🌍 Smart title matching via TMDB — works in both directions (Translated → Original and Original → Translated)
- 🔎 Cinema.nl fallback with IMDB verification when POMS API returns no results
- 📦 TMDB fallback metadata — movies not in VPRO can still be added to Plex with basic info
- 💾 Persistent caching (with short 1-hour TTL for not-found entries)
- 🐳 Docker-ready with health checks
- 🔗 Combines with other providers (returns description + content rating by default)
- ⚙️ Configurable: optionally return VPRO images and/or ratings
For years I wanted to automatically pull the excellent Dutch film reviews from VPRO Cinema (formerly vprogids.nl/cinema) into Plex. I made several attempts over the years, but without an official NPO API, I never got it to work. After getting tired of manually copying descriptions into Plex — only to have them overwritten by the next metadata refresh — I teamed up with Claude to build a proper solution. After some experimentation (first with scraping, then reverse-engineering the NPO's internal POMS API), I finally got a working Plex agent! I decided to share it with the community, hoping it can help others too.
Feel free to use, fork, and contribute, but note that the API is not officially supported by NPO, so the approach is technically a bit dodgy and may break at any time. Though it has been working excellently for me, so far!
Required:
- Docker and Docker Compose — Install Docker
- Plex Media Server 1.40+ — Uses the new Custom Metadata Providers API
Recommended:
-
TMDB API Key — Enables smart alternate title lookup. Many films are indexed in VPRO Cinema under their original ( often French, German, or Dutch) title rather than the English title. With a TMDB API key, the provider automatically discovers and tries alternate titles in both directions:
- English → Original: "Downfall" → "Der Untergang" (via IMDB ID from Plex)
- Original → English: "Der Untergang" → "Downfall" (via TMDB title search)
Get your free API key at: https://www.themoviedb.org/settings/api
git clone https://github.com/c-kick/vpro-cinema-plex.git
cd vpro-cinema-plex
cp env.example .envEdit .env to add your TMDB API key (optional but recommended):
TMDB_API_KEY=your_tmdb_api_key_heredocker-compose up -dcurl "http://localhost:5100/health"
curl "http://localhost:5100/test?title=Apocalypse+Now&year=1979"Alternative: Add to existing Docker stack (Portainer)
Portainer often can't access local build contexts. Build the image on your server first:
cd /path/to/vpro-cinema-plex
docker build -t vpro-plex-provider:latest .Add to your stack:
vpro-plex-provider:
image: vpro-plex-provider:latest
pull_policy: never
container_name: vpro-plex-provider
restart: unless-stopped
ports:
- "5100:5100"
environment:
- TZ=Europe/Amsterdam
- LOG_LEVEL=INFO
- TMDB_API_KEY=your_tmdb_api_key_here # Optional but recommended
- CACHE_DIR=/app/cache
- POMS_CACHE_FILE=/app/cache/credentials.json
- VPRO_RETURN_SUMMARY=true # Dutch descriptions (main feature)
- VPRO_RETURN_CONTENT_RATING=true # Kijkwijzer age ratings
- VPRO_RETURN_IMAGES=false # Set to true to use VPRO posters
- VPRO_RETURN_RATING=false # Experimental: VPRO ratings (see limitations)
volumes:
- /path/to/vpro-cinema-plex/cache:/app/cache
networks:
- your-plex-network # Must be on same network as PlexNote: if you use the plex network, you can use 'vpro-plex-provider' (instead of localhost, as in the examples below)
to directly reference the agent in the provider URL in Plex: http://vpro-plex-provider:5100/movies
Important: Replace
localhostwith your server's IP if Plex runs on a different host.
| Endpoint | Provider Name | Use For |
|---|---|---|
http://localhost:5100/movies |
VPRO Cinema (Dutch Summaries) | Movies |
- In Plex, go to Settings → Metadata Agents → Metadata Providers
- Click + Add Provider, paste
http://localhost:5100/movies, save
Movie Agent:
- Under Metadata Agents, click + Add Agent
- Title: "VPRO + Plex Movie"
- Primary provider:
VPRO Cinema (Dutch Summaries) - Add "Plex Movie" as additional provider (click +)
- Optionally add "Plex Local Media"
- Save
- Settings → Manage Libraries → click
...next to library → Edit Library - Advanced tab → Agent → select your new agent
- Save and repeat for other movie libraries
For existing content: Select items → ... → Refresh Metadata
New content will automatically use the provider on scan.
When configured as a Metadata Provider alongside Plex Movie (see Plex Configuration), the VPRO agent runs a lookup cascade while Plex Movie fetches its metadata in parallel. Results are merged with VPRO as primary — meaning Dutch summaries and Kijkwijzer ratings take precedence. Everything VPRO doesn't return (posters, cast, genres, etc.) is filled in by Plex Movie automatically.
# Basic search
docker exec vpro-plex-provider python vpro_lookup.py "Apocalypse Now" --year 1979
# With IMDB ID + verbose output
docker exec vpro-plex-provider python vpro_lookup.py "Downfall" --year 2004 --imdb tt0363163 -v
# Test cinema.nl fallback directly (bypass POMS API)
docker exec vpro-plex-provider python vpro_lookup.py "Der Untergang" --year 2004 --skip-poms -v# Test search
curl "http://localhost:5100/test?title=Le+dernier+métro&year=1980"
# Test cinema.nl fallback directly (bypass POMS API)
curl "http://localhost:5100/test?title=Der+Untergang&year=2004&skip_poms=1"
# Plex metadata endpoint
curl "http://localhost:5100/movies/library/metadata/vpro-apocalypse-now-1979-tt0078788-m"
# Cache operations
curl "http://localhost:5100/cache"
curl "http://localhost:5100/cache?key=vpro-apocalypse-now-1979-tt0078788-m"
curl -X POST "http://localhost:5100/cache/clear"
curl -X POST "http://localhost:5100/cache/delete?key=vpro-apocalypse-now-1979-tt0078788-m"
curl -X POST "http://localhost:5100/cache/delete?pattern=apocalypse"The POMS API uses hardcoded default credentials that have been working reliably.
# View cached credentials
docker exec vpro-plex-provider cat cache/credentials.jsonNote: Credential auto-refresh from vprogids.nl is no longer functional since the migration to cinema.nl, but the default credentials continue to work with the POMS API.
docker-compose logs -f| Variable | Default | Description |
|---|---|---|
PORT |
5100 | Server port |
LOG_LEVEL |
INFO | DEBUG, INFO, WARNING, ERROR |
CACHE_DIR |
./cache | Cache directory path |
TMDB_API_KEY |
(none) | TMDB API key for alternate title lookup |
POMS_CACHE_FILE |
./credentials.json | Path to cached POMS credentials |
VPRO_RETURN_SUMMARY |
true | Return VPRO Dutch summary/description |
VPRO_RETURN_CONTENT_RATING |
true | Return Kijkwijzer content rating (AL, 6, 9, 12, 14, 16, 18) |
VPRO_RETURN_IMAGES |
false | Return VPRO images (recommended for Dutch films — better poster coverage than TMDB) |
VPRO_RETURN_RATING |
false | Return VPRO rating (experimental, see Limitations) |
| Endpoint | Method | Description |
|---|---|---|
/movies |
GET | Movie provider info (type 1) |
/movies/library/metadata/<key> |
GET | Plex metadata lookup for movies |
/movies/library/metadata/matches |
POST | Plex match endpoint for movies |
/movies/library/metadata/<key>/images |
GET | Returns VPRO images if enabled, otherwise empty |
/movies/library/metadata/<key>/extras |
GET | Returns empty (no extras) |
/health |
GET | Simple health check (version only) |
/health/ready |
GET | Detailed health with checks, cache stats, config |
/health/live |
GET | Liveness probe (always returns ok) |
/test |
GET | Test search: ?title=X&year=Y&imdb=ttZ&skip_poms=1&skip_tmdb=1 |
/cache |
GET | List cached entries or view specific: ?key=X |
/cache/clear |
POST | Clear all cached entries (preserves credentials) |
/cache/delete |
POST | Delete specific entries: ?key=X or ?pattern=X |
vpro-cinema-plex/
├── docker-compose.yml # Docker Compose config
├── Dockerfile # Container definition
├── env.example # Environment template (copy to .env)
├── requirements.txt # Python dependencies
│
├── vpro_metadata_provider.py # Flask HTTP server for Plex
├── vpro_lookup.py # Search orchestrator + CLI
├── poms_client.py # NPO POMS API + TMDB clients
├── vpro_scraper.py # Cinema.nl fallback + page scraper
├── models.py # Shared data models (VPROFilm)
│
├── cache.py # Disk cache with sharding
├── credentials.py # POMS credential management
├── http_client.py # HTTP session factory
├── text_utils.py # Title matching utilities
├── logging_config.py # Logging configuration
├── metrics.py # Simple metrics collection
├── constants.py # Shared constants
│
├── LICENSE # MIT License
└── README.md # This file
| Problem | Solution |
|---|---|
| Provider not in Plex | Verify running: curl http://localhost:5100/health. Check network from Plex to provider. |
| No Dutch descriptions | Test film exists: docker exec vpro-plex-provider python vpro_lookup.py "TITLE" --year YEAR -v. Check logs: docker-compose logs --tail=100. Clear cache and retry. |
| Metadata not updating after port change | Restart Plex server (known Plex bug with URL changes). |
| POMS auth errors | Default credentials should work. If issues persist, check NPO API availability |
| Film not found | Try original title: "Der Untergang" instead of "Downfall". Or provide IMDB ID: --imdb tt0363163 |
| TMDB alternate titles not working | Verify "configured": true and "status": "ok" under tmdb in /health/ready response. |
| Still it's not working | Restart your Plex server. |
Standalone docker-compose:
git pull && docker-compose down && docker-compose build --no-cache && docker-compose up -dPortainer stack:
cd /path/to/vpro-cinema-plex
git pull
docker build -t vpro-plex-provider:latest .
# Then redeploy the stack in PortainerVerify: curl http://localhost:5100/health
Cache and .env are preserved during updates.
Upgrade notes for specific versions
TV series support has been removed. Only movies are now supported.
Migration:
- Remove the series provider from Plex (
http://localhost:5100/series) - Remove any TV Show agents that used VPRO as primary provider
- For TV libraries, switch to standard Plex Series agent
- Clear cache:
curl -X POST "http://localhost:5100/cache/clear"
| Old | New |
|---|---|
http://localhost:5100/ |
http://localhost:5100/movies |
http://localhost:5100/tv |
http://localhost:5100/series |
Provider names also changed (added - Movies / - Series suffix).
Migration: Remove old providers in Plex, add new URLs, update agents, restart Plex.
Single provider → two providers (/movies and /series). Required by Plex API for proper secondary provider combining.
Migration: Remove old provider, add both new URLs, create separate TV Show agent.
- Poster extraction from Cinema.nl — Now extracts the main movie poster from the top of Cinema.nl pages (marked as PROMO_PORTRAIT), in addition to stills from the Afbeeldingen section. Uses alt-text matching and URL heuristics to identify posters reliably.
- Fixed dead vprogids.nl image URLs — POMS API returns old vprogids.nl URLs that now return HTTP 410 Gone. The provider now converts these to cinema.nl URLs and scrapes for fresh images.vpro.nl URLs.
- Proper image embedding in metadata — Images are now embedded directly in the metadata response (thumb, art, Image array) per the Plex TMDB example, not just via the /images endpoint.
- Conditional images feature — The provider only declares the "images" feature when
VPRO_RETURN_IMAGES=true. When disabled, Plex correctly falls back to secondary agents (Plex Movie) for artwork. - Rating array support — VPRO ratings are now returned in the Rating array format with
cinemanl://image.ratingscheme. Note: Plex UI only displays ratings with recognized schemes (imdb://, rottentomatoes://), so the rating is stored but won't show an icon. - WebP to JPEG conversion — Image URLs are converted from .webp to .jpg for better Plex compatibility.
- Correct Plex image types — Fixed image type mapping to use Plex's expected values (
coverPoster,background) instead of incorrect ones (poster,art).
⚠️ TV series support removed — VPRO's data sources don't provide complete series metadata (seasons, episodes, episode descriptions), which caused Plex scanning failures. This provider now only supports movies.- TMDB fallback metadata — When a movie isn't found in VPRO sources, the provider now returns basic metadata from TMDB (title, year, IMDB ID) so Plex can still add the movie. The description is omitted to let secondary providers (Plex Movie) fill it in.
- Reduced not-found cache TTL — Changed from 7 days to 1 hour to allow quicker retries for newly indexed content
- Simplified API — Removed
/seriesendpoint and all TV-related routes
Migration from v3.x:
- Remove the series provider from Plex (
http://localhost:5100/series) - Remove any TV Show agents that used VPRO as primary provider
- For TV libraries, switch to standard Plex Series agent
- Clear cache:
curl -X POST "http://localhost:5100/cache/clear"
- Cinema.nl direct scraper — Replaced DuckDuckGo/Startpage web search with direct cinema.nl scraping
- IMDB verification — Cinema.nl fallback now verifies matches using IMDB IDs for reliable matching
- Image extraction — Cinema.nl fallback extracts high-resolution images from the Afbeeldingen section
- Migration complete — vprogids.nl/cinema has fully migrated to cinema.nl
- Search optimizations — Year-in-query and model=cinema parameter for better search ranking
- Circuit breaker — Prevents hammering cinema.nl after repeated failures
- Credential refresh deprecated — Auto-refresh from vprogids.nl no longer works (site returns 404)
- Kijkwijzer content ratings — Dutch age classification (AL, 6, 9, 12, 14, 16, 18) now extracted from POMS API
- Configurable metadata fields — New environment variables to control what metadata is returned:
VPRO_RETURN_SUMMARY(default: true) — Dutch descriptionsVPRO_RETURN_CONTENT_RATING(default: true) — Kijkwijzer ratingsVPRO_RETURN_IMAGES(default: false) — VPRO poster imagesVPRO_RETURN_RATING(default: false) — VPRO appreciation ratings (experimental, see Limitations)
- Fix Match thumbnails — Images now display in Plex's Fix Match dialog when
VPRO_RETURN_IMAGES=true - Health endpoint improvements —
/health/readynow shows configured feature flags - Selective cache deletion — New
/cache/deleteendpoint for targeted cache management
- Added debug logging for troubleshooting
- Docker environment variable passthrough improvements
- Breaking URL changes:
/→/movies,/tv→/series - Provider name suffix changes (
- Movies/- Series)
- Two-provider architecture for proper Plex secondary agent combining
- Added series/TV show support (removed in v4.0.0)
- Movies only — TV series support was removed in v4.0.0 because VPRO's data sources don't provide complete series metadata
- POMS API is undocumented — Not officially supported by NPO; may change without notice
- Not all content covered — Only films reviewed by VPRO Cinema; movies not found get basic TMDB fallback metadata
- Artwork optional — Disabled by default; enable
VPRO_RETURN_IMAGESor use Plex Movie fallback - Ratings display limited — Plex's Custom Metadata Provider API may store
audienceRatingvalues, but the rating icon displayed in the UI is controlled by the library's "Ratings Source" setting (Rotten Tomatoes, IMDb, or TMDb), not by the provider. CustomratingImageURI schemes are not supported. This is a Plex architectural limitation — see the Plex Dev/API Forum for updates - Credential refresh broken — vprogids.nl/cinema has migrated to cinema.nl; auto-refresh no longer works but default credentials still function
- Cinema.nl fallback — Direct scraping; may break if site structure changes
MIT — Do whatever you want with it.
- VPRO Cinema for the Dutch film reviews
- TMDB for alternate title data
- Klaas (c_kick/hnldesign) — Original idea and development
- Claude (Anthropic) — Implementation assistance
