Skip to main content

Reproduce videos como animaciones ASCII en terminales ANSI.

Project description

ascii-motion

Deploy landing page CI License: MIT Landing

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.

ascii-motion terminal demo

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.

ascii-motion landing desktop

Mobile view:

ascii-motion landing mobile

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. Use 0 for 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 custom or 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 curses dependency.
  • GIF export is not a runtime feature.
  • blocks uses Unicode characters; the default classic charset is pure ASCII.
  • Very large output sizes increase stdout write cost and can reduce effective FPS.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ascii_motion-0.2.0.tar.gz (28.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ascii_motion-0.2.0-py3-none-any.whl (20.4 kB view details)

Uploaded Python 3

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

Hashes for ascii_motion-0.2.0.tar.gz
Algorithm Hash digest
SHA256 6d7add72c19703a4f32b0581f173c87425d45072b9577e9d8c7231296ec543d0
MD5 30d2a5c957d05f65473986da30e0e2a6
BLAKE2b-256 b280127705c1d203fa8856c984f03fb66a5c5c722a4ab6f0de5b92426b5b08a7

See more details on using hashes here.

Provenance

The following attestation bundles were made for ascii_motion-0.2.0.tar.gz:

Publisher: release.yml on c4rl0s04/ascii-motion

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for ascii_motion-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 66e3bb1ebb61e874bfcf61e1e35553128d8f59d22833c61ea32870340f9d9364
MD5 9231e83f2b7ce50fde3c043969a7704d
BLAKE2b-256 8c9824081b66dddf7c5d8e31dd682e138707c90e6c04be7f9c73d57e6a55b947

See more details on using hashes here.

Provenance

The following attestation bundles were made for ascii_motion-0.2.0-py3-none-any.whl:

Publisher: release.yml on c4rl0s04/ascii-motion

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page