Skip to content

Repository files navigation

    ____  __            ________    ____
   / __ \/ /___ ___  __/ ____/ /   /  _/
  / /_/ / / __ `/ / / / /   / /    / /
 / ____/ / /_/ / /_/ / /___/ /____/ /
/_/   /_/\__,_/\__, /\____/_____/___/
              /____/
PlayCLI Demo

PlayCLI — Live Terminal Video Player

PlayCLI is a real-time terminal video player that renders videos directly in your terminal using 24-bit true color ANSI escape sequences. Built with NumPy-vectorized rendering for speed and FFmpeg-backed decoding for broad format support.

Features

  • Five render modes — half-block, ASCII art, braille, dither, and edge detection
  • True 24-bit color — full RGB per pixel, not 256-color palettes
  • Real-time playback — frame-accurate timing at native video FPS
  • Audio sync — optional audio playback via sounddevice + soundfile
  • Keyboard controls — pause, seek, speed control, volume, frame-stepping, screenshot
  • Three quality levels — from fast nearest-neighbor to full box-filter downscaling
  • Adaptive quality — auto-drops quality when your CPU can't keep up
  • Custom scaling — fill any fraction of your terminal size
  • Subtitle support — SRT overlay with auto-detection
  • Playlist mode — play multiple files in sequence
  • URL support — stream from YouTube and other sites (needs yt-dlp)
  • Image mode — display static images in the terminal
  • Screenshot — save current frame as PNG + ANSI HTML
  • Config file — persistent defaults via ~/.config/playcli/config.json
  • Cross-platform — macOS, Linux, Windows (any terminal with true color support)

How It Works

Every terminal cell gets its own \033[38;2;R;G;Bm foreground and \033[48;2;R;G;Bm background escape sequence. By pairing this with the Unicode half-block character , each cell encodes two pixel rows — one in the foreground color, one in the background. This gives 2× effective vertical resolution out of the character grid with no tricks.

Frame downscaling uses area averaging by default: every source pixel within a cell's coverage area contributes to the final color, eliminating the shimmer and aliasing that nearest-neighbor produces on motion.

All rendering is vectorized with NumPy — no Python loops over individual pixels. The FFmpeg decode runs through imageio-ffmpeg which shells out to a native binary, so the decode path never touches Python either.

Requirements

  • Python 3.9 or later
  • A terminal with 24-bit true color support

Supported Terminals

Terminal Platform Status
Windows Terminal Windows ✔ Works
iTerm2 macOS ✔ Works
Kitty Linux / macOS ✔ Works
WezTerm All ✔ Works
Alacritty All ✔ Works
GNOME Terminal 3.36+ Linux ✔ Works
Terminal.app macOS ✗ Does not work (256 colors only)

To verify your terminal supports true color:

echo $COLORTERM
# Should output: truecolor

On Windows Terminal, true color is supported by default.

Installation

Quick Setup (macOS / Linux)

bash setup.sh

This installs dependencies and links the playcli command to /usr/local/bin/playcli.

Quick Setup (Windows)

setup.bat

Manual Installation

  1. Install Python dependencies:
pip install numpy imageio imageio-ffmpeg

For audio support:

pip install sounddevice soundfile

For subtitle support:

pip install Pillow

For everything:

pip install sounddevice soundfile Pillow
  1. Run directly:
python3 ascii_play/cli.py video.mp4
  1. Make it available system-wide (macOS / Linux):
chmod +x playcli.sh
sudo ln -sf "$(pwd)/playcli.sh" /usr/local/bin/playcli

Now you can run playcli video.mp4 from anywhere.

Windows PATH Setup

  1. Press Win+R, type sysdm.cpl
  2. Advanced tab → Environment Variables
  3. Under User Variables, select Path and click Edit
  4. Click New and paste the full path to the PlayCLI folder
  5. Click OK and restart your terminal

Usage

playcli <video> [options]              Play a video (smart default)
playcli play <video> [options]         Play with explicit options
playcli image <image> [options]        Display a static image
playcli modes                          List render modes
playcli help                           Show help

Smart dispatch: playcli video.mp4 and playcli photo.png auto-detect the file type.

Options

Flag Description Default
-m, --mode MODE Render mode: half, ascii, braille, dither, or edge half
-q, --quality N Quality level: 1 (fast), 2 (smooth), or 3 (best) 2
-s, --scale F Fraction of terminal to fill (0.1–1.0) 1.0
-S, --speed F Playback speed 0.25–4.0 1.0
--loop Loop the video indefinitely off
--no-info Hide the status bar at the bottom off
--no-audio Disable audio playback off
--subtitle FILE SRT subtitle file auto-detected
--adaptive Auto-lower quality when frame rate drops off

Examples

# Play with defaults
playcli video.mp4

# Best quality downscaling
playcli video.mp4 -q 3

# Braille mode, loop forever
playcli video.mp4 -m braille --loop

# Use 80% of terminal, no status bar
playcli video.mp4 -s 0.8 --no-info

# Classic ASCII art look
playcli video.mp4 -m ascii

# 1.5x speed with audio
playcli video.mp4 -S 1.5

# Playlist mode
playcli play *.mp4

# Static image
playcli image photo.png -m dither

# URL streaming (needs yt-dlp)
playcli play https://youtu.be/dQw4w9WgXcQ

# With subtitles
playcli video.mp4 --subtitle subs.srt

# Adaptive quality
playcli video.mp4 --adaptive

Keyboard Controls

Key Action
Space Pause / Resume
Seek forward 5 seconds
Seek backward 5 seconds
= / + Speed up
- / _ Slow down
] Volume up
[ Volume down
. Step forward one frame (when paused)
, Step back one frame (when paused)
s Save screenshot (PNG + HTML)
q Quit

Config File

PlayCLI reads ~/.config/playcli/config.json for persistent defaults:

{
  "mode": "half",
  "quality": 3,
  "scale": 0.85,
  "speed": 1.0,
  "loop": false,
  "adaptive": true
}

Supported keys: mode, quality, scale, speed, loop, adaptive.

Render Modes

Half-block (default)

Uses the Unicode half-block character . The foreground color maps to the top pixel row of each cell, the background color to the bottom. This encodes 2 pixel rows per terminal row, giving double the vertical resolution of any character-based approach. Combined with 24-bit color, this is the highest quality mode.

Best for: everything. Use this unless you have a specific reason not to.

ASCII

Maps grayscale brightness to a density character set (@%#*+=-:. ) and applies the source pixel color as the foreground. Familiar look, lower spatial resolution than half-block, but the true-color tinting makes it look significantly better than traditional ASCII art renderers.

Best for: aesthetic preference, lower-contrast content.

Braille

Each braille character cell covers a 2-wide by 4-tall pixel region. Each dot in the braille pattern is lit or unlit based on whether its corresponding source pixel crosses a brightness threshold. This gives the highest spatial resolution of the three modes. Colors are averaged across the 8-pixel cell.

Best for: high-contrast content, line art, animation with sharp edges.

Dither

Ordered Bayer dithering to a 6×6×6 color cube (216 colors). Applies a 4×4 Bayer threshold matrix before quantization, producing a retro posterized aesthetic. Uses full 24-bit color output but the visible palette looks like a limited-pixel-art style.

Best for: artistic effect, retro/vaporwave aesthetics, pixel art.

Edge

Sobel edge detection blended with the original color. Detects edges in the frame using 3×3 Sobel kernels and blends them with the source color — bright edges pop through while non-edge areas darken. The result looks like a glowing wireframe overlay.

Best for: high-contrast content, cyberpunk aesthetic, technical/line art.

Quality Levels

Controls how source pixels are sampled when downscaling to terminal resolution.

Flag Method Notes
-q 1 Nearest-neighbor One source pixel per cell. Fastest. Shimmers on fine detail and motion.
-q 2 4-tap supersample Samples 4 points per cell and averages. Default. Eliminates most aliasing with near-zero extra cost.
-q 3 Full box filter Every source pixel within a cell's coverage area is averaged in. Equivalent to a proper video downscaler. Best quality, highest CPU use.

Adaptive Quality

With --adaptive, PlayCLI monitors render time per frame. If the renderer consistently exceeds 85% of the frame budget, quality drops one level. If it stays under 25% of the budget, quality rises. This keeps playback smooth on lower-end hardware or high-FPS videos without manual tuning.

Subtitles

PlayCLI supports SRT subtitle overlays. Pass a subtitle file explicitly:

playcli video.mp4 --subtitle captions.srt

Or place an .srt file next to your video with the same base name — PlayCLI auto-detects it:

# Auto-detects movie.srt
playcli movie.mp4

Subtitles require Pillow (pip install Pillow). Without it, subtitles are silently skipped.

URL / Streaming Support

PlayCLI can stream from URLs when yt-dlp is installed:

playcli play https://youtu.be/dQw4w9WgXcQ

If yt-dlp is not available, PlayCLI falls back to passing the URL directly to FFmpeg (may work for direct media URLs).

Screenshots

Press s during playback to save the current frame. Files are saved to ~/Pictures/playcli/:

  • playcli_<timestamp>.png — raw frame as PNG (requires Pillow)
  • playcli_<timestamp>.html — ANSI-rendered frame as HTML (viewable in any browser)

Project Structure

PlayCLI/
├── ascii_play/
│   ├── __init__.py       Version and public API
│   ├── __main__.py       Enables: python -m ascii_play video.mp4
│   ├── cli.py            Entry point — argument parsing and dispatch
│   ├── ansi.py           ANSI escape code helpers
│   ├── resize.py         Frame downscaling (3 quality levels)
│   ├── renderers.py      5 render modes (half/ascii/braille/dither/edge)
│   ├── player.py         FFmpeg decode loop, audio sync, keyboard controls
│   └── subtitle.py       SRT parser + Pillow-based text overlay
├── playcli.sh            macOS / Linux wrapper script
├── playcli.bat           Windows wrapper script
├── setup.sh              macOS / Linux one-shot setup
├── setup.bat             Windows one-shot setup
├── pyproject.toml        Python package configuration
└── README.md             This file

The package modules are independent with no circular imports. cli.py is the only entry point — it imports from the package and dispatches to player.py. Adding a new render mode means adding a function to renderers.py and registering it in the MODES dict — nothing else needs to change.

Running Without Installing

# From the repo root
python3 ascii_play/cli.py video.mp4

# Or as a module
python -m ascii_play video.mp4

Audio Support

Audio playback is optional. Install the extra dependencies:

pip install sounddevice soundfile

PlayCLI will automatically sync video to audio when these are available. Audio is extracted to a temporary WAV file during playback and cleaned up on exit.

Windows users may need to install the sounddevice portaudio backend:

pip install sounddevice PortAudio

To disable audio even when dependencies are installed:

playcli video.mp4 --no-audio

Technical Details

ANSI Rendering Pipeline

  1. Frame is decoded from video via FFmpeg (RGB24 format)
  2. Frame is downscaled to terminal resolution using NumPy
  3. Each terminal cell is assigned foreground/background colors
  4. Output string is built with ANSI escape codes + Unicode characters
  5. String is written to stdout in a single write call
  6. Terminal renders the frame with hardware-accelerated text rendering

Why Not mpv -vo caca?

mpv -vo caca uses a 256-color palette and nearest-neighbor sampling. PlayCLI uses full RGB ANSI escape sequences with area-averaged downscaling — the difference is visible, especially on fine detail and gradients.

Terminal Raw Mode

PlayCLI uses tty.setraw() for non-blocking keyboard input during playback. This disables terminal line buffering and echo. The terminal settings are always restored on exit (including crashes via finally block).

License

GNU General Public License v3.0 (GPL-3.0)

About

watch videos in your terminal — half-block, ASCII, and braille modes with true color

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages