Skip to content

Repository files navigation

movarr

Automated movie downloader based on IMDb criteria filtering.

Features

  • Jackett and Prowlarr integration — polls any Jackett-configured indexer (or all indexers at once) or a Prowlarr instance for movie torrents across configurable quality tiers (1080p, 2160p, 2160p remux, etc.).
  • Deep IMDb filtering — every candidate is resolved to an IMDb ID and evaluated against rating, vote count, year, runtime, language, country, title type, and genre before anything is queued.
  • Override lists — bypass the standard filters for specific directors, cast members, writers, movie titles, or characters (e.g. force-accept all James Bond films regardless of rating).
  • Genre threshold overrides — relax rating and vote minimums on a per-genre basis (e.g. accept Animation titles at a lower rating floor than live-action).
  • Library deduplication — resolves candidate titles to IMDb IDs and checks against all configured library paths before queuing, preventing duplicate downloads.
  • Database deduplication — records every evaluated title in SQLite; previously passed, failed, or stalled titles are skipped on subsequent runs.
  • Configurable TTL expiry — failed, stalled, and passed records are automatically pruned after configurable retention windows so titles can be re-evaluated over time.
  • Queue management — monitors qBittorrent for torrents stuck in stalled or metadata-fetching states and removes them after configurable grace periods.
  • qBittorrent-aware queue management — stalled torrent removal is paused when qBittorrent reports it is disconnected from the internet, preventing false positives from transient outages.
  • Post-processing — detects completed downloads in qBittorrent, copies qualifying files to your media library, and removes source files if configured.
  • Genre/certification routing — routes completed movies to different library paths per viewer profile based on genre and age-rating rules.
  • Notifications — sends alerts via any apprise-compatible service (ntfy, Discord, Telegram, email, and more). Notifications use Markdown formatting; ?format=markdown is automatically appended to ntfy URLs for proper Markdown rendering.
  • Three independent schedulers — acquisition, queue management, and post-processing each run on their own configurable interval.
  • Automatic scheduler mode — runs in continuous scheduling mode when any scheduler task is enabled in the config; falls back to a single pass when all schedulers are disabled.
  • Automatic config migration — upgrades the YAML config schema automatically on startup, backing up the previous version before applying changes.
  • Search circuit breaker — when the index proxy (Jackett/Prowlarr) search endpoint errors, the acquisition scheduler skips subsequent searches for a configurable cooldown window instead of burning through long retry cycles. The circuit reopens automatically so movarr resumes as soon as the proxy recovers — no manual restart required.

Prerequisites

Quick start

Installation using uv (recommended)

git clone https://github.com/binhex/movarr
cd movarr
uv venv --quiet
uv sync

Installation using pip

git clone https://github.com/binhex/movarr
cd movarr
python -m venv .venv
source .venv/bin/activate
pip install .

Usage

movarr --help

Options

All options are optional overrides. When an option is omitted, the value from movarr.yml is used.

General

OptionDescriptionDefault
--config-path <dir>Directory containing movarr.yml.configs
--log-path <dir>Override the log directory from config. The file movarr.log is created inside.(from config)
--log-level <level>Override the console log level. Choices: DEBUG, INFO, SUCCESS, WARNING, ERROR. Useful for temporary debugging without editing the config file.(from config)
--db-path <dir>Override the database directory from config. The file movarr.db is created inside.(from config)
--library-path <path[,path...]>Comma-separated list of library root paths, overrides general.library_path_list in config. Example: /media/movies,/media/4k.(from config)
--testValidate configuration and exit without running any tasks.false
--versionPrint the version and exit.

qBittorrent

OptionDescriptionDefault
--qbt-host <host>Override qBittorrent WebUI host from config.(from config)
--qbt-port <port>Override qBittorrent WebUI port from config.(from config)
--qbt-username <user>Override qBittorrent username from config.(from config)
--qbt-password <pass>Override qBittorrent password from config.(from config)

Index Proxy

OptionDescriptionDefault
--index-proxy <proxy>Override index proxy selection. Choices: jackett, prowlarr.(from config)
--jackett-host <host>Override Jackett host from config.(from config)
--jackett-port <port>Override Jackett port from config.(from config)
--jackett-api-key <key>Override Jackett API key from config.(from config)
--prowlarr-host <host>Override Prowlarr host from config.(from config)
--prowlarr-port <port>Override Prowlarr port from config.(from config)
--prowlarr-api-key <key>Override Prowlarr API key from config.(from config)

Unraid users: map container environment variables directly to these flags so a working deployment requires no manual config file editing.

Configuration

All behaviour is controlled by a YAML file inside the config directory (configs/movarr.yml by default). A default config is created automatically on first run. The file is divided into the sections below.

general

KeyDescriptionDefault
config_versionSchema version — managed automatically; do not edit.(current)
log_level_consoleConsole logging level (debug, info, success, warning, error). Overridden by --log-level.info
log_level_fileFile logging level.info
log_pathDirectory for the log file (movarr.log is created inside). Empty string disables file logging. Overridden by --log-path."logs"
library_path_listRoot paths to scan when checking whether a movie already exists in the library. Overridden by --library-path.[]
db_pathDirectory for the SQLite history database (movarr.db is created inside). Overridden by --db-path."db"
pid_pathDirectory for the PID file (movarr.pid is created inside). Empty string disables PID file creation. Overridden by --pid-path."pids"

schedule

Each of the three background tasks has its own schedule block with the same keys:

KeyDescriptionDefault
enabledEnable or disable this task.true
schedule_time_minsInterval in minutes between runs.30 (acquisition), 5 (queue_management / post_processing)
run_on_startRun this task immediately when movarr starts, before the first interval elapses.true

Tasks: acquisition, queue_management, post_processing.

filters

Controls which torrents pass the IMDb quality gate.

KeyDescriptionDefault
minimum_yearReject movies released before this year.1970
minimum_runtime_minsReject movies shorter than this many minutes.60
minimum_ratingMinimum IMDb rating (0–10).7.0
minimum_votesMinimum IMDb vote count.5000
override_genreMap of genre → {minimum_rating, minimum_votes} to relax thresholds for specific genres.{}
allow_imdb_title_type_listAllowed IMDb title types.[movie, video, tvmovie]
allow_country_listAllowed production country codes (ISO 3166-1 alpha-2). Empty = allow all.[]
allow_language_listAllowed spoken language codes (ISO 639-1). Empty = allow all.[]
reject_index_title_listIndex titles containing any of these keywords (case-insensitive) are rejected before IMDb lookup.(see default config)
reject_genre_listReject any movie whose IMDb genres include one of these values.[]
reject_genre_exclusive_listReject a movie only when ALL of its IMDb genres are in this list (e.g. add Horror to reject pure horror, keep horror/sci-fi hybrids).[]
reject_movie_title_listReject movies whose resolved title exactly matches any entry.[]
reject_index_group_listReject torrents from these release groups (case-insensitive).[]
override_cast_listForce-accept any movie featuring one of these cast members, bypassing all other filters.[]
override_writer_listForce-accept any movie written by one of these writers.[]
override_director_listForce-accept any movie directed by one of these directors.[]
override_movie_title_listForce-accept any movie whose title contains one of these strings.[]
override_character_listForce-accept any movie featuring one of these characters.[]
preferred_index_group_listPreferred release group names. Matching torrents sort higher.[]

torrent_client

KeyDescriptionDefault
selectedTorrent client to use. Currently only qbittorrent is supported.qbittorrent
qbittorrent.hostqBittorrent Web UI hostname or IP address.localhost
qbittorrent.portqBittorrent Web UI port.8080
qbittorrent.usernameWeb UI username.admin
qbittorrent.passwordWeb UI password.adminadmin
qbittorrent.add_pausedAdd torrents in paused state.false
qbittorrent.categoryCategory tag applied to all movarr-managed torrents.movies-movarr

notification

KeyDescriptionDefault
apprise_urlsList of apprise service URLs. Leave empty to disable.[]
index_proxy_alert_hoursSend an alert if the index proxy returns no results or is unreachable for this many consecutive hours. Set to 0 to disable.0
torrent_client_alert_hoursSend an alert if the torrent client has been unreachable for this many consecutive hours. Set to 0 to disable.0

Apprise supports ntfy, Discord, Telegram, email, Slack, and many other services. Example: ntfy://my-topic, discord://webhook-id/webhook-token.

index_proxy

KeyDescriptionDefault
selectedIndex proxy to use: jackett or prowlarr.jackett
jackett.hostJackett hostname or IP address.localhost
jackett.portJackett port.9117
jackett.api_keyJackett API key (found in the Jackett dashboard).""
jackett.read_timeoutHTTP read timeout in seconds.60.0
jackett.limitMaximum number of results to request per search query.500
jackett.offsetResult offset for pagination.0
jackett.ignore_listJackett indexer names to skip when querying with jackett_indexer: all. Case-insensitive.[]
prowlarr.hostProwlarr hostname or IP address.localhost
prowlarr.portProwlarr port.9696
prowlarr.api_keyProwlarr API key (found in Settings → General).""
prowlarr.read_timeoutHTTP read timeout in seconds.60.0
prowlarr.ignore_listProwlarr indexer names to skip when querying with prowlarr_indexer: all. Case-insensitive.[]
circuit_open_minutesMinutes to skip search after a proxy search error (circuit breaker). Set to 0 to disable.30

credentials

KeyDescriptionDefault
tmdb.api_keyTMDb API key (IMDb ID resolution fallback).""
omdb.api_keyOMDb API key (IMDb ID resolution fallback).""

index_site

Controls what is searched and which indexers are used.

KeyDescriptionDefault
jackett_indexerJackett indexer to query. Use all to query every configured indexer simultaneously.all
prowlarr_indexerProwlarr indexer ID to query. Use all (maps to -1) or a numeric indexer ID from Prowlarr.all
searchList of search criteria blocks (see below).(1080p only)
override_searchPer-indexer overrides for search parameters, keyed by indexer name.{}

Each entry in search:

KeyDescriptionDefault
criteriaSearch string passed to the index proxy (e.g. 1080p, 2160p remux).
categoryTorrent category codes, comma-separated (Torznab format).2000,5000
minimum_size_mbMinimum torrent size in MB.3000 (1080p), 7000 (2160p)
maximum_size_mbMaximum torrent size in MB.20000 (1080p), 170000 (2160p)
minimum_bitrate_mbMinimum video bitrate in MB/min. Set 0 to disable.50 (1080p), 115 (2160p)

queue_management

KeyDescriptionDefault
queue_management_enabledMaster switch for the queue management pipeline.true
stalled_monitor_enabledRemove torrents that have stalled (no peers, no download progress).true
metadata_monitor_enabledRemove torrents stuck in metadata-fetching state.true
stalled_delete_torrent_dataAlso delete downloaded data when removing a stalled torrent.true
metadata_delete_torrent_dataAlso delete downloaded data when removing a metadata-stuck torrent.true
stalled_delete_torrent_max_minsMinutes a torrent must be continuously stalled before it is removed.120
metadata_delete_torrent_max_minsMinutes a torrent must be stuck in metadata-fetching state before removal.30
supersede_enabledWhen a higher-quality torrent for the same IMDb ID is queued, cancel the inferior download and delete its partial data. Requires the IMDb ID in the qBittorrent tag (automatic with v2.22.0+).false

post_process

KeyDescriptionDefault
post_process_enabledMaster switch for the post-processing pipeline.true
copy_completedCopy completed files to the media library.true
remove_completedRemove source files after a successful copy.true
exclude_file_min_kbFiles smaller than this size (kilobytes) are not copied (skips small extras and samples).1500000
exclude_file_regex_listRegex patterns matched against file names — matching files are skipped.[]
exclude_folder_regex_listRegex patterns matched against folder names — matching folders and their contents are skipped.[]
copy_library_rulesOrdered list of routing rules (see below).[]
default_copy_library.hd_pathFallback destination for HD movies when no rule matches.""
default_copy_library.uhd_pathFallback destination for UHD/4K movies when no rule matches.""
delete_lower_qualityAuto-delete lower-quality library files when a better version is copied. Defaults to false. Permanent deletion — use with care.false

Each entry in copy_library_rules:

KeyDescription
nameHuman-readable label for this rule (e.g. a viewer's name).
genresList of IMDb genres that match this rule.
max_certificationOptional age-rating ceiling (e.g. 12A). Movies rated above this are skipped for this rule.
hd_pathDestination directory for 1080p / HD movies.
uhd_pathDestination directory for 2160p / UHD movies.

post_process.hooks

KeyDescriptionDefault
pre_copyShell command to run before each copy. Failure aborts the copy. {dir} is substituted with the absolute destination directory. {leaf} is substituted with the last path component (e.g. movie folder name)."" (disabled)
post_copyShell command to run after each successful copy. {dir} is substituted with the absolute destination directory. {leaf} substituted likewise."" (disabled)
pre_deleteShell command to run before the deletion pass. Failure aborts deletion. {dir} and {leaf} substituted."" (disabled)
post_deleteShell command to run after the deletion pass. Failure is non-fatal. {dir} and {leaf} substituted."" (disabled)

Hooks must not rename or move the target files. Use only in-place operations (e.g. chattr -i, trimarr).

database

KeyDescriptionDefault
stalled_expiry_daysDelete stalled history records older than this many days, allowing the title to be retried.7
failed_expiry_daysDelete failed history records older than this many days, allowing the title to be re-evaluated.7
passed_expiry_daysDelete passed history records older than this many days. Allows re-queuing if qBittorrent was reset externally. Set 0 to disable.30

How it works

movarr runs three independent pipelines on configurable schedules.

Acquisition pipeline

flowchart TD
A([Start]) --> B[Query Jackett/Prowlarr for each search criteria]
B --> C[For each result]
C --> D{Bad keyword\nin title?}
D -- Yes --> SKIP1([⛔ Skip])
D -- No --> E{Size within\nlimits?}
E -- No --> SKIP2([⛔ Skip])
E -- Yes --> F{Already\nin library?}
F -- Yes --> SKIP3([⛔ Skip])
F -- No --> G{Already in\ndatabase?}
G -- Yes --> SKIP4([⛔ Skip])
G -- No --> H[Resolve IMDb ID]
H --> I{Title type\nallowed?}
I -- No --> FAIL1([❌ Fail])
I -- Yes --> J{Bad genre?}
J -- Yes --> FAIL2([❌ Fail])
J -- No --> K{Pass rating, votes,\nyear, runtime,\nlanguage, country?}
K -- No --> FAIL3([❌ Fail])
K -- Yes --> L{Override list\nmatch?}
L -- Yes --> PASS([✅ Add to qBittorrent])
L -- No --> M{All standard\nfilters pass?}
M -- No --> FAIL4([❌ Fail])
M -- Yes --> PASS
Loading

Queue management pipeline

Runs on its own interval and inspects all movarr-managed torrents in qBittorrent:

  • Stalled torrents — torrents with no peers and no download progress for longer than stalled_delete_torrent_max_mins are removed. The history record is updated to Stalled and stalled_expiry_days controls when the title can be retried.
  • Metadata-stuck torrents — torrents that have been fetching metadata for longer than metadata_delete_torrent_max_mins are removed.
  • Connectivity guard — if qBittorrent reports it is disconnected from the internet, queue management is paused until connectivity returns (so stalled torrents are not incorrectly deleted during an outage).

Post-processing pipeline

Runs on its own interval and inspects all movarr-managed torrents in qBittorrent:

  1. Detects torrents with a completed status.
  2. Scans the download directory, skipping files and folders that match the exclude rules.
  3. Evaluates copy_library_rules in order — the first matching rule determines the destination.
  4. Falls back to default_copy_library if no rule matches.
  5. Copies qualifying files to the destination; removes source files if remove_completed is enabled.
  6. Marks the history record as Completed.

Scheduler

movarr runs three schedulers concurrently. Each uses a run-then-sleep strategy: the interval is measured from the start of the previous run, so drift does not accumulate over time.

TaskDefault intervalConfig key
Acquisition30 minschedule.acquisition
Queue management5 minschedule.queue_management
Post-processing5 minschedule.post_processing

Any task can be disabled independently by setting enabled: false in its schedule block.

Development

git clone https://github.com/binhex/movarr
cd movarr
uv venv --quiet
uv sync --extra dev

If you wish to perform linting on all files before committing (PR will not be accepted if it does not pass all linting) then run pre-commit run --all-files.

Running tests

uv run pytest

FAQ

Q: movarr queued a movie I already have. Why?

Library matching works by extracting the movie title and year from the torrent's index title and scanning your library_path_list for video files whose sanitised filename contains the same title and year. If the filename does not match (e.g. it contains a garbled release-group string), the parent folder name is checked as a fallback. Ensure your media files include the release year in their filename (e.g. The Matrix 1999 1080p BluRay.mkv) or organise movies into folders whose names contain the full movie title and year. movarr also attempts an IMDb ID lookup as a secondary match when the index title can be resolved.

Q: How do I prevent movarr from downloading non-English movies?

Set filters.allow_language_list to [en]. movarr will reject any title where the primary IMDb language is not English.

Q: Can I force-accept a specific director's entire filmography?

Yes — add the director's name to filters.override_director_list. All filter checks (rating, votes, genre, year, etc.) are bypassed for matching titles. The same pattern applies to override_cast_list, override_writer_list, override_movie_title_list, and override_character_list.

Q: What happens if qBittorrent is not reachable?

The acquisition pipeline skips the search entirely if qBittorrent is unreachable. The post-processing and queue management pipelines also log a warning and skip their cycles when qBittorrent is unavailable.

Q: How do I disable passed record expiry?

Set database.passed_expiry_days: 0. This prevents movarr from ever re-queuing a title that was previously sent to qBittorrent, even if qBittorrent is reset externally.

Q: I am using no_ransom script to secure my media, is it possible to use the 'pre_delete' hook to unlock existing media before deletion?

Yes - however there are thee additional changes you will need to make to the movarr container for this to work with the no_ransom script:

  • Grant chattr additional permissions inside the container This is done by adding in the following to the extra parameters field for the container: --cap-add LINUX_IMMUTABLE.

  • Pass through the 'diskX' shares to the container, this is required for no_ransom as chattr cannot traverse FUSE shares (/mnt/user/...) This is done by creating an additional path and setting the container path and host path to /mnt.

  • chattr MUST be run as root, so in order for no_ransom to operate you either need to switch to root account or use sudo.


If you appreciate my work, then please consider buying me a beer :D

PayPal donation

About

Automated movie downloader based on IMDb criteria filtering.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages