YusufB5/ASCILINEPublic

A high-performance ASCII video rendering engine featuring real-time WebSocket binary streaming and an isolated compiler for serverless static generation. Built for low-latency 30 FPS playback on HTML5 Canvas.

AI summary: High-performance ASCII video rendering engine with real-time WebSocket binary streaming.

Stars
2.6K
+9 today
Forks
298
Watchers
12
Open issues
1
Open PRs
0
Contributors
~11
Commits
172
Branches
4

PythonOtherCreated May 1, 2026Last push 1d ago+20 stars this week+28 this month

Star history

since May 3, 2026
01K2KMay 2026Jun 2026Jul 2026Aug 2026
2.6K stars as of Aug 7, 2026, tracked back to May 3, 2026. Historical curve reconstructed from public GitHub event archives, calibrated to the current total.

Contribution activity

commits per day, last 52 weeks
AugSepOctNovDecJanFebMarAprMayJunJulAugMonWedFri2025-08-10: 0 commits2025-08-11: 0 commits2025-08-12: 0 commits2025-08-13: 0 commits2025-08-14: 0 commits2025-08-15: 0 commits2025-08-16: 0 commits2025-08-17: 0 commits2025-08-18: 0 commits2025-08-19: 0 commits2025-08-20: 0 commits2025-08-21: 0 commits2025-08-22: 0 commits2025-08-23: 0 commits2025-08-24: 0 commits2025-08-25: 0 commits2025-08-26: 0 commits2025-08-27: 0 commits2025-08-28: 0 commits2025-08-29: 0 commits2025-08-30: 0 commits2025-08-31: 0 commits2025-09-01: 0 commits2025-09-02: 0 commits2025-09-03: 0 commits2025-09-04: 0 commits2025-09-05: 0 commits2025-09-06: 0 commits2025-09-07: 0 commits2025-09-08: 0 commits2025-09-09: 0 commits2025-09-10: 0 commits2025-09-11: 0 commits2025-09-12: 0 commits2025-09-13: 0 commits2025-09-14: 0 commits2025-09-15: 0 commits2025-09-16: 0 commits2025-09-17: 0 commits2025-09-18: 0 commits2025-09-19: 0 commits2025-09-20: 0 commits2025-09-21: 0 commits2025-09-22: 0 commits2025-09-23: 0 commits2025-09-24: 0 commits2025-09-25: 0 commits2025-09-26: 0 commits2025-09-27: 0 commits2025-09-28: 0 commits2025-09-29: 0 commits2025-09-30: 0 commits2025-10-01: 0 commits2025-10-02: 0 commits2025-10-03: 0 commits2025-10-04: 0 commits2025-10-05: 0 commits2025-10-06: 0 commits2025-10-07: 0 commits2025-10-08: 0 commits2025-10-09: 0 commits2025-10-10: 0 commits2025-10-11: 0 commits2025-10-12: 0 commits2025-10-13: 0 commits2025-10-14: 0 commits2025-10-15: 0 commits2025-10-16: 0 commits2025-10-17: 0 commits2025-10-18: 0 commits2025-10-19: 0 commits2025-10-20: 0 commits2025-10-21: 0 commits2025-10-22: 0 commits2025-10-23: 0 commits2025-10-24: 0 commits2025-10-25: 0 commits2025-10-26: 0 commits2025-10-27: 0 commits2025-10-28: 0 commits2025-10-29: 0 commits2025-10-30: 0 commits2025-10-31: 0 commits2025-11-01: 0 commits2025-11-02: 0 commits2025-11-03: 0 commits2025-11-04: 0 commits2025-11-05: 0 commits2025-11-06: 0 commits2025-11-07: 0 commits2025-11-08: 0 commits2025-11-09: 0 commits2025-11-10: 0 commits2025-11-11: 0 commits2025-11-12: 0 commits2025-11-13: 0 commits2025-11-14: 0 commits2025-11-15: 0 commits2025-11-16: 0 commits2025-11-17: 0 commits2025-11-18: 0 commits2025-11-19: 0 commits2025-11-20: 0 commits2025-11-21: 0 commits2025-11-22: 0 commits2025-11-23: 0 commits2025-11-24: 0 commits2025-11-25: 0 commits2025-11-26: 0 commits2025-11-27: 0 commits2025-11-28: 0 commits2025-11-29: 0 commits2025-11-30: 0 commits2025-12-01: 0 commits2025-12-02: 0 commits2025-12-03: 0 commits2025-12-04: 0 commits2025-12-05: 0 commits2025-12-06: 0 commits2025-12-07: 0 commits2025-12-08: 0 commits2025-12-09: 0 commits2025-12-10: 0 commits2025-12-11: 0 commits2025-12-12: 0 commits2025-12-13: 0 commits2025-12-14: 0 commits2025-12-15: 0 commits2025-12-16: 0 commits2025-12-17: 0 commits2025-12-18: 0 commits2025-12-19: 0 commits2025-12-20: 0 commits2025-12-21: 0 commits2025-12-22: 0 commits2025-12-23: 0 commits2025-12-24: 0 commits2025-12-25: 0 commits2025-12-26: 0 commits2025-12-27: 0 commits2025-12-28: 0 commits2025-12-29: 0 commits2025-12-30: 0 commits2025-12-31: 0 commits2026-01-01: 0 commits2026-01-02: 0 commits2026-01-03: 0 commits2026-01-04: 0 commits2026-01-05: 0 commits2026-01-06: 0 commits2026-01-07: 0 commits2026-01-08: 0 commits2026-01-09: 0 commits2026-01-10: 0 commits2026-01-11: 0 commits2026-01-12: 0 commits2026-01-13: 0 commits2026-01-14: 0 commits2026-01-15: 0 commits2026-01-16: 0 commits2026-01-17: 0 commits2026-01-18: 0 commits2026-01-19: 0 commits2026-01-20: 0 commits2026-01-21: 0 commits2026-01-22: 0 commits2026-01-23: 0 commits2026-01-24: 0 commits2026-01-25: 0 commits2026-01-26: 0 commits2026-01-27: 0 commits2026-01-28: 0 commits2026-01-29: 0 commits2026-01-30: 0 commits2026-01-31: 0 commits2026-02-01: 0 commits2026-02-02: 0 commits2026-02-03: 0 commits2026-02-04: 0 commits2026-02-05: 0 commits2026-02-06: 0 commits2026-02-07: 0 commits2026-02-08: 0 commits2026-02-09: 0 commits2026-02-10: 0 commits2026-02-11: 0 commits2026-02-12: 0 commits2026-02-13: 0 commits2026-02-14: 0 commits2026-02-15: 0 commits2026-02-16: 0 commits2026-02-17: 0 commits2026-02-18: 0 commits2026-02-19: 0 commits2026-02-20: 0 commits2026-02-21: 0 commits2026-02-22: 0 commits2026-02-23: 0 commits2026-02-24: 0 commits2026-02-25: 0 commits2026-02-26: 0 commits2026-02-27: 0 commits2026-02-28: 0 commits2026-03-01: 0 commits2026-03-02: 0 commits2026-03-03: 0 commits2026-03-04: 0 commits2026-03-05: 0 commits2026-03-06: 0 commits2026-03-07: 0 commits2026-03-08: 0 commits2026-03-09: 0 commits2026-03-10: 0 commits2026-03-11: 0 commits2026-03-12: 0 commits2026-03-13: 0 commits2026-03-14: 0 commits2026-03-15: 0 commits2026-03-16: 0 commits2026-03-17: 0 commits2026-03-18: 0 commits2026-03-19: 0 commits2026-03-20: 0 commits2026-03-21: 0 commits2026-03-22: 0 commits2026-03-23: 0 commits2026-03-24: 0 commits2026-03-25: 0 commits2026-03-26: 0 commits2026-03-27: 0 commits2026-03-28: 0 commits2026-03-29: 0 commits2026-03-30: 0 commits2026-03-31: 0 commits2026-04-01: 0 commits2026-04-02: 0 commits2026-04-03: 0 commits2026-04-04: 0 commits2026-04-05: 0 commits2026-04-06: 0 commits2026-04-07: 0 commits2026-04-08: 0 commits2026-04-09: 0 commits2026-04-10: 0 commits2026-04-11: 0 commits2026-04-12: 0 commits2026-04-13: 0 commits2026-04-14: 0 commits2026-04-15: 0 commits2026-04-16: 0 commits2026-04-17: 0 commits2026-04-18: 0 commits2026-04-19: 0 commits2026-04-20: 0 commits2026-04-21: 0 commits2026-04-22: 0 commits2026-04-23: 0 commits2026-04-24: 0 commits2026-04-25: 0 commits2026-04-26: 0 commits2026-04-27: 0 commits2026-04-28: 0 commits2026-04-29: 0 commits2026-04-30: 0 commits2026-05-01: 0 commits2026-05-02: 1 commit2026-05-03: 2 commits2026-05-04: 0 commits2026-05-05: 2 commits2026-05-06: 0 commits2026-05-07: 0 commits2026-05-08: 0 commits2026-05-09: 0 commits2026-05-10: 0 commits2026-05-11: 0 commits2026-05-12: 0 commits2026-05-13: 0 commits2026-05-14: 0 commits2026-05-15: 0 commits2026-05-16: 0 commits2026-05-17: 0 commits2026-05-18: 0 commits2026-05-19: 0 commits2026-05-20: 0 commits2026-05-21: 0 commits2026-05-22: 0 commits2026-05-23: 0 commits2026-05-24: 0 commits2026-05-25: 0 commits2026-05-26: 0 commits2026-05-27: 0 commits2026-05-28: 0 commits2026-05-29: 0 commits2026-05-30: 0 commits2026-05-31: 0 commits2026-06-01: 0 commits2026-06-02: 0 commits2026-06-03: 0 commits2026-06-04: 4 commits2026-06-05: 7 commits2026-06-06: 2 commits2026-06-07: 2 commits2026-06-08: 2 commits2026-06-09: 0 commits2026-06-10: 4 commits2026-06-11: 5 commits2026-06-12: 4 commits2026-06-13: 9 commits2026-06-14: 6 commits2026-06-15: 8 commits2026-06-16: 0 commits2026-06-17: 2 commits2026-06-18: 2 commits2026-06-19: 9 commits2026-06-20: 1 commit2026-06-21: 8 commits2026-06-22: 4 commits2026-06-23: 4 commits2026-06-24: 3 commits2026-06-25: 0 commits2026-06-26: 1 commit2026-06-27: 0 commits2026-06-28: 0 commits2026-06-29: 0 commits2026-06-30: 0 commits2026-07-01: 0 commits2026-07-02: 0 commits2026-07-03: 0 commits2026-07-04: 0 commits2026-07-05: 0 commits2026-07-06: 0 commits2026-07-07: 2 commits2026-07-08: 1 commit2026-07-09: 1 commit2026-07-10: 3 commits2026-07-11: 7 commits2026-07-12: 2 commits2026-07-13: 0 commits2026-07-14: 7 commits2026-07-15: 3 commits2026-07-16: 1 commit2026-07-17: 1 commit2026-07-18: 0 commits2026-07-19: 2 commits2026-07-20: 2 commits2026-07-21: 3 commits2026-07-22: 7 commits2026-07-23: 3 commits2026-07-24: 0 commits2026-07-25: 2 commits2026-07-26: 2 commits2026-07-27: 0 commits2026-07-28: 2 commits2026-07-29: 2 commits2026-07-30: 2 commits2026-07-31: 4 commits2026-08-01: 0 commits2026-08-02: 0 commits2026-08-03: 0 commits2026-08-04: 0 commits2026-08-05: 2 commits2026-08-06: 0 commits2026-08-07: 0 commits2026-08-08: 0 commits
153 commits in the last yearLessMore

Signals and awards

derived from tracked data
  • Actively maintained

    Pushed within 48 hours

  • Continuous integration

    Automated checks passing

What ASCILINE does

ASCILINE is a cross-platform engine designed to render ASCII video in real-time. It uses an isolated compiler for serverless static generation. The system handles low-latency WebSocket binary streaming to deliver fluid ASCII animations. It is specifically optimized to achieve 30 FPS playback on HTML5 Canvas. The architecture allows it to run efficiently without requiring heavy client-side processing.

Frontend developers and creative coders looking for a unique, retro visual style. It is ideal for those needing performant, text-based video rendering.

  • Real-time streaming: Handles binary streaming via WebSockets for low latency.
  • High frame rate: Optimized for 30 FPS rendering on HTML5 Canvas.
  • Isolated compiler: Enables serverless static generation of ASCII frames.
  • Cross-platform support: Works across different operating systems and browsers.
  • Low latency: Minimizes delay in video playback and rendering.

Where teams use it

Browser games

Integrate ASCII art animations directly into HTML5 games.

Terminal emulation

Display video content in a retro, text-based format.

Live streaming

Stream video content to clients using purely ASCII characters.

Serverless deployment

Deploy ASCII rendering engines using serverless functions.

Getting started: npm install asciline

README

main branch
 █████╗ ███████╗ ██████╗██╗██╗     ██╗███╗   ██╗███████╗  ██
██╔══██╗██╔════╝██╔════╝██║██║     ██║████╗  ██║██╔════╝  █████
███████║███████╗██║     ██║██║     ██║██╔██╗ ██║█████╗    ████████
██╔══██║╚════██║██║     ██║██║     ██║██║╚██╗██║██╔══╝    ████████
██║  ██║███████║╚██████╗██║███████╗██║██║ ╚████║███████╗  █████
╚═╝  ╚═╝╚══════╝ ╚═════╝╚═╝╚══════╝╚═╝╚═╝  ╚═══╝╚══════╝  ██

YusufB5%2FASCILINE | Trendshift YusufB5%2FASCILINE | Trendshift YusufB5%2FASCILINE | Trendshift

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 4 (32K colors) from a 30fps source.
PIXEL Mode PIXEL Mode
Rendered using the --pixel flag for high fidelity colored blocks █ .

Table of Contents

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 high 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.

ASCILINE uses a modular architecture that separates the core rendering engine from its delivery methods.

ASCILINE/
# --- 1. Core Processing & Codec Engine ---
├── ascii_video_player2.py       # Core VideoDecoder, AsciiMapper & standalone terminal player
├── codec.py                     # Master Python encoder (RAW/ZLIB/DELTA/RLE/DCT)
├── codec.js                     # Root JS decoder (Optimized for Live WebSocket streaming)
│
# --- 2. Live Streaming Web Client ---
├── index.html                   # Web client UI for the live streaming server
├── app.js                       # Frontend WebSocket connection and Canvas render loop
├── style.css                    # UI styling, responsive layout, and real-time FX
│
# --- 3. Standalone Ecosystem & Compilers ---
├── compiler.py                  # CLI Python compiler: Converts videos into .ascf format
├── 📁 static_player/            # Web player for .ascf files
│   ├── index.html               # Main UI for the static web player
│   ├── reader.js                # .ascf file parser, chunk loader, and buffer manager
│   ├── codec.js                 # Standalone JS decoder (Optimized for static buffers)
│   └── 📁 studio/               # Browser-based compiler IDE
│       ├── index.html           # Studio UI with built-in preview and seekbar
│       └── encoder.js           # Client-side encoder to compile videos locally
│
# --- 4. Server & Backend Services ---
├── stream_server.py             # FastAPI WebSocket server for real-time video streaming
├── ytdl.py                      # yt-dlp integration for dynamic YouTube/URL fetching
│
# --- 5. Development & Testing ---
├── 📁 experiments/              # Codec benchmarks, test vectors & experimental scripts
├── 📁 test/                     # E2E tests, unit tests & backpressure validation
│
# --- 6. CLI Assets & Cache ---
├── logo.py                      # ASCII branding banner displayed on startup
├── 📁 videos/                   # Auto-managed local cache directory for downloaded media
│
# --- 7. Configuration & Infrastructure ---
├── playlist.json                # Playback queue and per-video overrides
├── Dockerfile                   # Docker container configuration
├── docker-compose.yml           # Multi-service Docker setup
├── pyproject.toml               # Python project metadata, dependencies & optional extras
└── requirements.txt             # Python dependencies

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

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
4 DCT Discrete Cosine Transform High-ratio spatial compression. Used exclusively by the static player. Automatically enforces --pixel output.

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 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 6, 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 compiler.py your_video.mp4 --cols 250 --pixel --quantize 2
  • --quantize 0-3: drops color bits to reduce file size (0 = lossless, 3 = aggressive).
  • --profile: Enables Discrete Cosine Transform (Tag 4) spatial compression. Provides the engine's highest compression ratio, significantly reducing the final .ascf payload size at the cost of higher encode times and lossy quantization. Automatically enforces --pixel.
  • --qf 1-100: Quality factor for the DCT profile (default: 70). Higher means better quality and larger file.
  • --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 & watch without installing anything

static_player/studio/ is a standalone page (index.html + encoder.js, using pako from a CDN) that compiles a video to .ascf entirely client-side — drop a video in, get a .ascf out, nothing ever leaves your browser, no Python required.

The page includes a built-in preview with a custom seekbar, allowing you to instantly scrub through your compiled clip. Because it shares the main codec.js, this studio player natively decodes all advanced compression tags (including Tag 4 DCT).

(Note: While it can play all tags, the client-side encoder itself is conservative and only emits RAW/ZLIB/DELTA for speed. For production output or maximum compression with RLE/DCT, use the Python compiler).

Playing a compiled file (the full player)

For the full experience — audio sync and ASCII/pixel mode support — use the main player at static_player/index.html.

Method A: Drag & Drop (No server needed!) Simply open static_player/index.html in your browser and drag your .ascf file (along with an optional .mp3 file for audio) directly onto the page. Playback starts instantly, completely bypassing browser CORS restrictions with zero backend required.

Method B: Local File Server If you prefer to load files via URL instead of drag-and-drop, serve the folder through a plain static server:

python -m http.server

Infinite Playback & Low RAM: The static player uses an aggressive rolling buffer (~3 seconds). Rendered frames are instantly garbage-collected, allowing continuous playback with no duration limit and a near-zero memory footprint.

Installation

0. Requirements

  • Python 3.9+
  • FFmpeg & FFprobe (see below — required for audio and thumbnails)
  • A modern browser for the web player (any browser with Canvas + WebSocket support)

1. Clone the repository

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

2. Install dependencies

ASCILINE's dependencies are defined in pyproject.toml. Install the base package with:

pip install .

Or, if you prefer the plain requirements file:

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 ".[ytdlp]"

This installs the ytdlp extra defined in pyproject.toml, pulling in yt-dlp for URL streaming. 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 the ytdlp extra):

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

Running with Docker

ASCILINE ships with a Dockerfile and docker-compose.yml for running the live streaming server without installing Python, FFmpeg, or any dependency on the host. The image is based on python:3.11-slim, installs FFmpeg/FFprobe and CA certificates, and swaps opencv-python for opencv-python-headless at build time (the container has no display, so this is the lighter drop-in — see Requirements). Note that webcam and terminal-standalone (ascii_video_player2.py) modes aren't practical inside a container — Docker is intended for the web streaming server (stream_server.py), which by default runs in folder mode, watching videos/.

Docker Compose (recommended)

docker compose up --build

This builds the image and starts stream_server.py --folder videos --host 0.0.0.0 --port 8000, exposing the web UI on http://localhost:8000. docker-compose.yml mounts ./videos on the host to /app/videos in the container — drop your .mp4/.mkv/etc. files into your local videos/ folder and they'll show up automatically, no rebuild needed. stdin_open/tty are enabled so interactive terminal prompts still work; the high-FPS y/n confirmation prompt is skipped automatically when running in Docker.

Plain Docker

docker build -t asciline .
docker run -p 8000:8000 -v $(pwd)/videos:/app/videos asciline

Pass any stream_server.py CLI flags after the image name to override the default --folder videos --host 0.0.0.0 --port 8000, e.g.:

docker run -p 8000:8000 -v $(pwd)/videos:/app/videos asciline --folder videos --cols 220 --loop

YouTube/URL playback in Docker: the image installs from requirements.txt only, so yt-dlp is not included by default. To enable URL playback inside the container, add RUN pip install ".[ytdlp]" (or pip install yt-dlp) to the Dockerfile before building, or install it in a custom layer on top of the base image.

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 6 --cols 240 --rows 100

# For pixel mode, simply pass the flag (no mode number required):
python stream_server.py video.mp4 --pixel --cols 560
  • 1: Black & White (DOM mode)
  • 2: 64 colors
  • 3: 512 colors
  • 4: 32K colors
  • 5: 262K colors
  • 6: 16M colors (ultra)

(Note: The --pixel flag operates independently and automatically applies the highest color fidelity, rendering --mode unnecessary when used).

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 6 --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",   "pixel": true, "vol": 3, "cols": 520 },
    { "video": "https://youtu.be/VIDEO_ID", "mode": 4, "vol": 2, "cols": 240 }
]

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

Troubleshooting

Quick fixes for the most common issues. Full protocol/technical details will live in a separate technical guide (coming soon).

  • Audio and video fall out of sync — you've pushed --cols higher than your machine can encode/send in time. Lower --cols until playback keeps up. See Resolution & auto-scaling.
  • FileNotFoundError for ffmpeg/ffprobe (usually Windows) — FFmpeg isn't on your PATH. Either install it via winget install ffmpeg, or manually drop ffmpeg.exe/ffprobe.exe next to stream_server.py. See FFmpeg & FFprobe.
  • Terminal playback layout breaks / garbles mid-video — don't resize the terminal window while ascii_video_player2.py is running; dynamic text wrapping corrupts the fixed-grid layout.
  • YouTube/URL playback fails or hangs — make sure the ytdlp extra is installed (pip install ".[ytdlp]"); it's optional and isn't required for local file playback.
  • First-run YouTube video is slow to start — the server downloads and normalizes it to H.264/AAC first; every replay afterward is served instantly from the videos/ cache.
  • Disk filling up from cached downloads — set a lower --cache-limit (in MB) to cap the LRU video cache.
  • Studio (browser compiler) output is bigger than expected, or compiling takes a long time — the browser-side encoder only emits RAW/ZLIB/DELTA (no RLE_FULL) and is meant for short clips. For long or size-sensitive videos, use the Python compiler (compiler.py) instead. See Browser Studio and Playing a compiled file for the two preview options.

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 ASCILINE Discord Server to share ideas, or contribute to ASCILINE.

Contact

asciline.engine@gmail.com

View on GitHub

Recent activity

commits and pull requests

Commits per week

last 52 weeks
280Week of 2025-08-10: 0 commitsWeek of 2025-08-17: 0 commitsWeek of 2025-08-24: 0 commitsWeek of 2025-08-31: 0 commitsWeek of 2025-09-07: 0 commitsWeek of 2025-09-14: 0 commitsWeek of 2025-09-21: 0 commitsWeek of 2025-09-28: 0 commitsWeek of 2025-10-05: 0 commitsWeek of 2025-10-12: 0 commitsWeek of 2025-10-19: 0 commitsWeek of 2025-10-26: 0 commitsWeek of 2025-11-02: 0 commitsWeek of 2025-11-09: 0 commitsWeek of 2025-11-16: 0 commitsWeek of 2025-11-23: 0 commitsWeek of 2025-11-30: 0 commitsWeek of 2025-12-07: 0 commitsWeek of 2025-12-14: 0 commitsWeek of 2025-12-21: 0 commitsWeek of 2025-12-28: 0 commitsWeek of 2026-01-04: 0 commitsWeek of 2026-01-11: 0 commitsWeek of 2026-01-18: 0 commitsWeek of 2026-01-25: 0 commitsWeek of 2026-02-01: 0 commitsWeek of 2026-02-08: 0 commitsWeek of 2026-02-15: 0 commitsWeek of 2026-02-22: 0 commitsWeek of 2026-03-01: 0 commitsWeek of 2026-03-08: 0 commitsWeek of 2026-03-15: 0 commitsWeek of 2026-03-22: 0 commitsWeek of 2026-03-29: 0 commitsWeek of 2026-04-05: 0 commitsWeek of 2026-04-12: 0 commitsWeek of 2026-04-19: 0 commitsWeek of 2026-04-26: 1 commitsWeek of 2026-05-03: 4 commitsWeek of 2026-05-10: 0 commitsWeek of 2026-05-17: 0 commitsWeek of 2026-05-24: 0 commitsWeek of 2026-05-31: 13 commitsWeek of 2026-06-07: 26 commitsWeek of 2026-06-14: 28 commitsWeek of 2026-06-21: 20 commitsWeek of 2026-06-28: 0 commitsWeek of 2026-07-05: 14 commitsWeek of 2026-07-12: 14 commitsWeek of 2026-07-19: 19 commitsWeek of 2026-07-26: 12 commitsWeek of 2026-08-02: 2 commitsAug 10, 2025Aug 2, 2026
153 commits in the last 52 weeks.

When work happens

weekday and hour
SunMonTueWedThuFriSat036912151821Sun 0:00 — 0 commitsSun 1:00 — 0 commitsSun 2:00 — 0 commitsSun 3:00 — 0 commitsSun 4:00 — 0 commitsSun 5:00 — 0 commitsSun 6:00 — 0 commitsSun 7:00 — 0 commitsSun 8:00 — 0 commitsSun 9:00 — 0 commitsSun 10:00 — 1 commitsSun 11:00 — 7 commitsSun 12:00 — 1 commitsSun 13:00 — 2 commitsSun 14:00 — 5 commitsSun 15:00 — 1 commitsSun 16:00 — 0 commitsSun 17:00 — 0 commitsSun 18:00 — 0 commitsSun 19:00 — 3 commitsSun 20:00 — 0 commitsSun 21:00 — 2 commitsSun 22:00 — 0 commitsSun 23:00 — 2 commitsMon 0:00 — 0 commitsMon 1:00 — 0 commitsMon 2:00 — 0 commitsMon 3:00 — 0 commitsMon 4:00 — 0 commitsMon 5:00 — 0 commitsMon 6:00 — 0 commitsMon 7:00 — 0 commitsMon 8:00 — 0 commitsMon 9:00 — 0 commitsMon 10:00 — 0 commitsMon 11:00 — 0 commitsMon 12:00 — 2 commitsMon 13:00 — 0 commitsMon 14:00 — 1 commitsMon 15:00 — 8 commitsMon 16:00 — 0 commitsMon 17:00 — 0 commitsMon 18:00 — 1 commitsMon 19:00 — 1 commitsMon 20:00 — 0 commitsMon 21:00 — 0 commitsMon 22:00 — 0 commitsMon 23:00 — 3 commitsTue 0:00 — 4 commitsTue 1:00 — 1 commitsTue 2:00 — 0 commitsTue 3:00 — 0 commitsTue 4:00 — 0 commitsTue 5:00 — 0 commitsTue 6:00 — 0 commitsTue 7:00 — 0 commitsTue 8:00 — 0 commitsTue 9:00 — 0 commitsTue 10:00 — 1 commitsTue 11:00 — 0 commitsTue 12:00 — 0 commitsTue 13:00 — 4 commitsTue 14:00 — 2 commitsTue 15:00 — 0 commitsTue 16:00 — 0 commitsTue 17:00 — 1 commitsTue 18:00 — 0 commitsTue 19:00 — 0 commitsTue 20:00 — 1 commitsTue 21:00 — 2 commitsTue 22:00 — 3 commitsTue 23:00 — 1 commitsWed 0:00 — 0 commitsWed 1:00 — 0 commitsWed 2:00 — 0 commitsWed 3:00 — 0 commitsWed 4:00 — 0 commitsWed 5:00 — 0 commitsWed 6:00 — 0 commitsWed 7:00 — 0 commitsWed 8:00 — 0 commitsWed 9:00 — 0 commitsWed 10:00 — 0 commitsWed 11:00 — 1 commitsWed 12:00 — 0 commitsWed 13:00 — 0 commitsWed 14:00 — 6 commitsWed 15:00 — 5 commitsWed 16:00 — 3 commitsWed 17:00 — 6 commitsWed 18:00 — 0 commitsWed 19:00 — 1 commitsWed 20:00 — 2 commitsWed 21:00 — 0 commitsWed 22:00 — 0 commitsWed 23:00 — 0 commitsThu 0:00 — 0 commitsThu 1:00 — 0 commitsThu 2:00 — 0 commitsThu 3:00 — 0 commitsThu 4:00 — 0 commitsThu 5:00 — 0 commitsThu 6:00 — 0 commitsThu 7:00 — 0 commitsThu 8:00 — 0 commitsThu 9:00 — 0 commitsThu 10:00 — 0 commitsThu 11:00 — 2 commitsThu 12:00 — 0 commitsThu 13:00 — 1 commitsThu 14:00 — 6 commitsThu 15:00 — 0 commitsThu 16:00 — 1 commitsThu 17:00 — 6 commitsThu 18:00 — 0 commitsThu 19:00 — 1 commitsThu 20:00 — 0 commitsThu 21:00 — 0 commitsThu 22:00 — 1 commitsThu 23:00 — 0 commitsFri 0:00 — 2 commitsFri 1:00 — 0 commitsFri 2:00 — 0 commitsFri 3:00 — 0 commitsFri 4:00 — 0 commitsFri 5:00 — 0 commitsFri 6:00 — 0 commitsFri 7:00 — 0 commitsFri 8:00 — 0 commitsFri 9:00 — 0 commitsFri 10:00 — 0 commitsFri 11:00 — 3 commitsFri 12:00 — 1 commitsFri 13:00 — 1 commitsFri 14:00 — 1 commitsFri 15:00 — 0 commitsFri 16:00 — 4 commitsFri 17:00 — 1 commitsFri 18:00 — 0 commitsFri 19:00 — 3 commitsFri 20:00 — 3 commitsFri 21:00 — 1 commitsFri 22:00 — 0 commitsFri 23:00 — 9 commitsSat 0:00 — 1 commitsSat 1:00 — 5 commitsSat 2:00 — 4 commitsSat 3:00 — 0 commitsSat 4:00 — 0 commitsSat 5:00 — 0 commitsSat 6:00 — 0 commitsSat 7:00 — 0 commitsSat 8:00 — 0 commitsSat 9:00 — 0 commitsSat 10:00 — 0 commitsSat 11:00 — 0 commitsSat 12:00 — 2 commitsSat 13:00 — 4 commitsSat 14:00 — 1 commitsSat 15:00 — 0 commitsSat 16:00 — 0 commitsSat 17:00 — 0 commitsSat 18:00 — 0 commitsSat 19:00 — 2 commitsSat 20:00 — 0 commitsSat 21:00 — 0 commitsSat 22:00 — 1 commitsSat 23:00 — 2 commits
Commit volume by weekday and hour (UTC). Larger dots mean more commits.

Who is committing

last 52 weeks
Maintainer commits90 (52%)
Community commits82 (48%)

172 commits in total over the last year.

DateListRankStars gained
Jun 14, 2026daily#23+32