A Python library to check release availability via Radarr/Sonarr search results
Project description
Filtarr
Running multiple Radarr/Sonarr instances? You know the problem: your 4K instance fills up with movies that will never be available in 4K, your indexers get hammered searching for releases that don't exist, and tools like Huntarr waste API calls hunting for content you'll never find.
Filtarr solves this. Instead of blindly syncing your entire library to secondary instances, filtarr checks what's actually available from your indexers and tags items accordingly. Only sync what you can actually get.
How It Works
┌─────────────────┐ ┌──────────┐ ┌─────────────────┐
│ Primary Radarr │────▶│ Filtarr │────▶│ Tags Applied │
│ (All Movies) │ │ │ │ • 4k-available │
└─────────────────┘ │ Searches │ │ • 4k-unavailable│
│ Indexers │ └────────┬────────┘
└──────────┘ │
▼
┌─────────────────────────────┐
│ Secondary Radarr (4K) │
│ Import List filtered by │
│ "4k-available" tag │
└─────────────────────────────┘
- Filtarr queries your indexers through Radarr/Sonarr's search API—using the same sources you have access to
- Tags are applied based on availability:
4k-availableor4k-unavailable - Use import lists with tag filters to sync only available content to secondary instances
Why Filtarr?
| Problem | Filtarr Solution |
|---|---|
| Secondary instance cluttered with unavailable content | Only syncs what's actually available |
| Wasted API calls searching for non-existent releases | Fewer items = fewer searches |
| Manual checking is tedious | Automated via webhooks, schedules, or batch operations |
| Don't know if content will ever be available | Clear tagging shows availability at a glance |
Use Cases
- 4K Instance Management — Only add movies/shows that have 4K releases on your indexers
- Special Edition Hunting — Tag content with Director's Cuts, IMAX editions, or remasters available
- HDR/DV Filtering — Separate HDR/Dolby Vision content from SDR libraries
- Smart Huntarr Integration — Stop Huntarr from searching for content that doesn't exist
Features
- Multiple Check Methods: CLI, Python API, webhooks, or scheduled jobs
- Flexible Criteria: Built-in presets (4K, HDR, Director's Cut) or custom matching functions
- Smart Tagging: Automatic tag creation and management in Radarr/Sonarr
- Series Sampling: Efficiently check TV shows without querying every season
- Resume Support: Batch operations can be interrupted and resumed
- Docker Ready: Official image at
ghcr.io/dabigc/filtarr
Quick Start
1. Install
# Install with CLI support
pip install filtarr[cli]
# Or for development
pip install -e ".[dev]"
2. Configure
Set environment variables:
# Radarr (for movies)
export FILTARR_RADARR_URL="http://localhost:7878"
export FILTARR_RADARR_API_KEY="your-radarr-api-key"
# Sonarr (for TV series)
export FILTARR_SONARR_URL="http://localhost:8989"
export FILTARR_SONARR_API_KEY="your-sonarr-api-key"
Or create a config file at ~/.config/filtarr/config.toml:
[radarr]
url = "http://localhost:7878"
api_key = "your-radarr-api-key"
[sonarr]
url = "http://localhost:8989"
api_key = "your-sonarr-api-key"
Environment variables take precedence over the config file.
3. Check 4K Availability
# Check a movie by Radarr ID
filtarr check movie 123
# Check a TV series by Sonarr ID
filtarr check series 456
# Check multiple items from a file
filtarr check batch --file items.txt
CLI Usage
Check Movie
filtarr check movie <MOVIE_ID> [OPTIONS]
Options:
-f, --format [json|table|simple] Output format (default: table)
Example:
$ filtarr check movie 123 --format simple
movie:123: 4K available
Check Series
filtarr check series <SERIES_ID> [OPTIONS]
Options:
-s, --seasons INTEGER Seasons to check (default: 3)
--strategy [recent|distributed|all] Sampling strategy (default: recent)
-f, --format [json|table|simple] Output format (default: table)
Strategies:
recent- Check most recent N seasons (fastest)distributed- Sample across all seasons evenlyall- Check every season (slowest, most thorough)
Example:
$ filtarr check series 456 --strategy recent --seasons 2 --format json
{
"item_id": 456,
"item_type": "series",
"has_4k": true,
"releases_count": 42,
"four_k_releases_count": 8,
"seasons_checked": [3, 4],
"strategy_used": "recent"
}
Batch Check
Process your entire library or a subset with automatic tagging and resume support.
filtarr check batch [OPTIONS]
Options:
-f, --file PATH File with items to check (optional)
--all-movies Check all movies in Radarr
--all-series Check all series in Sonarr
--batch-size INTEGER Max items per run (0=unlimited, default: 0)
-d, --delay FLOAT Delay between checks in seconds (default: 0.5)
--skip-tagged/--no-skip-tagged Skip items with existing 4k tags (default: skip)
--resume/--no-resume Resume interrupted batch run (default: resume)
--no-tag Disable automatic tagging
--dry-run Show what would be tagged without making changes
--format [json|table|simple] Output format (default: simple)
-s, --seasons INTEGER Seasons to check for series (default: 3)
--strategy [recent|distributed|all] Strategy for series (default: recent)
Examples:
# Check all movies and tag them
filtarr check batch --all-movies
# Check all movies, 100 at a time (good for large libraries)
filtarr check batch --all-movies --batch-size 100
# Check all series with 1 second delay between checks
filtarr check batch --all-series --delay 1.0
# Check specific items from a file
filtarr check batch --file items.txt
# Preview what would be tagged (no changes made)
filtarr check batch --all-movies --dry-run
Batch file format (one item per line):
# Comments start with #
movie:123
movie:456
series:789
Automatic Tagging
Batch operations automatically tag items in Radarr/Sonarr based on 4K availability:
| Tag | Meaning |
|---|---|
4k-available |
4K releases were found |
4k-unavailable |
No 4K releases found |
Tags are created automatically if they don't exist. Use --no-tag to disable tagging.
Resume Support
Batch operations track progress and can resume after interruption:
- Progress is saved to
~/.config/filtarr/state.json - Use
--resume(default) to continue where you left off - Use
--no-resumeto start fresh - Items are marked with check timestamps to avoid re-checking recently scanned items
Exit Codes
| Code | Meaning |
|---|---|
| 0 | 4K releases found |
| 1 | No 4K releases found |
| 2 | Error (config, API, etc.) |
Use exit codes in scripts:
if filtarr check movie 123 --format simple; then
echo "4K is available!"
else
echo "No 4K found"
fi
Webhook Server
Run a webhook server to automatically check 4K availability when new movies or series are added to Radarr/Sonarr.
Installation
# Install with webhook support
pip install filtarr[webhook]
Starting the Server
# Start with default settings (port 8080)
filtarr serve
# Custom host and port
filtarr serve --host 0.0.0.0 --port 9000
# With debug logging
filtarr serve --log-level debug
Configuring Webhooks in Radarr
- Go to Settings > Connect > Add > Webhook
- Configure:
- Name:
filtarr - URL:
http://<filtarr-host>:8080/webhook/radarr - Method:
POST - On Movie Added: ✓ (enable)
- Name:
- Add custom header:
- Key:
X-Api-Key - Value: Your Radarr API key (same one in your filtarr config)
- Key:
- Save and test
Configuring Webhooks in Sonarr
- Go to Settings > Connect > Add > Webhook
- Configure:
- Name:
filtarr - URL:
http://<filtarr-host>:8080/webhook/sonarr - Method:
POST - On Series Add: ✓ (enable)
- Name:
- Add custom header:
- Key:
X-Api-Key - Value: Your Sonarr API key (same one in your filtarr config)
- Key:
- Save and test
Webhook Configuration
Add to your ~/.config/filtarr/config.toml:
[webhook]
host = "0.0.0.0" # Listen on all interfaces
port = 8080 # Default port
Or use environment variables:
export FILTARR_WEBHOOK_HOST="0.0.0.0"
export FILTARR_WEBHOOK_PORT="8080"
How It Works
- When you add a movie/series to Radarr/Sonarr, it sends a webhook to filtarr
- filtarr immediately returns
200 OKand processes the check in the background - After checking, filtarr applies the appropriate tag (
4k-availableor4k-unavailable)
The webhook uses your existing tag configuration from [tags] section.
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/health |
GET | Health check (returns {"status": "healthy"}) |
/webhook/radarr |
POST | Receive Radarr webhooks |
/webhook/sonarr |
POST | Receive Sonarr webhooks |
Running as a Service (systemd)
Create /etc/systemd/system/filtarr-webhook.service:
[Unit]
Description=filtarr Webhook Server
After=network.target
[Service]
Type=simple
User=your-user
ExecStart=/path/to/filtarr serve --host 0.0.0.0 --port 8080
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload
sudo systemctl enable filtarr-webhook
sudo systemctl start filtarr-webhook
Running with Docker
The official Docker image is available on GitHub Container Registry.
Quick Start
docker run -d \
--name filtarr \
-p 8080:8080 \
-e FILTARR_RADARR_URL="http://radarr:7878" \
-e FILTARR_RADARR_API_KEY="your-radarr-key" \
-e FILTARR_SONARR_URL="http://sonarr:8989" \
-e FILTARR_SONARR_API_KEY="your-sonarr-key" \
ghcr.io/dabigc/filtarr:latest
Using a Config File
Mount your config file to /config/config.toml:
docker run -d \
--name filtarr \
-p 8080:8080 \
-v /path/to/config.toml:/config/config.toml:ro \
ghcr.io/dabigc/filtarr:latest
Docker Compose
Copy the example environment file and add your API keys:
cp .env.example .env
# Edit .env with your Radarr/Sonarr API keys
Then start the container:
services:
filtarr:
image: ghcr.io/dabigc/filtarr:latest
container_name: filtarr
env_file: .env # Required - Docker Compose doesn't auto-read .env
ports:
- "8080:8080"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
# Example: Run alongside Radarr/Sonarr
radarr:
image: linuxserver/radarr:latest
# ... your radarr config
sonarr:
image: linuxserver/sonarr:latest
# ... your sonarr config
Note: The
env_file: .envdirective is required. Docker Compose does not automatically load.envfiles for variable substitution in the compose file.
Building Locally
# Build the image
docker build -t filtarr .
# Run it
docker run -d -p 8080:8080 \
-e FILTARR_RADARR_URL="http://radarr:7878" \
-e FILTARR_RADARR_API_KEY="your-key" \
filtarr
Scheduler
Run batch operations automatically on configurable schedules.
Installation
# Install with scheduler support
pip install filtarr[scheduler]
# Or with everything (CLI, webhook, scheduler)
pip install filtarr[cli,webhook,scheduler]
Quick Start
Add schedules to your ~/.config/filtarr/config.toml:
[scheduler]
enabled = true
history_limit = 100 # Keep last 100 run records
[[scheduler.schedules]]
name = "daily-movies"
target = "movies"
trigger = { type = "cron", expression = "0 3 * * *" } # 3 AM daily
batch_size = 100
skip_tagged = true
[[scheduler.schedules]]
name = "weekly-series"
target = "series"
trigger = { type = "interval", hours = 168 } # Every week
strategy = "recent"
seasons = 3
Then start the server with the scheduler:
filtarr serve # Scheduler is enabled by default
filtarr serve --no-scheduler # Webhooks only
CLI Commands
# List all schedules
filtarr schedule list
# Add a dynamic schedule
filtarr schedule add daily-check --target movies --cron "0 3 * * *"
filtarr schedule add hourly-all --target both --interval 6h
# Remove a schedule
filtarr schedule remove daily-check
# Enable/disable a schedule
filtarr schedule enable daily-check
filtarr schedule disable daily-check
# Run a schedule immediately
filtarr schedule run daily-movies
# View run history
filtarr schedule history
filtarr schedule history --name daily-movies --limit 10
# Export to external schedulers
filtarr schedule export --format cron
filtarr schedule export --format systemd --output /etc/systemd/system/
Schedule Triggers
Cron expressions (5 fields: minute hour day month weekday):
trigger = { type = "cron", expression = "0 3 * * *" } # Daily at 3 AM
trigger = { type = "cron", expression = "0 */6 * * *" } # Every 6 hours
trigger = { type = "cron", expression = "0 3 * * 0" } # Sundays at 3 AM
Interval triggers:
trigger = { type = "interval", hours = 6 } # Every 6 hours
trigger = { type = "interval", days = 1 } # Daily
trigger = { type = "interval", hours = 2, minutes = 30 } # Every 2h 30m
CLI interval format: 6h, 30m, 1d, 1w, 2h30m
Schedule Parameters
| Parameter | Default | Description |
|---|---|---|
name |
required | Unique schedule identifier |
target |
required | movies, series, or both |
trigger |
required | Cron or interval trigger |
enabled |
true |
Whether schedule is active |
batch_size |
0 |
Max items per run (0=unlimited) |
delay |
0.5 |
Seconds between checks |
skip_tagged |
true |
Skip items with existing tags |
include_rechecks |
true |
Include stale unavailable items |
no_tag |
false |
Disable automatic tagging |
dry_run |
false |
Preview mode |
strategy |
recent |
Series strategy: recent, distributed, all |
seasons |
3 |
Seasons to check for series |
Behavior
- Overlap prevention: If a schedule is still running when the next run is due, the new run is skipped
- Error resilience: Failed runs are logged but don't stop the scheduler
- History tracking: All runs are recorded with timestamps, item counts, and errors
- Graceful shutdown: Completes current run before stopping
Export to External Schedulers
If you prefer to use cron or systemd instead of the built-in scheduler:
# Generate cron configuration
filtarr schedule export --format cron
# Output:
# 0 3 * * * /usr/local/bin/filtarr check batch --all-movies --batch-size 100
# Generate systemd timers
filtarr schedule export --format systemd --output ./systemd-units/
# Creates:
# filtarr-daily-movies.timer
# filtarr-daily-movies.service
Monitoring
The /status endpoint shows scheduler state:
curl http://localhost:8080/status
{
"status": "healthy",
"scheduler": {
"enabled": true,
"running": true,
"total_schedules": 2,
"enabled_schedules": 2,
"currently_running": [],
"recent_runs": [
{
"schedule": "daily-movies",
"status": "completed",
"items_processed": 150,
"items_with_4k": 42
}
]
}
}
Python API
Check movie 4K availability (Radarr)
import asyncio
from filtarr import RadarrClient
async def main():
async with RadarrClient("http://localhost:7878", "your-api-key") as client:
# Check if movie ID 123 has 4K releases available
has_4k = await client.has_4k_releases(movie_id=123)
print(f"4K available: {has_4k}")
# Get all releases for detailed inspection
releases = await client.get_movie_releases(movie_id=123)
for release in releases:
if release.is_4k():
print(f"4K: {release.title} ({release.indexer})")
asyncio.run(main())
Check series 4K availability (Sonarr)
import asyncio
from filtarr import SonarrClient
async def main():
async with SonarrClient("http://localhost:8989", "your-api-key") as client:
has_4k = await client.has_4k_releases(series_id=456)
print(f"4K available: {has_4k}")
asyncio.run(main())
Combined checker
import asyncio
from filtarr import ReleaseChecker, SamplingStrategy
async def main():
checker = ReleaseChecker(
radarr_url="http://localhost:7878",
radarr_api_key="your-radarr-key",
sonarr_url="http://localhost:8989",
sonarr_api_key="your-sonarr-key",
)
# Check a movie
result = await checker.check_movie(movie_id=123)
print(f"Movie has 4K: {result.has_4k}")
print(f"4K releases: {len(result.four_k_releases)}")
# Check a series with sampling strategy
result = await checker.check_series(
series_id=456,
strategy=SamplingStrategy.RECENT,
seasons_to_check=3,
)
print(f"Series has 4K: {result.has_4k}")
asyncio.run(main())
Configuration Reference
Environment Variables
| Variable | Description |
|---|---|
FILTARR_RADARR_URL |
Radarr instance URL (e.g., http://localhost:7878) |
FILTARR_RADARR_API_KEY |
Radarr API key |
FILTARR_SONARR_URL |
Sonarr instance URL (e.g., http://localhost:8989) |
FILTARR_SONARR_API_KEY |
Sonarr API key |
FILTARR_TIMEOUT |
Request timeout in seconds (default: 120) |
Config File
Location: ~/.config/filtarr/config.toml
# Request timeout in seconds (default: 120)
timeout = 120
[radarr]
url = "http://localhost:7878"
api_key = "your-radarr-api-key"
[sonarr]
url = "http://localhost:8989"
api_key = "your-sonarr-api-key"
# Tag configuration (optional)
[tags]
available = "4k-available" # Tag for items with 4K releases
unavailable = "4k-unavailable" # Tag for items without 4K releases
create_if_missing = true # Create tags if they don't exist
recheck_days = 30 # Days before rechecking tagged items
# State file location (optional)
[state]
path = "~/.config/filtarr/state.json"
Timeout Considerations
Searching for releases on popular media (e.g., "The Matrix") can take significant time as Radarr/Sonarr queries multiple indexers. The default timeout is 120 seconds.
If you're behind a reverse proxy (nginx, Caddy, Traefik, Nginx Proxy Manager, etc.), you may need to increase the proxy timeout as well. The filtarr client timeout won't help if your reverse proxy times out first.
Nginx Proxy Manager
Add to the Advanced tab of your proxy host:
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
Nginx
location / {
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
# ... other proxy settings
}
Caddy
reverse_proxy localhost:7878 {
transport http {
read_timeout 300s
write_timeout 300s
}
}
Traefik
http:
middlewares:
slow-timeout:
forwardedHeaders:
trustedIPs: []
serversTransports:
slow-transport:
forwardingTimeouts:
dialTimeout: 300s
responseHeaderTimeout: 300s
Development
This project uses uv for dependency management.
# Install dev dependencies
uv sync --dev
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=filtarr --cov-report=term-missing
# Lint
uv run ruff check src tests
# Format
uv run ruff format src tests
# Type check
uv run mypy src
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file filtarr-1.1.4.tar.gz.
File metadata
- Download URL: filtarr-1.1.4.tar.gz
- Upload date:
- Size: 159.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f84b0ca1c5547cd19f83f5cd8be4e9729b4120d3be7589f8aa9b6291aabebaa9
|
|
| MD5 |
d9dfbc3cbfac231e5ab773300a372d6e
|
|
| BLAKE2b-256 |
bf9689010399ebaa210f78bce2695207cce81293f0a771b5551a58193104622b
|
Provenance
The following attestation bundles were made for filtarr-1.1.4.tar.gz:
Publisher:
release-please.yml on dabigc/filtarr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
filtarr-1.1.4.tar.gz -
Subject digest:
f84b0ca1c5547cd19f83f5cd8be4e9729b4120d3be7589f8aa9b6291aabebaa9 - Sigstore transparency entry: 778915827
- Sigstore integration time:
-
Permalink:
dabigc/filtarr@0a21d567869f1c659142b7745a911d87c27a12d8 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/dabigc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@0a21d567869f1c659142b7745a911d87c27a12d8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file filtarr-1.1.4-py3-none-any.whl.
File metadata
- Download URL: filtarr-1.1.4-py3-none-any.whl
- Upload date:
- Size: 61.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d74a7b2fe80185228d5cb88965a07017997ab90d42808917a9fa5d3c181c344
|
|
| MD5 |
cf24535979097c2c52ce7a3117b337fb
|
|
| BLAKE2b-256 |
dc95a8ada4ed3fe92ef361836f4d022f0808da93efad08560c26dbca16ed328a
|
Provenance
The following attestation bundles were made for filtarr-1.1.4-py3-none-any.whl:
Publisher:
release-please.yml on dabigc/filtarr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
filtarr-1.1.4-py3-none-any.whl -
Subject digest:
9d74a7b2fe80185228d5cb88965a07017997ab90d42808917a9fa5d3c181c344 - Sigstore transparency entry: 778915828
- Sigstore integration time:
-
Permalink:
dabigc/filtarr@0a21d567869f1c659142b7745a911d87c27a12d8 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/dabigc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@0a21d567869f1c659142b7745a911d87c27a12d8 -
Trigger Event:
push
-
Statement type: