High-performance terminal ASCII art converter for images, videos, live camera feeds, and retro game emulation
Installation • Quick Start • Usage • Documentation
PDASC transforms multimedia content into colored ASCII art with hardware-accelerated processing. Features include Numba JIT compilation for near-C performance, a custom .asc file format with Zstandard compression for instant playback, GPU-accelerated rendering for web deployment with edge detection support, and retro game emulation display powered by pdretro.
- Multiple Input Sources: Static images, video files, live camera feeds, retro game emulation, pre-encoded
.ascfiles - Hardware Acceleration: Numba JIT compilation and OpenGL shader-based rendering
- Advanced Processing: Customizable character sets from TrueType fonts, adjustable ASCII density and block size
- Edge Detection: Multi-pass rendering pipeline with Sobel edge detection for enhanced detail preservation
- Audio Support: Synchronized PCM16 audio in
.ascformat with real-time streaming - Instant Playback: Pre-rendered ANSI sequences with Zstandard compression (5-38× compression ratio)
- Web Interface: Interactive image converter and GPU-processed video player
- Emulator Support: Play retro games in ASCII art with keyboard controls via pdretro
pip install pdascpipx install pdascNote that pipx requires a C compiler to be installed.
Required:
- Python 3.10 or higher
- FFmpeg (download)
Platform-specific FFmpeg installation:
# Ubuntu/Debian
sudo apt install ffmpeg
# macOS
brew install ffmpeg
# Windows
# Download from ffmpeg.org and add to PATHTerminal requirements:
- 24-bit color support (most modern terminals)
- Test with:
printf "\x1b[38;2;255;100;0mTRUECOLOR\x1b[0m\n"
For emulator support:
- Libretro cores (download from RetroArch Buildbot)
- Game ROMs (legally obtained)
# Display an image
pdasc play image.png
# Play a video with audio
pdasc play video.mp4
# Stream from webcam
pdasc play camera
# Play a retro game in ASCII (experimental)
pdasc play emulator cores/snes9x_libretro.dll roms/game.sfc
# Encode for instant playback
pdasc encode video.mp4 -o output.asc
pdasc play output.asc
# Launch web interface
pdasc website
# GPU-accelerated web video with edge detection
pdasc website video.mp4pdasc play <input> [options]
# Examples
pdasc play photo.png -n 70 -b 4 # High detail image
pdasc play video.mp4 --no-audio # Silent video
pdasc play camera -c 1 -n 32 -b 8 # Webcam with custom settings
pdasc play emulator cores/snes9x_libretro.dll roms/mario.sfc -b 8 -n 32
pdasc website video.mp4 -n 32 # GPU-rendered web videoInput types:
- Image file path (PNG, JPG, GIF, BMP, TIFF, WebP)
- Video file path (MP4, AVI, MOV, MKV, WebM, FLV, WMV, M4V)
camerafor webcam inputemulator <core_path> <rom_path>for retro game emulation.ascfile path.asc.mp4file path (GPU-processed)
pdasc encode <input> -o <output.asc> [options]
# Examples
pdasc encode video.mp4 -o video.asc # Encode with defaults
pdasc encode image.png -o img.asc --no-color
pdasc encode large.mp4 -o out.asc -b 16 # Larger blocks = smaller filepdasc website [input]
# Examples
pdasc website # Interactive image converter
pdasc website video.mp4 # GPU-rendered video player with edge detection
pdasc website output.asc.mp4 # Play existing web video| Option | Short | Default | Description |
|---|---|---|---|
--block-size |
-b |
8 |
Pixels per character (2-32, must divide image dimensions) |
--num-ascii |
-n |
8 |
Number of ASCII characters (2-95) |
--font |
-f |
CascadiaMono.ttf |
TrueType font path for character generation |
--no-color |
Disable color output (grayscale only) | ||
--no-audio |
Disable audio playback | ||
--camera |
-c |
0 |
Camera index for camera input |
--output |
-o |
ascii_out.asc |
Output file path |
--debug |
Show FPS and debug info | ||
--port |
-p |
5000 |
Web server port (website only) |
Note on block size: Must be a factor of both image width and height. Common values: 2, 4, 8, 16, 32.
Note on website mode: GPU rendering with website only supports -n parameter, not -b.
PDASC can display retro games as ASCII art using pdretro, a Python wrapper for libretro cores.
- Keyboard only: Only keyboard input is supported
- Fixed keymap: Uses RetroArch default keyboard layout (see below)
- No customization: Keymapping configuration not yet available
- Future improvements: Controller support and custom keymaps coming in future releases
Arrow Keys D-pad movement
Z B button (bottom)
X A button (right)
A Y button (left)
S X button (top)
Q L shoulder
W R shoulder
E L2 trigger
R R2 trigger
Enter START
Right Shift SELECT
-
Download a libretro core from RetroArch Buildbot
- Example:
snes9x_libretro.dllfor SNES games - Example:
mgba_libretro.dllfor Game Boy Advance
- Example:
-
Obtain a ROM (legally - from games you own)
-
Run the emulator:
pdasc play emulator -core cores/snes9x_libretro.dll -rom roms/game.sfc -b 8 -n 32
- Use
-b 3or-b 2for 60 FPS on most systems - Add
--no-colorfor better performance (reduces processing) - Reduce
-nvalue (e.g.,-n 16) for faster rendering - Close other applications to free up CPU resources
The emulation functionality is powered by pdretro, a headless libretro wrapper I created specifically for programmatic emulation control. It provides frame-by-frame emulation, audio capture, and input handling without GUI dependencies.
The web video player uses a multi-pass OpenGL shader pipeline for real-time ASCII art rendering with edge detection, inspired by Acerola's ASCII shader technique.
The core concept works in two stages:
Fill ASCII (luminance-based):
- Extract luminance from the input frame
- Quantize to 10 discrete brightness levels
- Map each level to an ASCII character based on visual density (
.;coPO?@█)
Edge ASCII (edge-based):
- Apply Difference of Gaussians (DoG) filter to detect edges
- Use Sobel filter to compute edge angles
- Quantize angles into 4 directions: horizontal (0°), vertical (90°), and diagonals (45°, 135°)
- Determine the dominant edge direction in each 8×8 pixel block
- Map the dominant direction to an edge character
The original technique uses a compute shader that dispatches a thread group for each 8×8 pixel chunk, with each thread analyzing edge directions and writing to shared memory to find the dominant edge type. This implementation uses a simplified approach with iterative downsampling passes instead.
Fixed character set: The shader uses 4 edge ASCII characters and 8 fill ASCII characters. This configuration is not user-configurable.
The rendering pipeline consists of these sequential passes:
Input Frame
↓
1. Luminance Extraction
↓
2. Gaussian Blur (Horizontal)
↓
3. Gaussian Blur (Vertical) + DoG Edge Detection
↓
4. Sobel Edge Detection (Horizontal)
↓
5. Sobel Edge Detection (Vertical)
↓
6. Color/Luminance Packing
↓
7. Downsampling Chain (1/2 → 1/4 → 1/8)
↓
8. ASCII Character Selection
↓
Output Video (.asc.mp4)Each 8×8 pixel block is analyzed to determine whether to use an edge character (based on edge threshold and direction) or a fill character (based on luminance).
Example (Acerola's original implementation):
Note: This example showcases Acerola's original shader.
The .asc (ASCII Container) format stores pre-rendered ANSI escape sequences compressed with Zstandard, enabling instant playback with zero conversion overhead.
The format prioritizes playback performance over storage efficiency, typically requiring ~20× more storage than the original before compression. Zstandard compression (level 5) reduces this significantly:
Example (5326 frames, 720p, block-size 4, num-ascii 32):
- Original H.264 video: 48.3 MB
- Uncompressed ANSI: 3.62 GB
- Compressed colored: 722.15 MB (~5× compression)
- Compressed grayscale: 93.55 MB (~38.7× compression)
- Audio PCM16: 35.85 MB
Header (24 bytes)
| Offset | Size | Type | Description |
|---|---|---|---|
| 0x00 | 4 | char | Magic: "ASCI" |
| 0x04 | 2 | uint16 | Version (currently 2) |
| 0x06 | 2 | uint16 | Flags (IS_VIDEO=0x01, HAS_AUDIO=0x02) |
| 0x08 | 4 | float | FPS |
| 0x0C | 4 | uint32 | Frame count |
| 0x10 | 8 | - | Reserved |
Frame Index Section
- Array of uint32 values, one per frame, storing uncompressed lengths
Compressed Frame Data
- 4-byte uint32: compressed data size
- Zstandard-compressed concatenated frames (ANSI strings)
Audio Section (optional)
- 4-byte uint32: audio data size
- 1-byte uint8: format (1 = PCM16)
- 4-byte uint32: sample rate
- 1-byte uint8: channels
- Raw PCM16 audio data
The system analyzes TrueType fonts to create optimal character-to-brightness mappings:
- Render each printable ASCII character at 48×48 pixels
- Calculate average luminance per character
- Map characters to quantized luminance values
- Create uniform grayscale ramp (e.g., " .:-=+*#%@")
Input Image/Frame
↓
Divide into blocks (e.g., 8×8 pixels)
↓
Compute average color per block (Numba-accelerated)
↓
Calculate luminance: L = 0.2126R + 0.7152G + 0.0722B
↓
Map luminance to character index
↓
Generate ANSI escape sequence with RGB color
↓
Compress with Zstandard (encode) or Render (play)Core processing loops are JIT-compiled with Numba for near-C performance:
@njit(parallel=True, fastmath=True, cache=True)
def compute_blocks(img, cs, gray_levels, color):
# Parallel block processing
# Significantly faster than pure PythonBrowser playback uses ModernGL with OpenGL shaders (browsers cannot efficiently update thousands of span elements at 30+ FPS):
- Fragment shader processes each pixel in parallel
- Converts RGB to ASCII character lookup
- Applies edge detection for enhanced detail
- Outputs standard
.asc.mp4video file for smooth browser playback
| Block Size | Resolution | Performance | Use Case |
|---|---|---|---|
| 2×2 | Very High | Slower | Emulator: maximum detail |
| 3×3 | Very High | Good | Emulator: 60 FPS sweet spot |
| 4×4 | Very High | Good | High-quality images and videos |
| 8×8 | High | Better | Default, recommended |
| 16×16 | Medium | Best | Lower-end hardware |
| 32×32 | Low | Fastest | Very limited hardware |
| Count | Detail | Use Case |
|---|---|---|
| 4-16 | Low | Artistic effect, retro aesthetic |
| 32-64 | Medium | Good balance |
| 70-95 | High | Maximum detail preservation |
- Real-time (webcam): Use
-b 8 -n 32 - High-quality images: Use
-b 4 -n 70 - Video playback: Always encode to
.ascfirst - Web playback: Use
websitecommand with GPU acceleration - Storage constraints: Use grayscale (
--no-color) for 5-8× smaller files - Emulator (60 FPS): Use
-b 2 -n 16or-b 3 -n 32with--no-color
pdasc play landscape.png -n 50 -b 8pdasc play camera -b 6 -n 40pdasc play artwork.png --no-color -n 95 -b 2pdasc play emulator cores/snes9x_libretro.dll roms/game.sfc -b 3 -n 32Emulator screenshot coming soon
# Error: "Could not open camera 0"
# Try different camera index:
pdasc play camera -c 1
# Linux: Check available cameras
ls /dev/video*Frames dropping or stuttering:
- Increase block size:
-b 16 - Reduce ASCII density:
-n 32 - Encode to
.ascformat first (don't convert in real-time)
Audio out of sync:
- Always encode to
.ascformat for perfect synchronization
Game running too slow:
- Use smaller block size:
-b 2or-b 3 - Reduce ASCII characters:
-n 16or-n 24 - Disable color:
--no-color - Close background applications
Input not working:
- Ensure keyboard focus is on terminal window
- Try different keys (current keymap is fixed to RetroArch defaults)
- Check that core and ROM are compatible
Core/ROM not loading:
- Verify core path is correct
- Ensure ROM format matches core (e.g.,
.sfcfor SNES) - Download cores from official RetroArch buildbot
Colors not displaying:
- Verify 24-bit color support:
printf "\x1b[38;2;255;100;0mTRUECOLOR\x1b[0m\n" - Try different terminal (Windows Terminal, iTerm2, Alacritty)
Display cut off:
- Maximize terminal window
- Use smaller block sizes for more content in limited space
FFmpeg not found:
- Install FFmpeg and ensure it's in PATH
- Verify:
ffmpeg -version
Out of memory during encoding:
- Use larger block sizes (
-b 16or-b 32) - Close other applications
# Error: Block size must be a factor of image dimensions
# For 1920×1080: valid sizes include 2, 4, 5, 8, 10, 12, 15, 16, 20, 24
pdasc play image.png -b 8 # Works for most imagesMIT License - Copyright (c) 2026 Colin Politi (ColinThePanda)
See LICENSE for full text.
- PyPI: https://pypi.org/project/PDASC/
- GitHub: https://github.com/ColinThePanda/PDASC
- Issues: https://github.com/ColinThePanda/PDASC/issues
- pdretro: https://github.com/ColinThePanda/pdretro
- GPU-accelerated ASCII video shader based on "I Tried Turning Games Into Text" by Acerola (Garrett Gunnell)
- Emulation powered by pdretro - a Python libretro wrapper





