Skip to content

Repository files navigation

IWantIt

iwantit is a config-driven CLI that turns a URL, text, or screenshot into a clean, repeatable media workflow. It is designed to be automation-friendly: it reads/writes JSON, works well in pipelines, and keeps side effects (downloads) isolated to explicit steps.

This repo currently focuses on:

  • Music search + downloads via Prowlarr (and Redacted enrichment)
  • Movies/TV dispatch to Radarr/Sonarr
  • Books via Prowlarr
  • Strong defaults for query cleanup, media type detection, and scoring

Quick start and integrations

Get productive first; customize later.

  1. Install and initialize:
uv tool install .
iwantit init

If your shell does not see iwantit yet:

uv tool update-shell
  1. Run a safe test:
iwantit run --text "Pink Floyd - Dark Side of the Moon"
  1. Allow side effects when you're ready:
iwantit run --text "Bernard Badie - Bones [2011]" --confirm

Integration points (all configurable in ~/.config/iwantit/config.yaml):

  • Prowlarr search + grab (music/books)
  • Goodreads Want to Read ingestion (CSV bootstrap + incremental RSS)
  • Radarr and Sonarr dispatch (movie/tv)
  • Web search providers (Kagi/Brave) for release verification
  • Optional tracker enrichment (Redacted)
  • Custom HTTP or external command steps

Local service names and endpoints for this workspace are documented in docs/local_media_services.md.

Optional dependencies

  • OCR uses tesseract. Install it if you want to pass screenshots via --image.

Help and automation essentials

iwantit --help
iwantit help overview
iwantit help config
iwantit help config --verbose
iwantit help json
iwantit help safety
iwantit help exit-codes
iwantit help errors
iwantit doctor

Key automation notes:

  • URL-like input is auto-detected even from stdin/JSON.
  • Output arrays are compact by default; use --full for full items.
  • Exit code 20 means decision.status=needs_choice.
  • --dry-run skips side effects; --confirm allows them.
  • iwantit run --batch file.jsonl --jobs 4 runs batches in parallel.
  • run_id and logs are included in output JSON for tracing.

Batch processing

Batch inputs can be a JSON array, JSONL, or plain lines:

iwantit run --batch inputs.jsonl --jobs 4

Goodreads Want to Read automation

Import a complete Goodreads export as a non-downloading baseline:

iwantit shelf sync goodreads --csv docs/goodreads_library_export.csv --limit 0

Queue the existing shelf only when you explicitly want a full backfill:

iwantit shelf sync goodreads --csv docs/goodreads_library_export.csv --backfill --dry-run

Poll the configured RSS shelf and process due ebook/audiobook legs:

iwantit shelf sync goodreads --confirm
iwantit shelf status
iwantit shelf retry

CSV imports, incremental identity, per-format completion, retries, and the systemd timer are documented in docs/goodreads_automation.md.

Doctor / setup checks

Validate configuration and connectivity:

iwantit doctor

Functional smoke tests

Run an offline end-to-end test harness with stubbed services:

python3 scripts/functional_test.py

Options:

  • --verbose prints progress
  • --keep-temp keeps the generated config directory

Logs and reports

Optional structured logs and human-readable reports:

logging:
  path: ~/.local/state/iwantit/run.log.jsonl
report:
  enabled: true

Common error codes are documented via:

iwantit help errors

Plugins

External steps can be discovered via plugins/ or IWANTIT_PLUGIN_PATH:

plugins:
  paths:
    - /path/to/iwantit-plugins

Plugin format (plugin.yaml):

name: my-plugin
version: 0.1.0
steps:
  my_step:
    command: ["python3", "-m", "my_plugin.steps.do_thing"]

Minimal config template

web_search:
  provider: kagi
  providers:
    kagi:
      api_key: ${ENV:KAGI_SEARCH_API_KEY}
prowlarr:
  url: http://localhost:9696
  api_key: CHANGE_ME
arr:
  radarr:
    url: http://localhost:7878
    api_key: CHANGE_ME
    root_folder: /media/movies
    quality_profile_id: 1
    endpoint: /api/v3/movie
  sonarr:
    url: http://localhost:8989
    api_key: CHANGE_ME
    root_folder: /media/tv
    quality_profile_id: 1
    endpoint: /api/v3/series
redacted:
  url: https://redacted.sh
  api_key: CHANGE_ME
goodreads:
  shelf_url: https://www.goodreads.com/review/list/151049665-travis?shelf=to-read
  formats: [ebook, audiobook]
  batch_limit: 10
  inventory:
    required: true
    sources:
      ebook:
        - {type: calibre_ssh, host: root@192.168.1.222, database: /mnt/user/visualmedia/Calibre/metadata.db}
        - {type: ssh, host: root@192.168.1.222, path: /mnt/user/visualmedia}
      audiobook:
        - {type: audiobookshelf_ssh, host: root@192.168.1.222, database: /mnt/user/appdata/audiobookshelf/config/absdatabase.sqlite}
        - {type: ssh, host: root@192.168.1.222, path: /mnt/user/audiobooks}

How it works (mental model)

The pipeline runs in two phases:

  1. Pre-steps (normalize + identify)

    • OCR (for screenshots)
    • URL fetching (pulls title/description)
    • Web search cleanup (fix typos, normalize artist/title/year)
    • Media type detection
    • Track-to-album resolution (if input looks like a song)
  2. Workflow steps (by media type)

    • Music/Books: Prowlarr search → filtering → ranking → decision → grab
    • Movies/TV: dispatch to Radarr/Sonarr

All steps are configured in YAML. You can replace any step with your own command.

Diagrams

Index

End-to-end overview

End-to-end overview

Sources: docs/diagrams/flow-overview.mmd and docs/diagrams/rendered/flow-overview.svg.

Pre-steps detail

Pre-steps detail

Sources: docs/diagrams/pre-steps.mmd and docs/diagrams/rendered/pre-steps.svg.

Music workflow

Music workflow

Sources: docs/diagrams/music-workflow.mmd and docs/diagrams/rendered/music-workflow.svg.

Book workflow

Book workflow

Sources: docs/diagrams/book-workflow.mmd and docs/diagrams/rendered/book-workflow.svg.

Movie and TV workflow

Movie and TV workflow

Sources: docs/diagrams/movie-tv-workflow.mmd and docs/diagrams/rendered/movie-tv-workflow.svg.

Decision and dispatch logic

Decision and dispatch logic

Sources: docs/diagrams/decision-and-dispatch.mmd and docs/diagrams/rendered/decision-and-dispatch.svg.

Install (uv)

Install uv first (see https://astral.sh/uv).

Quick test (no install)

From the repo root:

uv run python -m iwantit --help

User install (recommended)

uv tool install .

. means "install from this local project". This keeps dependencies isolated and exposes iwantit on your PATH. If your shell doesn't see it yet, run:

uv tool update-shell

Install from git (shareable)

uv tool install git+https://github.com/<you>/iwantit.git

Install from PyPI (after publishing)

uv tool install iwantit

Development

uv venv
source .venv/bin/activate
uv pip install -e .

Build & publish (when ready)

uv build
uv publish

Testing

Unit tests:

python3 -m unittest discover -s tests

Functional smoke tests (offline, stubbed providers):

python3 scripts/functional_test.py --verbose

CLI commands

iwantit init [--force]
iwantit run [--text|--url|--image|--json|--stdin] [--workflow name] [--dry-run] [--confirm] [--book-format ebook|audiobook|both]
iwantit step <step-name> [--text|--url|--image|--json|--stdin] [--book-format ebook|audiobook|both]
iwantit choose [--json|--stdin] [--interactive] [--select <idx|substring>]
iwantit shelf sync goodreads [--csv export.csv] [--backfill] [--dry-run|--confirm]
iwantit shelf status
iwantit shelf retry [--include-choices] [--include-uncertain]
iwantit shelf resolve <goodreads-book-id> --book-format ebook|audiobook --choice N --confirm
iwantit list workflows|steps
iwantit validate

Notes:

  • iwantit run returns exit code 20 when decision.status = needs_choice.
  • iwantit choose can emit --choice N flags for reruns.
  • URLs are auto-detected (CLI args, stdin, or JSON input) and treated as --url.
  • CLI output is compacted by default; use --full to emit full JSON.
  • Progress messages are written to stderr; use --quiet to suppress.
  • Dispatch steps are skipped unless you pass --confirm.

Output shape

Every run returns a JSON object:

{
  "request": { "input": "...", "input_type": "text|url|image", "query": "...", "media_type": "music|movie|tv|book" },
  "work": { "candidates": [], "selected": {} },
  "decision": { "status": "selected|needs_choice|error" },
  "search": { "kagi": {}, "prowlarr": {} },
  "dispatch": { "prowlarr": {}, "radarr": {} },
  "tags": {},
  "error": { "message": "...", "step": "...", "type": "..." }
}

Configuration

Config lives at ~/.config/iwantit/config.yaml (override with IWANTIT_CONFIG). Secrets live at ~/.config/iwantit/secrets.yaml (override with IWANTIT_SECRETS).

Pre-steps (default)

ocr -> fetch_url -> identify -> identify_web_search -> extract_release_preferences
-> determine_media_type -> resolve_track_release

fetch_url

For URL inputs, fetch_url grabs <title> and meta description, then uses that as the query. It also has a YouTube oEmbed fallback to avoid the generic "YouTube" title.

steps:
  fetch_url:
    builtin: fetch_url
    timeout: 15
    retries: 1
    retry_backoff_seconds: 0.5
    headers:
      User-Agent: Mozilla/5.0 ...
      Accept-Language: en-US,en;q=0.9

identify_web_search

Uses a web search provider (Kagi by default) to normalize artist/title/year. It does not blindly use the top result: it builds a consensus from the top N.

steps:
  identify_web_search:
    provider: kagi
    result_limit: 10
    min_confirmations: 2
    min_match_ratio: 0.4

determine_media_type

Uses keywords from web results plus a fallback heuristic (Artist - Title → music, etc.).

steps:
  determine_media_type:
    provider: kagi
    min_score: 2
    fallback: true

resolve_track_release (music only)

If the query looks like a track, it attempts to find the album/EP:

  1. Try to extract album from web snippets (e.g., "from the album ...")
  2. If that fails, run Redacted advanced search using artistname + filelist
  3. Rewrite the query to search the album/EP first
steps:
  resolve_track_release:
    release_priority: ["Album", "EP", "Single", "Live album"]

Prowlarr integration

Music and book workflows use Prowlarr for search + grab.

prowlarr:
  url: http://localhost:9696
  api_key: ${ENV:PROWLARR_API_KEY}
  search:
    indexer_ids:
      music: []   # limit to music trackers
      book: []
    categories:
      music: [3000, 3010, 3040]
      book: [7000, 7020, 7040, 3030]

Download client routing

Prowlarr grabs can select a download client by name or numeric ID. Name-based routing is preferred because Prowlarr owns the final client IDs.

prowlarr:
  download_clients:
    music: Music
    book:
      ebook: Books
      audiobook: Audiobooks
      default: Books
  download_client_rules:
    - client_name: Music
      categories: [3010, 3040, 3050, 3060]
    - client_name: Movies
      categories: [3020]
      category_prefixes: [2]  # matches 2xxx
    - client_name: TV
      category_prefixes: [5]  # matches 5xxx
    - client_name: Audiobooks
      categories: [3030]
    - client_name: Books
      category_prefixes: [7]  # matches 7xxx

Prefix matching is thousands-based by default (33000-3999). You can use prefix_mode: "hundreds" in a rule if you prefer. If the selected client is a name, IWantIt resolves it through Prowlarr's /api/v1/downloadclient endpoint before posting the grab request.

Filtering and matching

The pipeline applies two filters to Prowlarr results:

steps:
  filter_candidates:
    allow_missing_categories: false
    category_prefixes:
      music: [30]

  filter_match:
    min_match_ratio: 0.4
    min_token_matches: 2

Book format decision

book_decide filters book results when the user specifies a format:

  • Query keywords like audiobook, ebook, epub, m4b are detected.
  • You can also pass --pref book_format=ebook or --pref book_format=audiobook.
  • The default can be set in config via book.default_format (ebook, audiobook, or both).

When book.default_format or --book-format is both, IWantIt tries to select one top-ranked ebook and one top-ranked audiobook, then sends both to Prowlarr on confirm. If a single format is specified, candidates are filtered to that type; otherwise, all results remain and the decision step handles selection.

Redacted enrichment

When a candidate is from Redacted, redacted_enrich pulls group/torrent metadata using the Redacted JSON API. This is used for ranking and for track/album resolution.

Set the API key in secrets:

redacted:
  api_key: YOUR_KEY

Ranking and quality rules

rank_releases scores candidates using quality_rules.

Default music preferences:

  • FLAC > V0 > 320
  • WEB > CD > SACD > vinyl
  • Rejects 24-bit and 5.1/surround
quality_rules:
  music:
    release_priority: [deluxe, studio, anniversary, live, bootleg]
    reject:
      - "(?i)\\b24[- ]?bit\\b"
      - "(?i)\\b5\\.1\\b"
    score:
      - { match: "(?i)\\bflac\\b", score: 120 }
      - { match: "(?i)\\bweb\\b", score: 60 }
      - { match: "(?i)\\bcd\\b", score: 40 }
      - { match: "(?i)\\bvinyl\\b", score: -10 }

External steps

Any step can be replaced with a command:

steps:
  my_step:
    command: ["python3", "-m", "my.module"]

The command receives the JSON payload on stdin and must return JSON to stdout.

Caching, retries, and timeouts

Most steps support:

steps:
  identify_web_search:
    cache: { enabled: true, ttl_seconds: 3600 }
    timeout: 15
    retries: 2

Global defaults can be set with timeouts and retries in config.

Safety notes

  • Prowlarr and Redacted have rate limits. The Redacted API allows ~10 requests/10s with API keys.
  • Always respect tracker rules and terms of service.
  • This tool is meant for personal automation; you control which indexers and download clients are used.

About

Download any shared media to your media server(s)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages