Monitor and compare expected vs actual quality scores for Radarr/Sonarr downloads.
Detects custom format score mismatches between grabbed and imported files.
Features • Getting Started • Configuration • Usage • How It Works • Development
- Auto-detect mode: Run as a Radarr/Sonarr Custom Script triggered on import
- Batch mode: Process all unchecked movies with a dual-queue system
- Score comparison: Compare expected vs actual custom format scores
- Tagging: Automatically tag movies based on score match/mismatch
- Discord notifications: Get notified when score mismatches are detected
- Configurable tolerance: Score is acceptable if actual is between (expected - maxUnderScore) and (expected + maxOverScore)
- Dry-run mode: Test without making any changes
docker run --rm -v "/path/to/config:/app/config" ghcr.io/navino16/qualitarr:latestEdit ./config/config.yaml, then schedule periodic runs or use as a Radarr custom script.
- Download the latest release for your platform
- Make the binary executable and run it:
chmod +x qualitarr-linux-amd64 ./qualitarr-linux-amd64
- Edit
./config.yamland run again
- Download the latest release for Windows
- Run the binary from the command line
- Edit
./config.yamland run again
Copy config.example.yaml to config.yaml and edit it:
cp config.example.yaml config.yamlRadarr — Radarr connection settings
| Name | Description | Mandatory | Default |
|---|---|---|---|
radarr.url |
Radarr base URL | Yes | |
radarr.apiKey |
Radarr API key | Yes | |
radarr.api.timeoutMs |
Request timeout (ms) | No | 30000 |
radarr.api.retryAttempts |
Retry attempts on failure | No | 3 |
radarr.api.retryDelayMs |
Initial retry delay (ms) | No | 1000 |
Discord — Webhook notifications
| Name | Description | Mandatory | Default |
|---|---|---|---|
discord.enabled |
Enable Discord notifications | Yes | true |
discord.webhookUrl |
Discord webhook URL | Yes |
Tag — Tagging settings
| Name | Description | Mandatory | Default |
|---|---|---|---|
tag.enabled |
Enable automatic tagging | Yes | true |
tag.successTag |
Tag applied when score matches | No | check_ok |
tag.mismatchTag |
Tag applied when score differs | No | quality-mismatch |
Quality — Score tolerance
| Name | Description | Mandatory | Default |
|---|---|---|---|
quality.maxOverScore |
Max allowed above expected | No | 100 |
quality.maxUnderScore |
Max allowed below expected (0 = must be >= expected) | No | 0 |
Batch — Batch mode settings
| Name | Description | Mandatory | Default |
|---|---|---|---|
batch.maxConcurrentDownloads |
Max concurrent downloads | No | 3 |
batch.searchIntervalSeconds |
Delay between searches (s) | No | 30 |
batch.downloadCheckIntervalSeconds |
Download progress check interval (s) | No | 10 |
batch.downloadTimeoutMinutes |
Download timeout (min) | No | 60 |
batch.commandTimeoutMs |
Search command timeout (ms) | No | 60000 |
batch.commandPollIntervalMs |
Command status polling (ms) | No | 2000 |
batch.grabWaitTimeoutMs |
Grab event timeout (ms) | No | 30000 |
batch.historyPollIntervalMs |
History polling interval (ms) | No | 3000 |
Example configuration
radarr:
url: "http://localhost:7878"
apiKey: "your-radarr-api-key"
api:
timeoutMs: 30000
retryAttempts: 3
retryDelayMs: 1000
discord:
enabled: true
webhookUrl: "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN"
tag:
enabled: true
successTag: "check_ok"
mismatchTag: "quality-mismatch"
quality:
maxOverScore: 100
maxUnderScore: 0
batch:
maxConcurrentDownloads: 3
searchIntervalSeconds: 30
downloadCheckIntervalSeconds: 10
downloadTimeoutMinutes: 60- In Radarr, go to Settings > Connect > + > Custom Script
- Set the path to the
qualitarrbinary - Select On Import as the trigger
- Save
When a file is imported, Qualitarr will automatically:
- Compare the grabbed score with the imported score
- Apply the appropriate tag (
check_okorquality-mismatch) - Send a Discord notification if there's a mismatch
Process all movies that haven't been checked yet:
qualitarr batchThis mode uses a dual-queue system:
- Search Queue: All movies without the success tag
- Download Queue: Limited concurrent downloads (configurable)
The batch process:
- Fetches all movies without
check_okorquality-mismatchtags - Triggers searches with configurable delays between each
- Monitors downloads in parallel (limited concurrency)
- Checks scores and applies tags as downloads complete
Search for a specific movie by TMDB ID (visible in the Radarr URL):
# TMDB ID is the number in the Radarr URL: /movie/550
qualitarr search 550Usage:
qualitarr [command] [options]
Commands:
(no command) Auto-detect mode from Radarr/Sonarr environment variables
batch Process all movies without success tag
search <tmdb-id> Search for a specific movie by TMDB ID
Options:
-c, --config <path> Path to config file (default: ./config.yaml)
-v, --verbose Enable verbose logging
-n, --dry-run Dry run mode (no searches, no tags, only logs)
-h, --help Show this help message
--version Show version
Qualitarr compares the grabbed score (what Radarr expected when it grabbed the release) with the current file score. This detects cases where the imported file doesn't match the expected custom format score.
A score is considered acceptable if the actual score is between expected - maxUnderScore (default: 0) and expected + maxOverScore (default: 100). Scores outside this range are considered mismatches.
- Trigger search: Tell Radarr to search for the movie
- Wait for grab: Monitor for the grabbed event and record its custom format score
- Wait for import: Monitor for the file to be imported
- Compare scores: Compare grabbed score vs current file score
- Take action: Apply tags and send Discord notifications based on the result
| Problem | Solution |
|---|---|
| "Request timeout" | Increase radarr.api.timeoutMs in configuration |
| "API key invalid" | Verify your Radarr API key in Settings > General |
| "No grab history found" | The movie may have been manually imported. Run a search to create grab history |
| "Score mismatch on all movies" | Review your quality.maxOverScore and quality.maxUnderScore settings |
# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run dev
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Lint
npm run lint
# Format
npm run format- Node.js >= 20.0.0
- Radarr v3+ (Sonarr support coming soon)