Metadata-Version: 2.4
Name: ascii-motion
Version: 0.2.0
Summary: Reproduce videos como animaciones ASCII en terminales ANSI.
Author: Carlos Andres Huete
License-Expression: MIT
Project-URL: Homepage, https://github.com/c4rl0s04/ascii-motion
Project-URL: Repository, https://github.com/c4rl0s04/ascii-motion
Project-URL: Issues, https://github.com/c4rl0s04/ascii-motion/issues
Project-URL: Landing, https://c4rl0s04.github.io/ascii-motion/
Keywords: ascii,terminal,opencv,video,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Terminals
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.26.0
Requires-Dist: opencv-python>=4.9.0
Provides-Extra: dev
Requires-Dist: build>=1.2.0; extra == "dev"
Requires-Dist: Pillow>=10.0.0; extra == "dev"
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: ruff>=0.6.0; extra == "dev"
Requires-Dist: twine>=5.0.0; extra == "dev"
Dynamic: license-file

# ascii-motion

[![Deploy landing page](https://github.com/c4rl0s04/ascii-motion/actions/workflows/pages.yml/badge.svg)](https://github.com/c4rl0s04/ascii-motion/actions/workflows/pages.yml)
[![CI](https://github.com/c4rl0s04/ascii-motion/actions/workflows/ci.yml/badge.svg)](https://github.com/c4rl0s04/ascii-motion/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-8dffb1.svg)](LICENSE)
[![Landing](https://img.shields.io/badge/site-GitHub%20Pages-6ee7f2.svg)](https://c4rl0s04.github.io/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.

![ascii-motion terminal demo](docs/demo/ascii-motion-demo.gif)

## Landing Page

The static promotional site lives in [`site/`](site/) and is published with GitHub Pages:

https://c4rl0s04.github.io/ascii-motion/

To view it locally:

```bash
python3 -m http.server 8765 --directory site
```

Then open `http://127.0.0.1:8765/` in your browser.

![ascii-motion landing desktop](docs/screenshots/landing-desktop.png)

Mobile view:

![ascii-motion landing mobile](docs/screenshots/landing-mobile.png)

## Installation

PyPI publishing is prepared, but the first publish requires PyPI trusted publisher setup. After the `Publish package` workflow succeeds, install from PyPI:

```bash
pip install ascii-motion
```

For isolated CLI usage after PyPI publishing succeeds, `pipx` is recommended:

```bash
pipx install ascii-motion
```

Install with Homebrew on macOS:

```bash
brew install c4rl0s04/ascii-motion/ascii-motion
```

Or tap first:

```bash
brew tap c4rl0s04/ascii-motion
brew install ascii-motion
```

Local development install:

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

Local usage-only install:

```bash
pip install .
```

## Basic Usage

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```text
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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`](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:

```bash
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:

```bash
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:

```text
Owner: c4rl0s04
Repository: ascii-motion
Workflow: release.yml
Environment: pypi
```

After the release workflow succeeds, verify the published package from a clean environment:

```bash
pipx install ascii-motion
ascii-motion --version
```

## Technical Pipeline

```text
VideoCapture -> resize -> luminance/edges/dither -> LUT ASCII -> ANSI render/export
```

Luminance uses the Rec. 709 formula:

```text
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:

```bash
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

```bash
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.
