Intelligently curate and manage your media library with automated cleanup rules. A Docker-optimized application for Sonarr, Radarr, Emby, and Jellyfin that helps you maintain a clean, organized media collection.
- Multiple Service Connections: Connect to multiple Sonarr, Radarr, and Emby instances
- Watch History Integration: Sync watch history from Emby to make informed cleanup decisions
- Dry Run Preview: Preview what would be cleaned up before running actual cleanup
- Import List Exclusions: Automatically add deleted items to Sonarr/Radarr exclusion lists
- Customizable Cleanup Rules: Create rules based on:
- Days since last watched
- Disk space thresholds
- Minimum age requirements
- Favorites exclusion
- Genre/tag filters
- Rating thresholds
- Watch progress
- Notifications: Get notified via Discord, Slack, or custom webhooks when media is cleaned up
- Scheduled Cleanups: Configure automatic cleanup schedules using cron expressions
- Secure Web Interface: Modern, responsive UI with JWT authentication
- Docker Optimized: Built for containerized deployments with minimal resource usage
- Create a
docker-compose.yml:
services:
mediacurator:
image: ghcr.io/serph91p/mediacurator:latest
container_name: mediacurator
restart: unless-stopped
ports:
- "8080:8080"
environment:
- TZ=Europe/Berlin
- SECRET_KEY=your-secure-secret-key
volumes:
- ./data:/app/data
- ./logs:/app/logs
- /media:/media:ro # Same path as your media server- Start the container:
docker compose up -d- Access the web interface at
http://localhost:8080
# Pull specific version
docker pull ghcr.io/serph91p/mediacurator:1.0.0
# Or pull latest stable
docker pull ghcr.io/serph91p/mediacurator:latest
# Or pull development version
docker pull ghcr.io/serph91p/mediacurator:dev
# Run container
docker run -d \
--name mediacurator \
-p 8080:8080 \
-e TZ=Europe/Berlin \
-e SECRET_KEY=your-secure-secret-key \
-v $(pwd)/data:/app/data \
-v $(pwd)/logs:/app/logs \
-v /media:/media:ro \
ghcr.io/serph91p/mediacurator:latestProduction releases (main branch):
latest- Latest stable release from main branchstable- Alias for latest0.0.31- Specific production version (based on commit count)v1.2.3- Manual tagged releases (when you create a git tag)
Development builds (develop branch):
dev- Latest development build from develop branchdev.0.0.30- Specific development version (based on commit count)
Versioning: Dev und Main teilen sich die gleiche Zählnummer (commit count).
- Push auf dev →
dev.0.0.30 - Nächster Push auf dev →
dev.0.0.31 - Merge auf main →
0.0.31(gleiche Nummer!) - Push auf dev →
dev.0.0.32
Git tagged releases (manual bumps):
v1.0.0,v1.2.3- Manual version tags für MINOR/MAJOR bumps1.2- Latest patch version of 1.2.x1- Latest minor version of 1.x.x
Example pull commands:
# Latest stable release
docker pull ghcr.io/serph91p/mediacurator:latest
# Specific production version
docker pull ghcr.io/serph91p/mediacurator:0.1.5
# Latest development build
docker pull ghcr.io/serph91p/mediacurator:dev
# Specific development version
docker pull ghcr.io/serph91p/mediacurator:dev.0.0.27Das Projekt nutzt Semantic Versioning:
Format: MAJOR.MINOR.PATCH (z.B. 1.2.3)
- MAJOR (1.x.x): Breaking Changes - Inkompatible API-Änderungen
- MINOR (x.1.x): Neue Features - Rückwärtskompatibel
- PATCH (x.x.1): Bugfixes - Rückwärtskompatibel
Version 0.x.y: Entwicklungsphase vor dem ersten stabilen Release
- Die Zahlen können beliebig hoch gehen (0.0.99, 0.1.234, etc.)
- Es gibt keine Regel, dass bei 9 umgeschaltet wird
- Wechsel zu 1.0.0 erfolgt, wenn die Software produktionsreif ist
Automatisches Versioning:
- Develop → main: Automatisch PATCH +1 (0.0.1 → 0.0.2)
- Für MINOR/MAJOR Bumps: Manuell Git-Tag erstellen (z.B.
v0.1.0,v1.0.0) - Development Builds:
dev.0.0.COMMIT_COUNT
| Variable | Description | Default |
|---|---|---|
TZ |
Timezone for scheduling | UTC |
SECRET_KEY |
JWT signing key (change in production!) | - |
DATABASE_URL |
Database connection string | sqlite+aiosqlite:////app/config/mediacurator.db |
INITIAL_ADMIN_USER |
Pre-create admin user (optional) | - |
INITIAL_ADMIN_PASSWORD |
Password for pre-created admin (optional) | - |
DEBUG |
Enable debug logging | false |
| Path | Description | Type |
|---|---|---|
mediacurator_config → /app/config |
Database and persistent data | Named volume |
mediacurator_logs → /app/logs |
Application logs | Named volume |
/media |
Media files (read-only recommended) | Bind mount |
Note: Named volumes are used for application data and logs, managed automatically by Docker. The config volume is separate from media paths to avoid conflicts with Emby/Jellyfin mounts.
The media path inside MediaCurator must match the path that Emby/Jellyfin uses!
When Emby reports a file location like /data/movies/Movie.mkv, MediaCurator needs to access that exact same path. If the paths don't match, file operations (delete, move to staging) will fail.
Example scenarios:
| Emby sees | MediaCurator mount | Works? |
|---|---|---|
/data/movies/... |
-v /mnt/storage/movies:/data/movies |
Yes |
/media/... |
-v /mnt/storage:/media |
Yes |
/data/movies/... |
-v /mnt/storage/movies:/media/movies |
No - paths don't match! |
Correct setup example:
If your Emby docker-compose looks like this:
# Emby container
volumes:
- /mnt/storage/movies:/data/movies
- /mnt/storage/tv:/data/tvThen MediaCurator should use the same container paths:
# MediaCurator container
volumes:
- /mnt/storage/movies:/data/movies # Same path as Emby!
- /mnt/storage/tv:/data/tv # Same path as Emby!
- /mnt/storage/staging:/data/staging # For staging system (needs write access)For staging/delete operations: Remove :ro (read-only) flag to allow MediaCurator to move/delete files.
- Start the application with
docker compose up -d - Open http://localhost:8080 in your browser
- You'll be redirected to create your admin account (first user is automatically admin)
- Add your service connections (Sonarr, Radarr, Emby)
- Test connections to verify API access
- Configure libraries from your Emby server
- Create cleanup rules or use templates
- Set up notification channels (optional)
- Configure system settings (schedules, dry-run mode)
Rules define when and how media should be cleaned up. Each rule can have multiple conditions:
- Not Watched Days: Delete media not watched for X days
- Disk Space Threshold: Only clean up when disk usage exceeds X%
- Minimum Age: Don't delete media added less than X days ago
- Exclude Favorites: Never delete favorited items
- Exclude Currently Watching: Never delete items someone is actively watching
- Exclude In Progress: Never delete items that are partially watched
- Exclude Recently Added: Don't delete items added within X days
- Genre/Tag Filters: Include or exclude by genre/tag
- Rating Threshold: Only delete items rated below X
- Watch Progress Threshold: Only delete if watch progress is below X%
- Max Items Per Run: Limit how many items are deleted per cleanup run
- Delete: Remove from Sonarr/Radarr and delete files
- Unmonitor: Stop monitoring in Sonarr/Radarr but keep files
- Notify Only: Send notification without taking action
When "Add to Import Exclusion" is enabled on a rule, deleted items will be automatically added to:
- Sonarr: Import List Exclusions (prevents re-downloading via import lists)
- Radarr: Movie Exclusions (prevents re-downloading via import lists)
The Preview page lets you see exactly what would be cleaned up without actually deleting anything:
- View all items that match your rules
- See detailed reasoning for why each item would/wouldn't be deleted
- View item details (watch progress, ratings, genres, etc.)
- Filter by rules or see all at once
Enable Dry Run Mode in Settings to have scheduled cleanups also run in preview mode.
| Configuration | Where | Description |
|---|---|---|
| Service URLs & API Keys | Web UI (Services) | Stored in database |
| Cleanup Rules | Web UI (Rules) | Stored in database |
| Schedules | Web UI (Settings) | Stored in database |
| System Settings | Web UI (Settings) | Stored in database |
Timezone (TZ) |
Environment Variable | Container-level setting |
SECRET_KEY |
Environment Variable | Should not change after setup |
| Volume Mounts | Docker Compose | File system paths |
The API documentation is available at /api/docs when running the application.
- Python 3.114+
- Node.js 24+
- Docker (optional)
# Backend
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload
# Frontend
cd frontend
npm install
npm run devdocker compose -f docker-compose.dev.yml up- Activate
tests.ymlworkflow for automated testing - Add pytest unit tests for backend services
- Add Vitest tests for frontend components
- Extend
release.ymlfor automatic Docker image builds - Push Docker images to GitHub Container Registry (ghcr.io)
- Create production-optimized Docker image (multi-stage build)
- Add health checks and monitoring endpoints
- Implement in-memory caching for Emby API calls (library items, watch status)
- Optimize TanStack Query staleTime/gcTime (5-10 minutes for rarely-changed data)
- Add SQLAlchemy eager loading where appropriate (reduce N+1 queries)
- Implement pagination for large result sets (rules, media items, logs)
- Add database indexes on frequently queried columns (external_id, service_connection_id)
- Implement debouncing for search/filter inputs
- Collapsible sidebar to maximize content area
- Redesign sidebar with proper icon positioning and hover states
- Mobile-responsive layout with hamburger menu
- Create ResponsiveTable component for mobile-friendly data display
- Add series evaluation mode and delete target options (9 granular options)
- Improve conditions form spacing and grouping for better readability
- Convert existing table pages to use ResponsiveTable component (History)
- Add loading skeletons for better perceived performance
- Implement toast notifications for all user actions
- Add confirmation dialogs for destructive actions
- Dark/Light theme toggle with system preference support
- Mobile First Design
- Dashboard with Watch Statistics (Most Viewed/Popular Movies & Series)
- Libraries page with detailed per-library stats cards
- Migrate to Apprise URL-schema for 90+ notification services
- Add notification preview/test button
- Implement notification templates (customize message format)
- Add webhook retry logic with exponential backoff
- Support multiple notification channels per event type
- Add rate limiting for API endpoints
- Implement session management with refresh tokens
- Add audit logging for admin actions
- Implement structured logging with correlation IDs
- Add performance monitoring for slow queries
- Bulk operations for rules (enable/disable multiple)
- Export/import rules as JSON
- Per-library staging settings (with global fallback)
- User tracking from Emby (MediaServerUser, UserWatchHistory models)
- Most Popular Movies/Series by unique users (popularity stats)
- Most Active Users statistics
- Detailed library statistics API (/libraries/stats)
- Advanced scheduling (different schedules per rule)
- Dry-run per rule (not just global)
- Media tags auto-management based on watch patterns
- Library Detail View (Overview, Media Browser, Activity Log per library)
- Users Page (list all users with stats, last watched, watch time, last seen)
- User Detail View (Overview, Activity, Timeline per user)
- Global Activity Log (all playback sessions with filtering)
- Statistics Dashboard with Charts (Daily Play Count, Play by Day/Hour)
- Genre Distribution Charts (by duration and play count)
- User Activity Timeline (when each user watched what)
- Watch Patterns/Heatmap (peak hours and days)
- Concurrent Streams Analysis (how many users watch simultaneously)
- Watch Duration Stats (average session length)
- Completion Rate Analytics (how often content is finished)
- Binge-Watch Detection (detect series marathon sessions)
- Shared vs. Solo Content (which content is watched by multiple users)
- Cleanup Rules per User ("Delete only if NO user watched in X days")
Note: See PLANNED_FEATURES.md for detailed descriptions of planned features.
Contributions are welcome! Please read our contributing guidelines before submitting a pull request.
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by Janitorr and Maintainerr
- Built with FastAPI, React, and TailwindCSS