Skip to content

YusufB5/ASCILINE

Repository files navigation

 █████╗ ███████╗ ██████╗██╗██╗     ██╗███╗   ██╗███████╗  ██
██╔══██╗██╔════╝██╔════╝██║██║     ██║████╗  ██║██╔════╝  █████
███████║███████╗██║     ██║██║     ██║██╔██╗ ██║█████╗    ████████
██╔══██║╚════██║██║     ██║██║     ██║██║╚██╗██║██╔══╝    ████████
██║  ██║███████║╚██████╗██║███████╗██║██║ ╚████║███████╗  █████
╚═╝  ╚═╝╚══════╝ ╚═════╝╚═╝╚══════╝╚═╝╚═╝  ╚═══╝╚══════╝  ██

ASCILINE is a high-performance, cross-platform real-time ASCII video rendering engine. It maps pixels to text-based representations and streams the result over a low-overhead binary protocol, turning the browser canvas into a typographic display surface.

Output Details
Original Source Original Source
Standard MP4 video file.
ASCII Mode ASCII Mode
Rendered using Mode 3 (32K colors) from a 30fps source.
PIXEL Mode PIXEL Mode
Rendered using Mode 5 (16M colors) combined with the --pixel flag for ultra-high fidelity.

Design Goals

  1. Pure typographic manipulation: the visual stream is raw HTML/Canvas text, not a standard media file. That means real-time CSS filters (glows, shadows, animations) can be applied directly to what would otherwise be a video.
  2. Zero GPU, ultra-low bandwidth (ASCII modes): standard codecs (H.264/VP9) need dedicated hardware decoders, which chokes microcontrollers and weak devices. ASCILINE does the heavy lifting server-side and streams lightweight text frames — fewer columns means proportionally less bandwidth. This makes fluid playback possible on constrained networks and zero-GPU devices (smart appliances, retro terminals, basic microcontrollers).
  3. Works everywhere: no <video> tag, no browser-side codec decoding, no autoplay restrictions. To the browser, it's just text on a canvas.

Roadmap idea, not implemented yet: because ASCII output is already a compact, structured text representation, it could in principle serve as a lightweight input for downstream text/LLM processing instead of feeding raw pixel streams to a vision model. Nothing in the current codebase does this — flagging it here as a direction, not a shipped feature.

Technical Features

  • Cross-platform: Windows, macOS, Linux.
  • Real-time ASCII and pixel streaming: low-latency video-to-text conversion; pixel mode replaces characters with colored blocks, approaching 360p quality.
  • HTML5 Canvas rendering, tuned for 24–30 FPS playback. Higher-FPS sources are automatically decimated for stability.
  • Master clock sync: the audio track is the absolute time reference, keeping A/V synchronized.
  • Low-overhead binary protocol: frames are streamed as raw Uint8Array straight to the canvas.
  • Multiple color modes: from black & white up to 16M-color ultra-fidelity.
  • Flexible video management: JSON playlists (per-video mode & volume), folder-based auto-queuing, single-file mode, infinite loop — all via CLI flags.

Architecture

  1. Backend (Python/FastAPI): decodes video via OpenCV, maps pixels to ASCII via NumPy, streams binary frames.
  2. Frontend (vanilla JS): receives binary frames over WebSocket, manages a jitter buffer, renders to a canvas grid.
  3. Communication: a custom INIT handshake negotiates resolution/FPS, followed by the binary frame stream.

Adaptive Frame Codec (opt-in, ASCII modes 2–5)

The original protocol re-sends the full grid every frame. An opt-in adaptive codec picks the smallest of several encodings per frame and tags it with a 1-byte header, without changing the rendered output:

tag encoding best for
0 RAW framebuffer as-is (legacy) incompressible frames
1 ZLIB zlib(framebuffer) general motion
2 DELTA only the cells that changed since the last frame static / low-motion
3 RLE_FULL run-length encoded framebuffer large flat-color regions

Clients opt in with /ws?codec=adaptive; omit it and you get the original protocol byte-for-byte, so existing clients are unaffected. A keyframe is forced periodically so dropped packets / late joiners resync.

codec.js (the shared decoder used by both the live player and the test suite) understands all four tags. Not every encoder produces all four, though: the Python side (codec.py, used by the live server and by static_player/compiler.py) can emit RLE_FULL when it wins the size comparison. The browser-side JS encoder (static_player/studio/encoder.js, used by the client-only Studio compiler) intentionally only emits RAW/ZLIB/DELTA — it doesn't implement RLE run-building, to keep the in-browser encoder simple. RAW/ZLIB/DELTA already cover most cases reasonably well, so this is a deliberate simplicity/size trade-off, not a bug — decoders stay permissive, encoders stay conservative.

Measured wire savings (mode 5, 200×80 grid):

content vs. legacy
static screen / slideshow 0.3% (≈375×)
high-motion / full-frame change 63% (never worse than legacy)

An optional --quality {lossless,high,balanced,low} enables lossy temporal delta: a color cell is only re-sent once it drifts past a tolerance from what the viewer already sees (the character plane stays exact), cutting the hard cases a further ~15–30% at imperceptible quality. Default is lossless (bit-exact).

Monitor bandwidth in real time: pass --debug when launching the server to see live RAW vs WIRE byte comparisons and the compression ratio in your terminal.

Verified two independent ways, both bit-exact: Python-encoded vectors decoded by codec.js in Node (experiments/gen_vectors.pyexperiments/check_vectors.js), and a live adaptive-vs-legacy WebSocket diff (experiments/test_e2e.js). Generate test clips with experiments/make_test_clips.sh.

LAN / network streaming: use --host to expose the server on your network.

python stream_server.py video.mp4 --host 0.0.0.0

Zero-Dependency Static Web Player

ASCILINE can compile a video into a self-contained .ascf (ASCII Compressed Format) file and play it back with a static HTML page — no Python backend at runtime, hostable anywhere (GitHub Pages, Vercel, Netlify).

Trade-off: compiled .ascf files are naturally larger than standard .mp4. In exchange you get true DOM-level interaction, pixel-perfect text selection, and no dependency on the browser's video codecs.

There are two ways to produce a .ascf file:

1. Python compiler (more capable and faster — the recommended default)

python static_player/compiler.py your_video.mp4 --cols 250 --pixel --quantize 2
  • --quantize 0-3: drops color bits to reduce file size (0 = lossless, 3 = aggressive).
  • --tolerance: color drift tolerance before a pixel update is sent, to skip invisible changes.
  • --hard: max zlib compression (level 9) — slower to compile, smaller output.

This is what powers the live demo at asciline.dev: the static clips there are compiled with this Python path.

2. Browser Studio — compile without installing anything

static_player/studio/ is a standalone page (index.html + encoder.js, using pako from a CDN for deflate) that compiles a video to .ascf entirely client-side — drop a video in, get a .ascf out, nothing ever leaves the browser and no Python is required. It's the more experimental of the two paths (see the codec note above: it only emits RAW/ZLIB/DELTA, not RLE_FULL) and is best for quick, short clips rather than production output — for anything longer or size-sensitive, use the Python compiler.

Playing a compiled file

Place the generated .ascf next to static_player/index.html and open it through any local web server.

Best practice: compile short clips (under 5–10 minutes). .ascf stores raw render instructions for the canvas, so full-length movies can produce file sizes that exceed your browser's memory limits.

Installation

1. Clone the repository

git clone https://github.com/YusufB5/ASCILINE.git
cd ASCILINE

2. Install dependencies

pip install fastapi uvicorn opencv-python numpy websockets

Running headless (server / no display, e.g. a VPS or container)? opencv-python-headless is a lighter drop-in replacement for opencv-python and avoids pulling in GUI dependencies you won't use.

Optional — play from YouTube (and other yt-dlp sites):

pip install yt-dlp

Only needed if you pass a URL instead of a local file. Local playback works without it. URL playback also uses FFmpeg (see below) to normalize downloads.

FFmpeg & FFprobe (required for audio and thumbnails)

Package manager (recommended):

  • Windows: winget install ffmpeg
  • macOS: brew install ffmpeg
  • Linux: sudo apt install ffmpeg

Manual (Windows): if you hit a FileNotFoundError or don't want to touch system variables, download the FFmpeg ZIP, extract ffmpeg.exe and ffprobe.exe from bin/, and drop both into the project folder next to stream_server.py.

3. Run the web server

Single video:

python stream_server.py video.mp4 --cols 240

YouTube / URL (requires yt-dlp):

python stream_server.py "https://youtu.be/VIDEO_ID" --cols 240
python stream_server.py "https://www.youtube.com/playlist?list=..." --cols 220 --loop

Garbage collection for cached downloads: ASCILINE includes an LRU cache limiter for on-demand YouTube downloads so disk usage doesn't grow unbounded.

python stream_server.py --cache-limit 5000   # cap the video cache at 5 GB (default 10240 MB)

How caching works:

  • ASCII rendering only needs a small grid, so yt-dlp fetches at ≤480p to save bandwidth.
  • Downloads are cached by video ID in videos/ — replays are instant.
  • Playlist/channel URLs and playlist.json expand into a queue and fetch on demand; the server starts immediately instead of waiting for bulk downloads.
  • Every downloaded video is normalized to H.264/AAC constant frame rate, so A/V sync holds regardless of the source codec.

Folder mode — drop videos into videos/ and run:

python stream_server.py --folder videos --cols 200
python stream_server.py --folder videos --cols 230 --loop
python stream_server.py --folder videos --mode 5 --pixel --cols 320 --vol 2

Videos play in filesystem order (as they appear in the folder, not alphabetically). Add/remove files to control the queue.

JSON playlist — per-video overrides:

python stream_server.py --playlist playlist.json --cols 220
python stream_server.py --playlist playlist.json --cols 220 --loop

Open http://localhost:8000 in your browser.

Player controls

Hover previews are built once per video on first hover, in a single ffmpeg pass, kept in memory — nothing written to disk. Disable with --no-thumbnails. To use a prebuilt sprite instead, point the /scrub route at it.

Live webcam streaming

python stream_server.py --webcam --cols 240

# Different camera device and target FPS
python stream_server.py --webcam --webcam-device 1 --webcam-fps 60

# Disable the automatic horizontal mirror
python stream_server.py --webcam --no-mirror

4. Run directly in a terminal (standalone)

Bypass the web interface and render inside an ANSI-capable terminal (zero flicker, true color):

python ascii_video_player2.py video.mp4 --cols 100 --quality 0

# Webcam directly in the terminal
python ascii_video_player2.py --webcam --cols 100

Don't resize the terminal window during playback — dynamic text wrapping will corrupt the layout.

Customization

Styling

Edit style.css to change accent colors and typography via CSS variables:

:root {
    --accent-color: #00ff41; /* Classic Matrix Green */
    --bg-color: #050505;
}

Real-time frontend filters & palettes (ASCII modes)

Click FX on the player controls (or press F) to open the filter overlay.

  • Contrast — adjust the difference between light and dark areas
  • Brightness — control the overall lightness of the output
  • Gamma — recover detail from dark/washed-out sources
  • Sharpen — Unsharp Mask, levels 0–10
  • Invert — instantly invert all brightness values
  • Palettes — swap character sets live:
    • Default: full detailed ASCII ramp
    • Flat/Anime: shortened, minimalist ramp (good for animation)
    • Block: chunky, dense characters for a retro-terminal look

Rendering modes

python stream_server.py --mode 5 --cols 240 --rows 100
  • 1: Black & White (DOM mode)
  • 2: 512 colors
  • 3: 32K colors
  • 4: 262K colors
  • 5: 16M colors (ultra)

Resolution & auto-scaling

Specify only --cols; ASCILINE derives --rows from the source aspect ratio.

  • ASCII mode: --cols 200240 (recommended starting point for the best balance of detail and 30 FPS performance; can be increased if your hardware allows).
  • Pixel mode: --cols 600900 (recommended starting point for near-HD quality; performance depends heavily on CPU).
  • If --cols isn't set, defaults are 450 in pixel mode and 200 in ASCII mode.
  • Hardware limits & A/V sync: pushing --cols beyond what your machine can encode/send in time causes the video to fall behind the audio (desync). If you see this, lower --cols.
python stream_server.py video.mp4 --mode 5 --cols 240
# Terminal shows: [AUTO] 1920x1080 → grid 240x67

Server-side volume control

--vol (0–5). At 0, FFmpeg's audio path never runs — saves CPU and bandwidth.

--vol Multiplier
0 Muted (no processing)
1 1.0× Normal (default)
3 1.5× Loud
5 2.0× Double volume
python stream_server.py video.mp4 --pixel --cols 560 --vol 0   # silent
python stream_server.py video.mp4 --cols 220 --vol 3           # loud

Playlist format (playlist.json)

Each entry can override the global --mode, --pixel, --vol, and --cols:

[
    { "video": "intro.mp4",  "mode": 1, "vol": 1 },
    { "video": "main.mp4",   "mode": 5, "pixel": true, "vol": 3, "cols": 520 },
    { "video": "https://youtu.be/VIDEO_ID", "mode": 3, "vol": 2, "cols": 240 }
]

Paths are resolved automatically — the project root and videos/ are both checked, so a filename alone is usually enough.

Live Demo

Live, browser-based showcase across multiple rendering modes: asciline.dev

Star History

Star History Chart

Support ❤️

If this project is useful to you, crypto donations are welcome:

  • Solana (SOL / USDC): H1wSQAhjgsu7AxenF4e5ZBYiBjkhDLVzkKaZuVPcrE14
  • Ethereum (ETH / USDT): 0x85B2f970045c0F7c282089Ab6CF897C20230e086
  • Bitcoin (BTC): bc1qvtcl55v54gkzwnp2zxn70usea3gf5ncncqa0fv

License

ASCILINE is distributed under a Custom License (Based on MIT) which includes an anti-advertisement clause. See LICENSE for the full text.

Community

Join the Codequerors Discord Server to discuss creative coding, share ideas, or contribute to ASCILINE.

Contact

asciline.engine@gmail.com

About

A high-performance, real-time ASCII video rendering engine. Streams binary-encoded frames via WebSockets for ultra-low latency, 30 FPS playback using HTML5 Canvas and requestAnimationFrame.

Resources

License

Stars

2.5k stars

Watchers

12 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors