Reproduce videos como animaciones ASCII en terminales ANSI.
Project description
ascii-motion
ascii-motion plays video files as real-time ASCII animations directly inside an ANSI-compatible terminal. The pipeline uses OpenCV for frame capture, NumPy for matrix-based processing, and ANSI escape sequences for flicker-resistant rendering without clearing the whole screen on every frame.
Landing Page
The static promotional site lives in site/ and is published with GitHub Pages:
https://c4rl0s04.github.io/ascii-motion/
To view it locally:
python3 -m http.server 8765 --directory site
Then open http://127.0.0.1:8765/ in your browser.
Mobile view:
Installation
PyPI publishing is prepared, but the first publish requires PyPI trusted publisher setup. After the Publish package workflow succeeds, install from PyPI:
pip install ascii-motion
For isolated CLI usage after PyPI publishing succeeds, pipx is recommended:
pipx install ascii-motion
Install with Homebrew on macOS:
brew install c4rl0s04/ascii-motion/ascii-motion
Or tap first:
brew tap c4rl0s04/ascii-motion
brew install ascii-motion
Local development install:
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Local usage-only install:
pip install .
Basic Usage
ascii-motion video.mp4
ascii-motion video.mp4 --width 120
ascii-motion video.mp4 --width 160 --fps 60
ascii-motion video.mp4 --loop
ascii-motion 0 --width 100
It also works as a Python module:
python -m ascii_motion video.mp4
Options
Source And Sizing
source: video path or camera index. Use0for the default webcam.-w, --width COLUMNS: target ASCII width. If omitted, the current terminal width is used.--height ROWS: target ASCII height. If omitted, height is calculated from the source aspect ratio.--fit-terminal: fit output to the current terminal size. When HUD/progress are visible, rows are reserved for those lines.
Terminal characters are usually taller than they are wide, so automatic height uses a 0.5 character aspect correction. With no explicit --width or --height, vertical videos are also capped to the current terminal height so the default playback fits the visible terminal.
Examples:
ascii-motion video.mp4 --width 120
ascii-motion video.mp4 --width 120 --height 40
ascii-motion video.mp4 --fit-terminal
ascii-motion 0 --width 100
Playback Timing
--fps FPS: override source FPS. If omitted, the video FPS is used.--start SECONDS: start playback from a timestamp.--duration SECONDS: stop playback after the given duration.--loop: restart the source when it reaches the end.--real-time: explicitly keep wall-clock playback by skipping source frames when processing or terminal output falls behind. This is also the default behavior.--no-frame-skip: render every source frame even if playback takes longer than the original video.
Examples:
ascii-motion video.mp4 --fps 60
ascii-motion video.mp4 --start 10 --duration 5
ascii-motion video.mp4 --loop
ascii-motion video.mp4 --real-time --benchmark
ascii-motion video.mp4 --no-frame-skip --benchmark
Interactive Controls
Default controls during interactive playback:
q quit
space pause / resume
left arrow seek backward
right arrow seek forward
h seek backward fallback
l seek forward fallback
? show / hide controls
Control options:
--quit-key KEY: change the quit key. Default:q.--pause-key KEY: change the pause/resume key. Default: space.--backward-key KEY: change the backward seek fallback key. Default:h.--forward-key KEY: change the forward seek fallback key. Default:l.--help-key KEY: change the controls overlay toggle key. Default:?.--seek-seconds SECONDS: seconds moved by each seek command. Default:5.
Examples:
ascii-motion video.mp4 --quit-key x
ascii-motion video.mp4 --pause-key p
ascii-motion video.mp4 --backward-key j --forward-key k --seek-seconds 10
HUD, Progress And Terminal Rendering
--no-hud: hide the compact playback status line.--no-progress: hide the progress bar.--show-controls: show the controls line from the start instead of waiting for?.--no-alt-screen: render in the normal terminal buffer instead of the alternate screen.--benchmark: print performance metrics to stderr after playback/export.
The renderer uses \033[H to return the cursor to the top-left before writing each frame. It does not clear the full screen per frame.
Examples:
ascii-motion video.mp4 --no-hud --no-progress
ascii-motion video.mp4 --show-controls
ascii-motion video.mp4 --no-alt-screen
ascii-motion video.mp4 --benchmark
Character Sets And Visual Modes
--charset classic|dense|blocks|custom: choose a built-in character ramp or a custom one.--chars "...": provide a custom dark-to-light character ramp. Use with--charset customor to override another charset.--invert: reverse the selected character ramp.--mode ascii|edges|hybrid: choose standard luminance ASCII, Sobel edge emphasis, or a luminance/edge blend.--dither none|ordered: apply no dithering or ordered Bayer dithering before character mapping.--list-charsets: print available character sets and exit.
Examples:
ascii-motion video.mp4 --charset dense
ascii-motion video.mp4 --charset blocks
ascii-motion video.mp4 --charset custom --chars " .oO@"
ascii-motion video.mp4 --chars " .oO@" --invert
ascii-motion video.mp4 --mode edges
ascii-motion video.mp4 --mode hybrid --dither ordered
ascii-motion --list-charsets
Color
--color none: emit plain text only. This is the default and fastest mode.--color truecolor: emit 24-bit ANSI foreground color.--color 256: emit ANSI 256-color foreground color.--color grayscale: emit ANSI 256-color grayscale foreground color based on luminance.
Examples:
ascii-motion video.mp4 --color truecolor
ascii-motion video.mp4 --color 256
ascii-motion video.mp4 --color grayscale
Preview, Snapshot And Export
Non-interactive modes do not use alternate screen or keyboard controls.
--preview: print source metadata, selected output dimensions, FPS, charset, mode, dithering, and color settings.--frame-at SECONDS: render one frame at a timestamp to stdout.--export PATH: export a plain text animation to one file. Frames are separated with form feed\f.--export-ansi PATH: export an ANSI animation file with cursor-home sequences between frames.--export-frames DIR: export each processed frame as a numbered text file.
Examples:
ascii-motion video.mp4 --preview
ascii-motion video.mp4 --frame-at 5.0 --width 100 > frame.txt
ascii-motion video.mp4 --export output.txt --width 100 --duration 5
ascii-motion video.mp4 --export-ansi output.ans --width 100 --duration 5 --color 256
ascii-motion video.mp4 --export-frames frames --start 2 --duration 4
Use --frame-at when you want exactly one saved frame:
ascii-motion video.mp4 --frame-at 12.5 --width 120 > frame.txt
Use --export-frames when you want many files, one per processed frame in the selected time range.
Metadata
--version: print the installed package version.-h, --help: print CLI help.
Release Process
Package version is defined once in ascii_motion/__init__.py. pyproject.toml reads that value during builds, so releases do not require editing the version in two places.
Before tagging a release:
python3 -m pytest
python3 -m ruff check .
python3 -m compileall ascii_motion tests scripts
rm -rf dist build ascii_motion.egg-info
python3 -m build
python3 -m twine check dist/*
To publish a release, update __version__, commit it, then create and push a semantic version tag:
git tag v0.2.0
git push origin v0.2.0
The Publish package GitHub Actions workflow builds the package and publishes it to PyPI through trusted publishing. The repository must be configured in PyPI with a trusted publisher for this workflow:
Owner: c4rl0s04
Repository: ascii-motion
Workflow: release.yml
Environment: pypi
After the release workflow succeeds, verify the published package from a clean environment:
pipx install ascii-motion
ascii-motion --version
Technical Pipeline
VideoCapture -> resize -> luminance/edges/dither -> LUT ASCII -> ANSI render/export
Luminance uses the Rec. 709 formula:
Y = 0.2126R + 0.7152G + 0.0722B
OpenCV provides frames in BGR order, so the processor reads R from channel 2, G from channel 1, and B from channel 0.
Engineering Notes
ascii-motion is structured as a terminal media pipeline rather than a frame-by-frame print script. OpenCV owns source capture and seeking, FrameProcessor owns vectorized image transforms, and TerminalRenderer owns ANSI terminal state. This keeps future processor backends isolated from playback and output concerns.
The core luminance and character mapping path is vectorized with NumPy over full frame matrices. Python does not iterate pixel by pixel; it only assembles the final character rows after the lookup table has produced the ASCII matrix.
Rendering avoids full-screen clears during playback. Each frame is emitted with a cursor-home sequence and clear-to-end-of-line suffixes, which prevents scrollback spam and stale HUD text while reducing flicker in modern ANSI terminals.
Frame pacing is based on accumulated wall-clock targets instead of sleeping a fixed amount after each frame. When rendering falls behind, default playback skips late source frames to keep the ASCII output close to the original video duration. --no-frame-skip switches to completeness over timing.
Export modes reuse the same processing path as playback. Snapshot, plain text animation, ANSI animation, and numbered frame exports are non-interactive stdout/file workflows, so they do not enter alternate screen or keyboard-control mode.
Performance Notes
Grayscale-to-character mapping is performed with NumPy over full matrices. There are no nested Python loops walking pixel by pixel. Final text conversion happens by row, which is the practical boundary between matrix processing and terminal output.
By default, playback can skip frames when processing or terminal rendering is late. This keeps the ASCII video close to the original duration. Use --no-frame-skip when you prefer to render every source frame even if terminal output is too slow.
--mode edges uses Sobel gradients to emphasize contours. --mode hybrid blends luminance and edges. --dither ordered applies vectorized Bayer dithering before character mapping.
Terminal output can be the main bottleneck at large widths, especially with ANSI color enabled. On modern terminals such as Ghostty, --width 120 or --width 160 is a reasonable range for testing 30/60 FPS depending on the video and machine.
To measure the pipeline:
ascii-motion video.mp4 --width 120 --benchmark
Terminal Compatibility
Compatibility depends on ANSI support, terminal throughput, font metrics, and keyboard escape handling. Current status:
| Terminal | ANSI render | Truecolor | Alternate screen | Keyboard controls | Notes |
|---|---|---|---|---|---|
| Ghostty | tested | tested | tested | tested | Best current validation target for high-FPS playback. |
| iTerm2 | expected | expected | expected | expected | Modern ANSI support; still needs a full local pass. |
| Terminal.app | expected | expected | expected | expected | Works for standard ANSI paths; high widths may be slower. |
| Alacritty | expected | expected | expected | expected | Good fit for fast rendering; needs validation. |
| WezTerm | expected | expected | expected | expected | Good fit for truecolor and alternate screen; needs validation. |
| VS Code terminal | expected | expected | expected | expected | Useful for development, but terminal throughput may vary. |
| tmux | needs validation | needs validation | needs validation | needs validation | Depends on tmux terminal-overrides and truecolor config. |
Recommended Manual Validation
ascii-motion sample.mp4 --width 80
ascii-motion sample.mp4 --width 120
ascii-motion sample.mp4 --width 160 --benchmark
ascii-motion sample.mp4 --loop
ascii-motion sample.mp4 --real-time --benchmark
ascii-motion sample.mp4 --mode edges
ascii-motion sample.mp4 --color 256
ascii-motion sample.mp4 --preview
ascii-motion sample.mp4 --frame-at 5 --width 100
ascii-motion sample.mp4 --export output.txt --duration 3
ascii-motion 0 --width 100
Also test Ctrl+C and confirm the cursor is visible and the terminal is restored.
Limitations
- No audio playback.
- No GUI.
- No required
cursesdependency. - GIF export is not a runtime feature.
blocksuses Unicode characters; the defaultclassiccharset is pure ASCII.- Very large output sizes increase stdout write cost and can reduce effective FPS.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ascii_motion-0.2.0.tar.gz.
File metadata
- Download URL: ascii_motion-0.2.0.tar.gz
- Upload date:
- Size: 28.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d7add72c19703a4f32b0581f173c87425d45072b9577e9d8c7231296ec543d0
|
|
| MD5 |
30d2a5c957d05f65473986da30e0e2a6
|
|
| BLAKE2b-256 |
b280127705c1d203fa8856c984f03fb66a5c5c722a4ab6f0de5b92426b5b08a7
|
Provenance
The following attestation bundles were made for ascii_motion-0.2.0.tar.gz:
Publisher:
release.yml on c4rl0s04/ascii-motion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ascii_motion-0.2.0.tar.gz -
Subject digest:
6d7add72c19703a4f32b0581f173c87425d45072b9577e9d8c7231296ec543d0 - Sigstore transparency entry: 1726325209
- Sigstore integration time:
-
Permalink:
c4rl0s04/ascii-motion@72401461c50912e226241c50ddf18ff13d79a799 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/c4rl0s04
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@72401461c50912e226241c50ddf18ff13d79a799 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ascii_motion-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ascii_motion-0.2.0-py3-none-any.whl
- Upload date:
- Size: 20.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66e3bb1ebb61e874bfcf61e1e35553128d8f59d22833c61ea32870340f9d9364
|
|
| MD5 |
9231e83f2b7ce50fde3c043969a7704d
|
|
| BLAKE2b-256 |
8c9824081b66dddf7c5d8e31dd682e138707c90e6c04be7f9c73d57e6a55b947
|
Provenance
The following attestation bundles were made for ascii_motion-0.2.0-py3-none-any.whl:
Publisher:
release.yml on c4rl0s04/ascii-motion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ascii_motion-0.2.0-py3-none-any.whl -
Subject digest:
66e3bb1ebb61e874bfcf61e1e35553128d8f59d22833c61ea32870340f9d9364 - Sigstore transparency entry: 1726325446
- Sigstore integration time:
-
Permalink:
c4rl0s04/ascii-motion@72401461c50912e226241c50ddf18ff13d79a799 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/c4rl0s04
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@72401461c50912e226241c50ddf18ff13d79a799 -
Trigger Event:
push
-
Statement type: