PixelProbe detects corrupted video, image, and audio files across media libraries
10K+
![]()
Find corrupted media files before they ruin your day. PixelProbe scans video, image, and audio libraries using FFmpeg, ImageMagick, and PIL, then shows you exactly what's broken.
Create a .env file:
SECRET_KEY=generate-a-long-random-string-here
MEDIA_PATH=/path/to/your/media
Create a docker-compose.yml:
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: pixelprobe
POSTGRES_USER: pixelprobe
POSTGRES_PASSWORD: changeme
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
pixelprobe:
image: ttlequals0/pixelprobe:latest
environment:
SECRET_KEY: ${SECRET_KEY}
POSTGRES_HOST: postgres
POSTGRES_PASSWORD: changeme
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
SCAN_PATHS: /media
MAX_WORKERS: 10
volumes:
- ${MEDIA_PATH}:/media:ro
ports:
- "5000:5000"
depends_on:
- postgres
- redis
celery-worker:
image: ttlequals0/pixelprobe:latest
command: python celery_worker.py
environment:
SECRET_KEY: ${SECRET_KEY}
POSTGRES_HOST: postgres
POSTGRES_PASSWORD: changeme
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
SCAN_PATHS: /media
MAX_WORKERS: 10
volumes:
- ${MEDIA_PATH}:/media:ro
depends_on:
- postgres
- redis
volumes:
postgres_data:
Then:
docker-compose up -d
Open http://localhost:5000 and create your admin account through the first-run setup wizard.
Four containers, each with a single job:
| Container | Image | Role |
|---|---|---|
| pixelprobe | ttlequals0/pixelprobe | Web UI and REST API (Gunicorn) |
| celery-worker | ttlequals0/pixelprobe | Background scanning and scheduled tasks |
| postgres | postgres:15-alpine | Database |
| redis | redis:7-alpine | Task queue broker for Celery |
The app and worker share the same image. The worker runs python celery_worker.py instead of Gunicorn.
Required:
| Variable | Description |
|---|---|
SECRET_KEY | Flask session secret. Generate with python -c "import secrets; print(secrets.token_hex(32))" |
MEDIA_PATH | Host path to your media files (mounted read-only into the container) |
Tuning:
| Variable | Default | Description |
|---|---|---|
MAX_WORKERS | 10 | Parallel file scanning threads per task. 10-24 recommended. |
CELERY_CONCURRENCY | 4 | Concurrent Celery tasks. Independent from MAX_WORKERS. |
BATCH_SIZE | 100 | Files per batch during discovery phase |
SCAN_PATHS | /media | Comma-separated paths inside the container to scan |
Scheduling:
| Variable | Default | Description |
|---|---|---|
PERIODIC_SCAN_SCHEDULE | (none) | Cron expression for automated scans |
CLEANUP_SCHEDULE | (none) | Cron expression for automated cleanup |
Filtering:
| Variable | Default | Description |
|---|---|---|
EXCLUDED_PATHS | (none) | Comma-separated paths to skip |
EXCLUDED_EXTENSIONS | .txt,.log,.md | File extensions to ignore |
Other:
| Variable | Default | Description |
|---|---|---|
TZ | UTC | Container timezone |
POSTGRES_PASSWORD | changeme | Database password (change this) |
FLASK_ENV | production | Flask environment |
Mount each directory separately and list them in SCAN_PATHS:
environment:
SCAN_PATHS: /movies,/tv-shows,/music
volumes:
- /mnt/movies:/movies:ro
- /mnt/tv-shows:/tv-shows:ro
- /mnt/music:/music:ro
The app and celery-worker containers must run as the same user, and that user needs read access to your media files. Add this to both services:
user: "1000:1000" # Match your host user's UID:GID
MIT
Content type
Image
Digest
sha256:6ba2d8aa9…
Size
760.5 MB
Last updated
7 days ago
docker pull ttlequals0/pixelprobe