Skip to main content

mediaq

Hybrid CLI + REST API that searches media sites through their native search, sort and filter capabilities and resolves results into direct media URLs — optionally downloading them. Both halves share one core: the CLI runs in-process by default, can manage the server (mediaq server start|stop|status), and can drive a running server with --remote.

Stack: Python 3.12 · uv · Typer (CLI) · FastAPI/uvicorn (REST) · wreq (browser-emulated HTTP) · selectolax/Lexbor (HTML) · Pydantic v2 · SQLite (jobs & results) · YAML config.

Sites covered are adult sites. Respect their terms of use and rate limits; defaults are deliberately conservative (1 request/s, 2 concurrent per host).

Install

uv sync                      # development
uv run mediaq --help

uv tool install .            # or: install the `mediaq` command globally

Quick start

mediaq sites list                                   # capability matrix
mediaq sites test xvideos -q blonde                 # live smoke test of one adapter
mediaq --json sites list                            # global --json: JSON output everywhere
mediaq search "blonde" -s xvideos -s pornhub --sort newest --quality hd
mediaq search "blonde" --all --pages 2 -f jsonl -o results.jsonl --save
mediaq fetch --from results.jsonl                   # resolve direct media URLs
mediaq fetch "https://www.xvideos.com/video.abc/x" --download --dest ./dl --quality 720p
mediaq jobs list && mediaq jobs results <job-id> -f csv

Search options map onto each site's own parameters (--sort, --quality, --period, --orientation, --min-duration, --max-duration); anything a site cannot honour is dropped with a warning (--strict fails instead). Raw native parameters pass through with --extra key=value, and native query syntax (booru tags, nhentai filters, site: on NudeVista) goes straight into the query string.

fetch accepts page URLs, site:id / site:kind:id refs, --from a search output file, or --job a saved search. Aggregator results (PornMD, TubeGalore, iXXX, NudeVista) are resolved through the adapter of the site they point at.

Server

mediaq server start                 # detached uvicorn on 127.0.0.1:8765, pidfile + log file
mediaq server status | logs -f | restart | stop
mediaq server start --foreground    # run in the terminal

curl -s localhost:8765/health
curl -s -X POST localhost:8765/jobs/search -H 'content-type: application/json' \
     -d '{"query":"blonde","sites":["nhentai","rule34"],"pages":1}'
mediaq --remote http://127.0.0.1:8765 jobs list

Endpoints: GET /health, GET /sites[/{id}], POST /search (sync, ≤5 pages), POST /fetch, POST /jobs/search|fetch|download (async), GET /jobs[/{id}[/results|/downloads]], POST /jobs/{id}/cancel, DELETE /jobs/{id}, POST /jobs/purge, GET /downloads/{id}, POST /admin/shutdown (loopback-only; bearer token when server.admin_token is set). OpenAPI docs are served at /docs.

Configuration

~/.config/mediaq/config.yaml (or ./mediaq.yaml, --config PATH, MEDIAQ_CONFIG). Environment variables override the file: MEDIAQ_SERVER__PORT=9000. See config/mediaq.example.yaml for every key, and use mediaq config init|show|set to manage it.

Per-site settings live under sites.<id>: enabled, cookies (e.g. cf_clearance), user_agent, api_key/user_id (Rule34, Gelbooru, nhentai).

Sites

Generated by uv run python scripts/site_matrix.py.

id site kinds default sorts filters notes
pornhub Pornhub gallery, gif, video on duration, newest, rating, views period, min-duration, max-duration
xvideos XVideos video on duration, newest, random, rating, views quality, period, min-duration, max-duration
xnxx XNXX video on random, views quality, period, min-duration, max-duration
xhamster xHamster gallery, video on duration, newest, rating, views quality, period, orientation, min-duration, max-duration
youporn YouPorn video on - -
redtube RedTube video on duration, rating, views period
spankbang SpankBang video on newest, views quality, period, min-duration
eporner Eporner video on duration, newest, rating, views quality, period, orientation
noodlemagazine NoodleMagazine video on duration, newest quality, min-duration, max-duration
hqporner HQPorner video on - -
redgifs RedGIFs gif, image on newest, rating, views period
pornmd PornMD video on - - aggregator (resolves via source site)
tubegalore TubeGalore video on - - aggregator (resolves via source site)
ixxx iXXX video on - - aggregator (resolves via source site)
nudevista NudeVista gallery, video on newest, random, views - aggregator (resolves via source site); native query syntax: site:<domain> restricts to one source site
imagefap ImageFap gallery on newest -
pornpics PornPics gallery on - -
sexcom Sex.com gif, image, video on - orientation
rule34 Rule34.xxx gif, image, video on newest, random, rating - native query syntax: space-separated tags; -tag excludes; `rating:explicit
gelbooru Gelbooru gif, image, video on newest, random, rating - native query syntax: space-separated tags; -tag excludes; `rating:explicit
nhentai nhentai gallery on newest, rating, views period native query syntax: "exact phrase", -word, tag:"big breasts", artist:x, language:english, pages:>10, favorites:>=100, uploaded:<7d

Notes on what could be verified during development:

  • Pornhub serves HLS renditions to guests; progressive MP4 (get_media) is usually empty. GIFs and album photos resolve to direct files.
  • xHamster and HQPorner sign progressive URLs against the client IP; downloads fail with 403 Wrong key / 404 from networks whose egress IP differs per destination. HLS masters work. HLS-only sources are recorded but not downloaded in v1.
  • XVideos/XNXX offer 240p/360p MP4 directly; higher qualities are HLS.
  • Sex.com videos resolve to an HLS master plus a 240p MP4 preview (the page shows a regional age-verification overlay, but the media URLs are still exposed); GIFs are .webp animations.
  • Interrupted downloads leave a .part file that the next attempt resumes with a Range request.
  • Cloudflare challenges are detected and retried with backoff; a persistent challenge marks that site as failed for the request while other sites still return results.

Output formats

--format table|json|jsonl|csv, -o FILE. JSON Lines output is accepted back by fetch --from. SearchResult/FetchResult JSON includes per-site errors and warnings.

Development

scripts/check.sh                       # ruff format --check, ruff check, mypy, pytest
uv run pytest -m network               # opt-in live checks against every site
uv run python scripts/capture_fixtures.py [site ...]   # raw captures for parser drift
uv run python scripts/trim_fixture.py IN.html OUT.html 'css selector' ...

Adapters live in src/mediaq/sites/ (one module per site, shared mixins prefixed with _). Each adapter is a pure parser (parse_search, parse_detail) plus URL building, tested against trimmed HTML/JSON fixtures in tests/fixtures/<site>/. PLAN.md documents the research findings and the implementation log.

Hermes plugin

plugins/mediaq/ wraps this CLI as a Hermes plugin: mediaq_search, mediaq_fetch, mediaq_sites, mediaq_jobs and mediaq_config tools, three bundled skills, and a /mediaq slash command. It shells out to the installed mediaq binary, so Hermes' venv stays independent of this project's dependencies.

uv tool install .
hermes plugins install ./plugins/mediaq && hermes plugins enable mediaq

See plugins/mediaq/README.md.

Release files for mediaq 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mediaq 0.1.0
File Size Uploaded
mediaq-0.1.0.tar.gz 88.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mediaq 0.1.0
File Interpreter ABI Platform
mediaq-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 220.4 kB

Release files / mediaq-0.1.0.tar.gz

Download URL mediaq-0.1.0.tar.gz
Size 88.3 kB
Tags Source
SHA-256 checksum
How to use checksums
7d744dda792023029756b168d42104154967b25de0446cc44e623975443a2cad
BLAKE2b-256 checksum
How to use checksums
97eb21b5a44b2a4b6cc3288b9872b2b9374c20ac4a4b101f11de42eb3d995441
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / mediaq-0.1.0-py3-none-any.whl

Download URL mediaq-0.1.0-py3-none-any.whl
Size 132.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
828644b33f89744ad8cf77363d40cf392177e1a40662bab276906c54f488190b
BLAKE2b-256 checksum
How to use checksums
11c3ccbfebbf790859dc4bcbaa57e033c8dcf30f77eb13eeffe4ac3caead9891
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page