Picture to Bad Apple — turns any image into a tile mosaic animation, designed for the Bad Apple!! music video but works with any video, animated GIF, or even a single static image.
Each frame (or single image) is converted to a binary mask. Every cell of the mask is then filled with either a bright or darkened version of your image, producing either a playable mosaic video (with the original audio) or a single PNG mosaic.
# Default: image.jpg + bad_apple.mp4
python pic2bad.py
# Custom image, default video
python pic2bad.py cat.jpg
# Custom image and video
python pic2bad.py cat.jpg my_video.mp4
# Animated GIF as source -> output is a video
python pic2bad.py cat.jpg animation.gif
# Static image as source -> output is a PNG mosaic
python pic2bad.py cat.jpg photo.png
python pic2bad.py cat.jpg logo.jpg
python pic2bad.py cat.jpg frame.bmp
# Strip audio from output (even if source has audio)
python pic2bad.py cat.jpg my_video.mp4 --no-audio
# or short form
python pic2bad.py cat.jpg my_video.mp4 -n
# Custom tile size (default 32)
python pic2bad.py cat.jpg my_video.mp4 --tile 48
# Custom grid resolution (default 80x60)
python pic2bad.py cat.jpg my_video.mp4 --grid 120x90
# Dark tile brightness 0.0-1.0 (default 0.12)
python pic2bad.py cat.jpg my_video.mp4 --brightness 0.20
# Everything together — flags can be combined in any order
python pic2bad.py cat.jpg my_video.mp4 --tile 24 --grid 100x75 --brightness 0.20 -n| Source type | Output file |
|---|---|
Video (mp4, mkv, mov, …) |
{tile_name}_video.mp4 |
| Animated GIF | {tile_name}_video.mp4 |
Static image (png, jpg, webp, bmp, tiff, jp2, single-frame GIF) |
{tile_name}_from_{source_name}_mosaic.png |
| Flag | Short | Argument | Default | Description |
|---|---|---|---|---|
--no-audio |
-n |
— | off | Produce a video-only output, even if the source has audio. (Video mode only — ignored for static images.) |
--tile |
— | integer ≥ 2 | 32 |
Tile size in pixels. |
--grid |
— | WxH (e.g. 120x90) |
80x60 |
Grid resolution in tiles (width × height). |
--brightness |
— | float 0.0–1.0 | 0.12 |
Dark tile brightness multiplier. 0.0 = black, 1.0 = original. |
Without --no-audio, audio is automatically copied from the source if present, and skipped silently if the source has no audio.
- Python 3.8+
- ffmpeg (required)
- ffprobe (recommended — bundled with ffmpeg on most platforms; without it audio is always skipped, even when the source has a soundtrack)
- NVIDIA GPU (NVENC) — auto-detected
- AMD/Intel GPU (VAAPI) — auto-detected
- No GPU required — CPU fallback
Linux (Arch)
sudo pacman -S ffmpegLinux (Debian/Ubuntu)
sudo apt install ffmpegmacOS
brew install ffmpegWindows
winget install ffmpeg
# or download from https://ffmpeg.org/download.html and add to PATH# On many Linux distros use `python3` instead of `python`
python -m venv venv
# Linux / macOS
source venv/bin/activate
pip install opencv-python pillow numpy
# Windows PowerShell
.\venv\Scripts\Activate.ps1
pip install opencv-python pillow numpy
# On headless servers (no GUI), prefer the headless OpenCV build:
pip install opencv-python-headless pillow numpy- Tile image is resized to
TILE_SIZE×TILE_SIZEpixels (default 32×32, see--tile) - Each source frame is downscaled to
GRID_WIDTH×GRID_HEIGHTgrayscale → binary mask (default 80×60, see--grid) - Bright tile is placed on white mask cells, dark tile (multiplied by
DARK_BRIGHTNESS) on black cells - Output format depends on the source:
- Static image → single PNG mosaic, no ffmpeg involved
- Video / animated GIF → MP4 video; encoder auto-detected: NVIDIA NVENC → VAAPI (AMD/Intel) → libx264 (CPU)
- Unless
--no-audiois set, original audio is copied from the source video; if the source has no audio, the mux step is skipped automatically
All rendering parameters can be set via CLI flags (see above). The defaults in the script are:
| Parameter | CLI flag | Default | Description |
|---|---|---|---|
DEFAULT_TILE_SIZE |
--tile |
32 | Tile size in pixels |
DEFAULT_GRID_WIDTH |
--grid WxH |
80 | Tiles horizontally |
DEFAULT_GRID_HEIGHT |
--grid WxH |
60 | Tiles vertically |
DEFAULT_DARK_BRIGHTNESS |
--brightness |
0.12 | Dark tile brightness multiplier |
- Missing files — exits with a clear message
- Missing ffmpeg — exits with a clear message (video mode only; image mode works without ffmpeg)
- Invalid flag values — exits with a clear message (NaN/inf/out-of-range rejected)
- Corrupt frames — skipped with a warning, render continues
- No frames decoded — clear error after the read loop with hints
- No audio in source — video-only output is produced automatically
Ctrl+C— gracefully stops ffmpeg and exits with code 1- Encoder auto-detection failure — falls back to CPU libx264
- Pipe full (encoder can't keep up) — exits with a hint to reduce grid/tile size
- ffmpeg stderr deadlock on long videos — prevented by a background stderr drain thread
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Any error (missing file, encode failure, interrupted, invalid flag, etc.) |