🎨 mozyq
mozyq is a Python command-line tool (mzq) that turns a collection of photographs into a mosaic video. Each frame of the video is a mosaic: a grid of small tile images, drawn from your photo collection, arranged so that from a distance they reproduce a "master" image. The video zooms out from a single tile to the full mosaic, then the tile it started on becomes the next master — chaining several mosaics into one continuous animation.
✨ Features
- 🖼️ Photo mosaics from any folder of images
- 🎬 Zoom-transition videos that chain multiple mosaics together
- 🎯 Optimal tile matching via the Hungarian algorithm (
scipy.optimize.linear_sum_assignment) - 🌈 Perceptual color matching in LAB color space (OpenCV)
- 📐 Square or rectangular tiles and output
- 🛠️ Built-in image normalization to prepare a collection
🚀 Quick start
Prerequisites
-
Python 3.10–3.12
-
ffmpeg on your
PATH— used to encode frames into a video:sudo apt install ffmpeg # Debian/Ubuntu brew install ffmpeg # macOS choco install ffmpeg # Windows
Install
pip install mozyq
Minimal pipeline
# 1. Collect at least grid_size² images in a folder (81 for the default 9×9).
mkdir photos
seq 200 | xargs -I{} -P8 wget -q https://picsum.photos/630 -O photos/{}.jpg
# 2. Normalize them to a uniform size and format.
mzq normalize photos/ normalized/
# 3. Build the mosaic chain from a seed image (tiles come from its folder).
mzq json normalized/0000.jpg mzq.json --grid-size 9
# 4. Render the video frames.
mzq frames mzq.json frames/
# 5. Encode with ffmpeg.
ffmpeg -r 30 -i frames/%04d.jpg -c:v libx264 -pix_fmt yuv420p mosaic.mp4
📚 Commands
Run mzq --help or mzq <command> --help for the authoritative, up-to-date usage.
Supported image extensions everywhere: .jpg .jpeg .png .bmp .tiff .tif .webp.
mzq normalize
mzq normalize IN_FOLDER OUT_FOLDER [OPTIONS]
Reads every image in IN_FOLDER, resizes it to --target-width (preserving aspect ratio), center-crops it to --target-height, converts to RGB, and writes sequentially numbered JPEGs (0000.jpg, 0001.jpg, …) into OUT_FOLDER. Images smaller than --min-width × --min-height, or whose aspect ratio makes them too short to crop to the target height, are skipped.
| Option | Default | Meaning |
|---|---|---|
--min-width |
630 |
Skip inputs narrower than this |
--min-height |
630 |
Skip inputs shorter than this |
--target-width |
630 |
Output width |
--target-height |
630 |
Output height |
mzq json
mzq json MASTER [OUTPUT_JSON] [OPTIONS]
Builds a chain of mosaics and writes it to OUTPUT_JSON (default mzq.json). The tiles are taken from the folder containing MASTER. For each transition it solves an optimal tile-to-cell assignment for the current master, then picks a random tile in the result to become the next master — repeating up to --max-transitions times (stopping early if a master repeats).
| Argument / option | Default | Meaning |
|---|---|---|
MASTER |
— | Seed image; its folder supplies the tiles |
OUTPUT_JSON |
mzq.json |
Output path |
--grid-size |
9 |
Tiles per row/column; must be odd |
--max-transitions |
10 |
Maximum number of mosaics in the chain |
Requires at least grid_size² images in the tile folder (81 for --grid-size 9, 441 for --grid-size 21).
mzq frames
mzq frames MZQ_JSON OUT_FOLDER [OPTIONS]
Reads a JSON file produced by mzq json and renders the zoom animation as sequentially numbered JPEG frames (0000.jpg, 0001.jpg, …) in OUT_FOLDER.
| Option | Default | Meaning |
|---|---|---|
--fpt |
60 |
Frames rendered per transition |
--crossfade-frames |
60 |
Frames spent cross-fading the mosaic into the master image at the end of each transition (higher = slower fade) |
Feed the frames to ffmpeg to produce the final video:
ffmpeg -r 30 -i OUT_FOLDER/%04d.jpg -c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p mosaic.mp4
mzq json-full
mzq json-full TILE_FOLDER [OUTPUT_JSON] [OPTIONS]
Computes the best tile arrangement for every image in TILE_FOLDER (each image treated as its own master) and writes a {image_stem: [tile_stem, …]} map to OUTPUT_JSON (default full_mzq.json). This is a bulk lookup table, not a video description — mzq frames does not consume it.
| Option | Default | Meaning |
|---|---|---|
--grid-size |
16 |
Tiles per row/column |
Requires at least grid_size² images in TILE_FOLDER.
🔧 How it works
- Normalize the collection to uniform dimensions.
- Vectorize each tile: downscale in LAB color space and flatten to a feature vector.
- Split the master into a
grid_size × grid_sizeset of cells. - Assign tiles to cells by minimizing total Euclidean distance in LAB space with the Hungarian algorithm.
- Animate:
mzq framesrenders a cosine-eased zoom out from the start tile to the full mosaic, then cross-fades toward the master image over--crossfade-framesframes at the end of each transition.
Each output frame is the size of the normalized images, so a larger mzq normalize target produces a higher-resolution video.
🐛 Troubleshooting
| Message | Fix |
|---|---|
Need at least N images for GxG grid |
Add more images, or lower --grid-size |
num_tiles must be odd |
mzq json needs an odd --grid-size (9, 15, 21, …) |
No supported image files found |
Folder has no .jpg/.png/… files |
Could not read image from … |
A file is corrupt or not a real image |
ffmpeg: command not found |
Install ffmpeg (see Prerequisites) |
🔗 Links
- Homepage: mozyq.org
- Repository: github.com/mozyq/app
- Issues: github.com/mozyq/app/issues
Created with ❤️ by Gilad Kutiel
Metadata
Release files for mozyq 0.0.24
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mozyq-0.0.24.tar.gz | 977.0 kB | Without provenance | Details for mozyq-0.0.24.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mozyq-0.0.24-py3-none-any.whl | Python 3 | none | any | Details for mozyq-0.0.24-py3-none-any.whl |
Total release size: 990.2 kB
Release files / mozyq-0.0.24.tar.gz
| Download URL | mozyq-0.0.24.tar.gz |
|---|---|
| Size | 977.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
603fb8ebe44c67e2de11cf8383a754f29b1df9688b2023126a72b296ceb28df0
|
|
BLAKE2b-256 checksum How to use checksums |
9e496a84dba0fd39bd5c678e268cfec53ae011a1a90d758a36d682a03e43aa73
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.13
|
Release files / mozyq-0.0.24-py3-none-any.whl
| Download URL | mozyq-0.0.24-py3-none-any.whl |
|---|---|
| Size | 13.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eae44f26ab240765ca8c98c4564d3f79b7001e94dacce2ed31650c910ade50f5
|
|
BLAKE2b-256 checksum How to use checksums |
16b25814f0da214d96c4d0ad397e9f02afc453141f8ddfab2520f64d5a6f6db2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.13
|
Security for mozyq 0.0.24
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.