Skip to main content

Search and download German public-broadcasting media from MediathekViewWeb, with Plex-friendly season downloads.

Project description

mvw

CI PyPI Python

A command-line tool for searching and downloading content from MediathekViewWeb (MVW), the index of German public-broadcasting media libraries (ARD, ZDF, WDR, and more). Built for automation: the headline feature is reliable, Plex-friendly season downloads.

Install

Requires Python ≥ 3.11. The distribution is published as mvw-cli (the PyPI name mvw was already taken); the installed command is mvw.

Install as a standalone tool with uv:

uv tool install mvw-cli      # adds the `mvw` command to your PATH

Or run it once without installing:

uvx --from mvw-cli mvw search "#Tatort"

With pip:

pip install mvw-cli

Note: HLS (.m3u8) downloads require ffmpeg on your PATH. It is an external (non-Python) dependency and is not installed automatically.

From source

uv sync                      # create the dev environment
uv run mvw --help            # run from the working tree

Query grammar

The query string follows the MediathekViewWeb syntax:

Prefix Field searched Example
! channel !ARD
# topic #Tatort
+ title +Schokolade
* description *Berlin
(none) topic and title feuer flamme
>N duration > N minutes >80
<N duration < N minutes <10

Combination rules:

  • Space between different selectors → AND: !WDR #Tatort means channel=WDR AND topic=Tatort.
  • Same selector repeated → OR: !ARD !ZDF means ARD or ZDF.
  • Comma within a selector's value → AND of words: #Olympia,Tokio matches topic containing both "Olympia" and "Tokio".
  • No negation operator. Exclusion is done client-side with --exclude.

Note: the API is case-insensitive and flexible with umlauts (öoeOE).

Commands

mvw search

Search MVW and display a Rich results table.

mvw search QUERY
           [--channel C] [--topic T] [--title T] [--description D]
           [--min-duration MIN] [--max-duration MAX]
           [--sort timestamp|duration|channel] [--order asc|desc]
           [--future] [--limit N] [--offset N] [--json]
Option Default Description
--channel Filter by channel (structured flag, not query syntax)
--topic Filter by topic
--title Filter by title
--description Filter by description
--min-duration Minimum duration in minutes
--max-duration Maximum duration in minutes
--sort timestamp Sort field
--order desc Sort order (asc or desc)
--future off Include not-yet-aired entries
--limit 15 Number of results to fetch
--offset 0 Pagination offset
--json off Emit raw JSON to stdout (scripting-friendly)

Example:

mvw search "#Tatort !ARD >80"

mvw download

Search and download matching entries. Run --dry-run first to preview the exact file tree before downloading anything.

mvw download QUERY
             [--channel C] [--topic T] [--title T]
             [--min-duration MIN] [--max-duration MAX]
             [--season] [--dry-run]
             [--resolution low|medium|high|best]
             [--output DIR] [-o DIR] [--template STR]
             [--exclude TERM ...] [--dedup] [--latest-season]
             [--season-number N] [--subtitles] [--force] [--limit N]
Option Default Description
--channel Filter by channel
--topic Filter by topic
--title Filter by title
--min-duration Minimum duration in minutes
--max-duration Maximum duration in minutes
--season off Group into Plex season folders using S##E## numbering
--dry-run off Preview the file tree and source URLs; download nothing
--resolution best Resolution preference: low, medium, high, or best
--output, -o config default Output directory
--template Plex default Custom filename template (see below)
--exclude Regex to exclude entries from title/topic/description (repeatable)
--dedup off Remove near-duplicate entries, keeping the highest-quality copy
--latest-season off Keep only entries from the highest detected season
--season-number Override detected season number
--subtitles off Also fetch subtitles, saved as <video>.de.<ext> sidecars
--limit 200 Maximum number of entries to resolve
--force off Redownload every matched entry even if a valid file already exists

Download behavior and verification

By default, download checks each target path before fetching:

  • an existing file is checked by probing the actual size of the version that would be downloaded (an HTTP HEAD, falling back to a ranged request) and comparing it to the file on disk; a match → ✓ … already exists, skipped, a mismatch → ⚠ … corrupted (size mismatch), redownloading;
  • if the size cannot be determined (or for HLS .m3u8, which has no single size), any non-empty existing file is treated as complete.

Each fresh download is verified against the server's Content-Length before the temporary .part file is promoted to its final name (this now applies to HLS too, which is muxed to a .part and promoted only after ffmpeg succeeds), so an interrupted transfer never leaves a corrupt final file. A run ends with a summary line, e.g. Done: 8 downloaded, 5 skipped, 2 redownloaded, 1 failed.

Filename template

The default template produces Plex/Jellyfin-compatible paths:

{series} ({year})/Season {s:02d}/{series} ({year}) - s{s:02d}e{e:02d} - {ep_title} [{res}].{ext}

Override with --template. Available tokens:

Token Value
{series} Topic (show name)
{year} Broadcast year
{s} Season number (supports :02d formatting)
{e} Episode number (supports :02d formatting)
{ep_title} Cleaned episode title
{res} Resolution label (see note below)
{channel} Broadcaster
{date} Broadcast date (YYYY-MM-DD)
{ext} File extension

Season/episode numbers are parsed from titles (S07/E05, Staffel 7 … Folge 5, Folge 5, trailing (5)); entries without a parseable season fall back to their broadcast year. Entries with no broadcast date at all land in Season 00 (Plex's "Specials") with {year} and {date} rendered as unknown — not the epoch year 1970.

{res} label note: MVW exposes only three tiers (low / medium / high), not measured pixel heights. The {res} token maps these to conventional labels — high → "1080p", medium → "720p", low → "480p" — because Plex parses these and they reflect typical public-broadcast encodes. These are labels, not guarantees of exact resolution.

ffmpeg requirement for HLS

Some entries serve .m3u8 HLS playlists instead of direct .mp4 files. Those are downloaded via ffmpeg -i <url> -c copy <dest>. If ffmpeg is not on your PATH and an HLS entry is encountered, mvw exits with code 4 and prints an install hint. Install from https://ffmpeg.org/download.html.

Flagship example: Feuer und Flamme

# Preview the newest season, no audio description, deduped
mvw download "#Feuer und Flamme" --season --latest-season --dedup \
    --exclude Audiodeskription --exclude "Gebärdensprache" \
    --output ~/Media/TV --dry-run

# Then download for real in best resolution
mvw download "#Feuer und Flamme" --season --latest-season --dedup \
    --exclude Audiodeskription --output ~/Media/TV

mvw info

Show a Rich detail panel for the first match of a query.

mvw info QUERY

Displays: topic, title, description, channel, aired datetime, duration, size, available resolutions with URLs, subtitle URL, website URL, and detected season/episode.

mvw config

Manage persistent configuration stored in config.toml (location: platformdirs.user_config_dir("mvw")).

mvw config show              # Print the effective config (key = value)
mvw config set KEY VALUE     # Write a key to config.toml
mvw config path              # Print the path to config.toml

Available keys: download_dir, template, resolution, user_agent, page_size, request_timeout.

CLI flags always override config file values, which override built-in defaults.

mvw --version

Print the installed version (-V for short).

Exit codes

Code Condition
0 Success or no results
2 Invalid input or config (bad template, regex, config file/key/value), API error, HTTP error, or network failure after retries
4 HLS entry encountered but ffmpeg is not installed
5 Partial/interrupted download failure

Contributing and releasing

Development setup, quality checks, and the release process are documented in CONTRIBUTING.md. Notable changes are tracked in CHANGELOG.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mvw_cli-0.3.0.tar.gz (33.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mvw_cli-0.3.0-py3-none-any.whl (27.6 kB view details)

Uploaded Python 3

File details

Details for the file mvw_cli-0.3.0.tar.gz.

File metadata

  • Download URL: mvw_cli-0.3.0.tar.gz
  • Upload date:
  • Size: 33.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for mvw_cli-0.3.0.tar.gz
Algorithm Hash digest
SHA256 913356baec67ecd46b30e562e3686e71cb976fededf42dd5e37c79a7fc662ab6
MD5 b76986a1639c151a564449aa1e016c52
BLAKE2b-256 d405dcb66bbedaa074cd4073382f4ff619d0b9cf361da26b5b6f548da482b17c

See more details on using hashes here.

File details

Details for the file mvw_cli-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: mvw_cli-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 27.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for mvw_cli-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5914da23341075cb37ff21a6ffcef5af3e8aee54eb83100f57df0097cb7b5846
MD5 3d1bd337cca0e700de6ffd1dd9a04722
BLAKE2b-256 0727ffbbf0ae2df2edde0c8cf9de89ebd7669ca7fba628908f506cdacf15a952

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page