Capture the colors and spirit of a movie — as a vinyl-like disk poster.
Every frame of a film becomes one concentric ring. Play a whole movie in a single image.
How it works · Install · Usage · Configuration · Contributing
Movinyl turns a movie into a poster: it samples thousands of frames across the runtime and paints each one as a single 1-pixel-wide concentric ring. The outermost ring is the opening shot, the innermost is the final frame — so the whole film's color journey is captured in one vibrant, vinyl-like disk. Pages add the title, year, director, runtime and an extracted color palette.
Movinyl runs natively on Linux, macOS and Windows (and in Docker). A small,
pure-Python front-end drives optimized C++ renderers and ffmpeg, with live
progress bars and an interactive dashboard. Rendering is deterministic — the
same input yields a bit-identical disk on every machine.
video ──▶ ffmpeg ──▶ 2000 frames ──▶ disk renderer ──▶ 4000×4000 disk ──▶ page
(extract) (capped 1080p) (C++ · OpenMP) PNG (title · palette · info)
| Stage | Tool | What happens |
|---|---|---|
| Extract | ffmpeg |
Pulls ~2000 evenly-spaced frames in a single pass, capped to 1080p (each frame only colors a 1px ring, so full 4K detail is wasted I/O). |
| Disk | C++ + OpenCV | Each frame → one concentric ring, filled in parallel with integer-only geometry for reproducible output. |
| Palette | Pillow | Extracts the dominant colors straight from the finished disk. |
| Info | TMDB | Looks up the official title, director and runtime (optional; degrades gracefully offline). |
| Page | C++ + Pillow | Composes the final poster: centered disk, text layers and palette. |
| Planche | Pillow | Optional printable contact sheets (grids of disks). |
Movinyl needs Python 3.8+, plus CMake, a C++ compiler, OpenCV and ffmpeg. The setup scripts handle the Python side and compile the renderers.
# Linux / macOS
./scripts/setup.sh
# add --install-system to also install opencv/ffmpeg/cmake via your package manager
# Windows (PowerShell)
./scripts/setup.ps1Check your environment at any time:
python3 -m movinyl doctorSystem libraries per platform (only if doctor reports them missing)
| OS | Command |
|---|---|
| Debian / Ubuntu | sudo apt-get install -y build-essential cmake libopencv-dev ffmpeg |
| Fedora | sudo dnf install -y gcc-c++ cmake opencv-devel ffmpeg |
| Arch | sudo pacman -S base-devel cmake opencv ffmpeg |
| macOS | brew install opencv libomp ffmpeg cmake |
| Windows | vcpkg install opencv4 ffmpeg (set VCPKG_ROOT), plus CMake + MSVC |
Tip
setup.sh / setup.ps1 create a local .venv. Activate it before running Movinyl:
source .venv/bin/activate — on Windows: .venv\Scripts\activate.
python3 -m movinyl tuiDrop videos in PROCESSING_ZONE, then use the on-screen buttons or shortcuts:
| Key | Action |
|---|---|
| d | Generate disks from the videos |
| p | Build pages from the disks |
| a | Assemble contact sheets (planche) |
| s | Setup / build the C++ renderers |
| o | Toggle re-processing of films already done |
| c | Cancel the running job |
| q | Quit |
Live progress bars, a file list and clean cancellation are built in. Films already processed are marked ✓ and skipped unless re-processing is on.
Prefer a regular window? The PyQt6 GUI does the same, and lets you pick the video and disk folders with a file browser:
python3 -m movinyl guiPyQt6 is optional: the setup scripts install it (requirements-gui.txt), or run
pip install -r requirements-gui.txt yourself. It is left out of the Docker image.
Name your videos Title_Year.ext (e.g. Inception_2010.mp4) and drop them in PROCESSING_ZONE:
python3 -m movinyl disk # → 4000×4000 PNG disks
python3 -m movinyl page PAGE_ZONE # → movie pages (title / year / director / palette)
python3 -m movinyl planche # → printable contact sheets
python3 -m movinyl doctor # → environment checkNo local toolchain required — everything builds inside the image:
docker build -t movinyl .
docker run --rm -v "$(pwd)/PROCESSING_ZONE:/app/PROCESSING_ZONE" movinyl diskOr with Compose (point MOVIES_DIR at your video folder):
docker compose run --rm movinyl diskTuning is done via command-line flags (not env vars):
| Flag | Command | Default | Description |
|---|---|---|---|
--n |
disk |
2000 |
Frames per video (= rings on the disk). |
--max-height |
disk |
1080 |
Cap frame resolution. Sources are never upscaled; each frame only paints a 1px ring, so this trades wasted decode/I-O for speed. Use 0 to keep full resolution. |
--overwrite |
disk, page |
off | Re-render films already processed (existing file or recorded in the history). |
--keep-frames |
disk |
off | Keep the extracted frames instead of cleaning them up. |
--jobs |
page |
CPU count | Number of parallel page workers. |
--leave-cores |
page |
0 |
Keep N cores free so the machine stays responsive. |
--tmdb-key |
disk, page |
bundled | Override the TMDB API key. |
Movinyl keeps a SQLite database of the films it processed (data/movinyl.db,
tracked by git; override with the MOVINYL_DB env var). It has two tables:
| Table | Content |
|---|---|
films |
One row per film: internal id, unique tmdb_id (NULL if the film isn't found on TMDB), file name, title, original title, year, director, runtime, and the disk/page processing state. |
mean_color |
(film_id, frame, r, g, b): the mean color of every sampled frame (frame 1 = start of the movie), filled by the disk stage. |
The colors are enough to redraw a film without its video — as a classic movie barcode or as a disk of flat rings:
python -m movinyl redraw Inception_2010 # Inception_2010_barcode.png
python -m movinyl redraw Inception_2010 --style disk --size 2000Already-processed films are skipped even if their PNG was moved elsewhere. Two things re-process a film automatically:
- a new video file with the same name but a different size (e.g. a better copy);
- a re-rendered disk: its page is invalidated and rebuilt by the next
pagerun.
Inspect or reset the history with:
python -m movinyl history # list films (tmdb id, stages, colors)
python -m movinyl history --forget Inception_2010
python -m movinyl history --clear
./run.sh history # same, through Docker (uses ./data)The optional TMDB API key (for title/director/runtime) can also be set via the
TMDB_API_KEY environment variable — see env.example. A working
default is bundled, so it's optional.
pip install pytest
python -m pytest testsThe tests cover the database (films, TMDB ids, mean colors, migration), the redraw and the skip / re-process rules. They don't need ffmpeg, the C++ renderers or network access.
See the open issues for proposed features and known bugs — contributions welcome!
- QR code on pages
- More page layout options
- Planche (contact sheet) rework
- Faster, resolution-aware extraction
- Cross-platform Python front-end + TUI
Made a disk you're proud of? Add it to the community album, or send us your creations by mail — it saves us re-processing movies and helps others discover new ones. Sharing is greatly appreciated.
📀 Community album · ✉️ project.movinyl@gmail.com (4000×4000 PNG files)
For code contributions:
- Fork the project
- Create your feature branch —
git checkout -b feature/AmazingFeature - Commit your changes —
git commit -m 'Add some AmazingFeature' - Push to the branch —
git push origin feature/AmazingFeature - Open a Pull Request
Distributed under the MIT License. See LICENSE for details.
Movinyl — project.movinyl@gmail.com Project link: https://github.com/Pataclop/Movinyl
