Self-hosted media library analysis for large video collections.
Scans your libraries and run analyses using ffprobe.
Explore technical metadata through a FastAPI + React web UI.
MediaLyze focuses (for now) on just analysis, not playback, scraping, or file modification, READ ONLY on your files!
| Platform | Download |
|---|---|
| macOS Apple Silicon | Download |
| Linux | Download |
| Windows | Download |
| All release assets | Open latest release |
MediaLyze is built for self-hosted setups that need visibility into large media collections without depending on external services and designed around ffprobe with normalized metadata.
Everything with a simple deployment model: one container, one SQLite database, one UI. Bring your own auth (for now).
- Technical media analysis powered by
ffprobe - Full and incremental scans using
path + size + mtime - historical analysis
- many different charts for all metrics
- Normalized formats, streams, subtitles, scan jobs, and quality scores (feel free to suggest improvements)
- recognize shows, seasons, bonus content
- Ignore files and folders with simple glob patterns such as
*.nfoor*/Extras/* - Native desktop packaging for Windows, macOS, and Linux in addition to the Docker/web deployment path
- and more
![]() |
![]() |
![]() |
![]() |
If you find MediaLyze useful and would like to support ongoing development, you can do so here:
use the production ready docker compose file: docker-compose.yaml
services:
medialyze:
image: ghcr.io/frederikemmer/medialyze:latest
container_name: medialyze
ports:
- "${HOST_PORT:-8080}:8080"
environment:
# change to your timezone, e.g. "Europe/Berlin" or "America/New_York"
TZ: UTC
volumes:
- ./config:/config
# use .env or change "./media" to the path of your media directory
- ./media:/media:ro
# additional media mounts by extending this pattern if needed:
# /PATH/TO/MEDIA0:/media/MEDIA0:ro
# /PATH/TO/MEDIA1:/media/MEDIA1:ro
can be extended by .env using: docker-compose-ENV.yaml and env.example
Open http://localhost:8080, or set HOST_PORT to expose the container on a different host port.
The container serves plain HTTP on its internal port 8080 by default - if you want HTTPS, terminate it in a reverse proxy.
Configuration through Docker configuration
Built with Electron, desktop builds run the same FastAPI + React stack locally with a local SQLite database and ffprobe.
Desktop behavior:
- choose local folders directly from the OS
- choose mounted NAS / SMB locations and, on Windows, UNC paths such as
\\server\share\videos - watch mode is limited to local paths; network paths fall back to scheduled scans
Release artifacts are packaged as:
- Windows:
.exe - macOS:
.dmg - Linux:
AppImage
run:
cp docker/env.example .env
docker compose -f docker-compose-dev.yaml up --buildThe default container setup mounts:
./configto/config./mediato/mediaas read-only
If you want a different media-path, or external port change env.example or .env.
For a single-command local dev setup, use scripts/dev-local.sh on macOS/Linux or scripts/dev-local.ps1 on Windows.
python3 -m venv .venv
source .venv/bin/activate
pip install -e .[dev]
uvicorn backend.app.main:app --reload --port 8080cd frontend
npm install
npm run devThe Vite dev server proxies /api to http://127.0.0.1:8080.
macOS/Linux:
./scripts/dev-local.shWindows PowerShell:
.\scripts\dev-local.ps1Both scripts expect:
.venvwithpip install -e .[dev]frontend/node_modulesfromnpm --prefix frontend install- a valid
MEDIA_ROOTdirectory, defaulting to your Desktop if not overridden
They start the backend with reload enabled, wait for /api/health, then launch the Vite dev server in the foreground.
python3 -m venv .venv
source .venv/bin/activate
pip install -e .[dev]
cd frontend
npm install
npm run build
cd ../desktop
npm install
npm run devLocal desktop development expects ffprobe in your PATH.
For packaged .app, .dmg, .exe, and AppImage builds, see docs/build_desktop.md.
Relevant environment variables:
MEDIALYZE_RUNTIME: runtime mode,serverordesktop, defaultserverCONFIG_PATH: writable config/data directory, default/configin server mode and the OS user-data directory in desktop modeMEDIA_ROOT: media mount root for server mode, default/mediaAPP_HOST: bind host for the backend, default0.0.0.0in server mode and127.0.0.1in desktop modeHOST_PORT: HTTP port exposed on the host by the provided Docker Compose files, default8080; access the app viahttp://<host>:<HOST_PORT>FRONTEND_DIST_PATH: optional explicit frontend bundle path, mainly used by packaged desktop buildsTZ: process/container timezone, defaultUTCDISABLE_DEFAULT_IGNORE_PATTERNS: optional; when set totrue, built-in default ignore patterns are not preloadedMEDIALYZE_TELEMETRY_DISABLED: optional; when set totrue, telemetry is forced off and the UI toggle is lockedMEDIALYZE_TELEMETRY_ENDPOINT: optional; overrides the telemetry ingest endpoint, defaulthttps://www.medialyze.app/api/telemetry/ingestFFPROBE_PATH: optional override for theffprobebinary pathJELLYFIN_API_KEY_FILE: optional path to a Jellyfin API-key secret file; see Jellyfin integrationPUID/PGID: optional runtime user/group ids for shared-folder permission setups; set both or leave both unset to keep the default root runtime user
MEDIA_ROOT should be mounted read-only in production.
If you need a specific runtime uid/gid, set PUID and PGID in .env. The compose files already load .env, so no compose changes are required.
For SMB / NAS setups, the recommended approach is to mount the share on the Docker host first and then point MEDIA_HOST_DIR at that host mount path.
In the desktop app, mounted network shares and UNC paths can be selected directly.
Scan parallelism is configured in the UI under Settings -> App settings -> Scan performance.
MediaLyze exposes separate limits for per-scan analysis workers and parallel library scans so you can tune throughput without editing compose or env files.
Ignore rules use glob patterns matched against the normalized relative path inside each library. MediaLyze ships editable built-in defaults for common system and temporary paths such as */.DS_Store, */@eaDir/*, */.deletedByTMM/*, and *.part. Set DISABLE_DEFAULT_IGNORE_PATTERNS=true if you do not want those defaults preloaded on first start.
See docs/patterns.md for folder discovery, series recognition, bonus-content rules, and ignore-pattern examples.
Telemetry payloads are documented in docs/telemetry.md, including the none, minimal, and enabled payload contracts and the privacy-preserving rounding rules for coarse usage counts.
Jellyfin permissions, Docker path mappings, playback-data privacy, compatibility, and secret handling are documented in docs/jellyfin.md.
Repository automation, Docker and desktop publishing, manual workflow controls, and release recovery are documented in docs/github_actions.md.
- Backend: Python, FastAPI, SQLAlchemy, SQLite
- Frontend: React, Vite, TypeScript, i18next
- Desktop packaging: Electron, electron-builder
- Media analysis:
ffprobe/ FFmpeg - Scheduling and watch mode: APScheduler, watchdog
- Packaging: GHCR
MediaLyze is an open-source project under active development. The current focus is technical media analysis for large self-hosted libraries, with the v1 scope centered on scanning, normalization, statistics, and file inspection.
Contributions are welcome. Read CONTRIBUTING.md before opening a pull request.
MediaLyze is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
See the LICENSE file for details.




