PixelProbe is a self-hosted server that finds corrupted video, image, and audio files across your media libraries before you hit play. It validates every file with FFmpeg, ImageMagick, and PIL, watches for bitrot with a rolling integrity queue, and gives you a web UI for browsing results, scheduling scans, and clearing false positives. Production-tested against libraries of over a million files.
Detection
- Deep FFmpeg video analysis with a staged pipeline: full-stream validation, frame integrity, temporal sampling, freeze detection with black-frame false-positive filtering
- Image validation through both PIL/Pillow (HEIC via pillow-heif) and ImageMagick pixel decode; audio validation through FFmpeg
- Separate warning verdict for signals that do not prove damage, so real corruption stays visible
- Bitrot detection: a content hash change without a matching mtime change flags the file for review instead of silently adopting the new hash
- Ignored-error patterns to suppress known benign decoder noise per deployment
Scanning
- Chunk-distributed parallel scanning on Celery workers, with heartbeats and automatic revival after container restarts
- Rolling integrity queue that sweeps the library stalest-first under optional per-run time budgets
- Scheduled scans on cron expressions, path and extension exclusions, bulk rescans
- Real-time progress with ETA and phase tracking; scans of 50GB+ remux files and million-file libraries are routine
Interface and ops
- Responsive web UI with dark/light themes, mobile layout, in-browser media preview, and bulk actions
- Trend analytics, scan reports with PDF/JSON export, in-app log viewer
- Event notifications to email (SMTP), Pushover, ntfy, or webhooks, one rule per event
- Healthchecks.io integration, full REST API with OpenAPI spec
Security
- Multi-user with role-based access, bcrypt passwords, API tokens, CSRF-protected sessions, audit logging
- First-run setup wizard creates the admin account
- Discovery - a directory walk registers candidate files by extension
- Chunking - pending files are split into path-range chunks and queued to Celery workers
- Validation - each file runs the FFmpeg/ImageMagick/PIL pipeline; verdicts are healthy, warning, corrupted, or error
- Integrity - previously scanned files are re-hashed on a rolling queue to catch silent changes (bitrot)
- Review - the web UI surfaces verdicts, trends, and false-positive tools (Mark as Good, ignored patterns)
Full pipeline detail (validation stages, chunk lifecycle, revival, failure recovery) is in docs/how-it-works.md.
- Docker with Docker Compose (the stack runs web, Celery worker, PostgreSQL, and Valkey containers)
- PostgreSQL is required; the bundled compose provides it
- 4 CPU cores and 8 GB RAM recommended for video-heavy libraries; see docs/installation.md
Upgrading to v2.7.0+: the bundled compose defaults to PostgreSQL 18 and Valkey 9. An existing PostgreSQL 15 data volume will not start on the 18 image - follow the migration guide first, or pin postgres:15-alpine.
# 1. Create environment file
cat > .env << EOF
SECRET_KEY=long-random-string
POSTGRES_PASSWORD=another-long-random-string
MEDIA_PATH=/path/to/your/media
EOF
# 2. Run
docker compose up -dOpen http://localhost:5000, create the admin account through the first-run wizard, and start a scan. Media is mounted read-only; SCAN_PATHS defaults to /media.
Everything else (concurrency, schedules, exclusions, notifications) is configured through the web UI or environment variables - see docs/configuration.md and .env.example.
Images are published to Docker Hub as ttlequals0/pixelprobe (:latest plus one tag per version).
| Topic | |
|---|---|
| How It Works | Layers, containers, scan lifecycle from claim to finalize, validation pipeline, failure recovery |
| Installation | Requirements, Docker quick start, manual install, first-run setup |
| Docker Setup | Full compose stack, container roles, PostgreSQL tuning, the 15-to-18 migration |
| Web Interface | Dashboard, file actions, admin views, screenshots |
| Configuration | Every environment variable with its real default, notifications, schedules, exclusions |
| Performance Tuning | Concurrency knobs, chunk sizing, worker recycling, CPU sizing for video scanning |
| Scan Types | The scan types, what each checks, and when to use which |
| API Reference | Authentication, every endpoint, rate limits, response shapes |
| Integration Guide | Polling patterns, CI hooks, notification payloads, client examples |
| Troubleshooting | Symptom-driven recipes, stuck-scan revival, incomplete scan repair |
| Glossary | Every term the app uses, linked to the doc that covers it |
| Project Structure | Where everything lives in the repository |
| Database Schema | All 17 models, indexes, the startup migration pattern |
| Developer Guide | Local setup, conventions, contribution flow |
| Testing Guide | Test layout, markers, fixtures, what CI runs |
| Release Process | Version bump to deployed container, scripted GitHub releases |
| Tools and Scripts | Maintenance tools and helper scripts, dry-run conventions |
Or browse the full docs index.
Video (MP4, MKV, AVI, MOV, WebM, HEVC, ProRes, MXF, AVCHD, and more), images (JPEG, PNG, GIF, TIFF, WebP, HEIC, and most camera RAW formats), and audio (MP3, AAC, FLAC, WAV, Opus, DSD, AC3, DTS). The canonical extension lists live in pixelprobe/constants.py.
MIT - see the LICENSE file.
- FFmpeg for video analysis
- ImageMagick for image processing
- PIL/Pillow for Python image handling
For issues, questions, or contributions, visit the GitHub repository.
This project was developed using AI agents as a pair programmer. It was NOT vibe coded. For context, I'm a systems engineer who also writes code professionally with 15+ years of experience. The codebase follows engineering best practices, and all architecture and design decisions were made by me, not by AI. All code generated by LLMs was reviewed and tested by me, a human.