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: A high-performance ASCII video rendering engine featuring real-time WebSocket streaming and zero-dependency static playback.

Stars
2.8K
+4 today
Forks
315
Watchers
16
Open issues
2
Open PRs
1
Contributors
~15
Commits
207
Branches
2

JavaScriptOtherCreated May 1, 2026Last push 1d agoLatest release v0.1.5+12 stars this week+49 this month

Quick answers

What is ASCILINE?
A high-performance ASCII video rendering engine featuring real-time WebSocket streaming and zero-dependency static playback.
What does ASCILINE do?
ASCILINE is an advanced video processing engine that converts standard video formats into real-time ASCII art animations. Engineered for extreme high performance, it achieves low-latency 30 FPS playback directly on standard HTML5 Canvas elements without requiring heavy GPU rendering. The system includes an isolated Python compiler for generating serverless static payloads and utilizes custom WebSocket binary streaming to deliver live ASCII frames efficiently. It bridges backend OpenCV video processing with highly optimized frontend text rendering, providing developers with a robust toolkit for building complex, retro-styled visual experiences on the web.
Who is ASCILINE for?
Creative developers, frontend engineers, and media artists looking to integrate high-performance text-based visuals into web applications efficiently.
How do I get started with ASCILINE?
python stream_server.py video.mp4 --host 0.0.0.0
How popular is ASCILINE on GitHub?
YusufB5/ASCILINE has 2,759 stars and 315 forks on GitHub, and gained 12 stars in the last 7 days.
What license does ASCILINE use?
YusufB5/ASCILINE is released under the Other license.

Star history

since Jul 28, 2026
01K2KJul 2026Aug 2026Sep 2026Oct 2026
2.8K stars as of Oct 4, 2026. Measured daily since Jul 28, 2026; GitHub no longer exposes earlier star timestamps.

Contribution activity

commits per day, last 52 weeks
OctNovDecJanFebMarAprMayJunJulAugSepMonWedFri2025-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: 1 commit2026-08-09: 0 commits2026-08-10: 0 commits2026-08-11: 0 commits2026-08-12: 0 commits2026-08-13: 0 commits2026-08-14: 0 commits2026-08-15: 0 commits2026-08-16: 0 commits2026-08-17: 0 commits2026-08-18: 0 commits2026-08-19: 1 commit2026-08-20: 4 commits2026-08-21: 0 commits2026-08-22: 0 commits2026-08-23: 1 commit2026-08-24: 0 commits2026-08-25: 0 commits2026-08-26: 0 commits2026-08-27: 0 commits2026-08-28: 0 commits2026-08-29: 1 commit2026-08-30: 0 commits2026-08-31: 0 commits2026-09-01: 0 commits2026-09-02: 3 commits2026-09-03: 3 commits2026-09-04: 0 commits2026-09-05: 2 commits2026-09-06: 0 commits2026-09-07: 0 commits2026-09-08: 0 commits2026-09-09: 7 commits2026-09-10: 0 commits2026-09-11: 0 commits2026-09-12: 0 commits2026-09-13: 0 commits2026-09-14: 0 commits2026-09-15: 0 commits2026-09-16: 1 commit2026-09-17: 0 commits2026-09-18: 0 commits2026-09-19: 0 commits2026-09-20: 0 commits2026-09-21: 0 commits2026-09-22: 0 commits2026-09-23: 3 commits2026-09-24: 3 commits2026-09-25: 0 commits2026-09-26: 1 commit2026-09-27: 0 commits2026-09-28: 0 commits2026-09-29: 0 commits2026-09-30: 0 commits2026-10-01: 0 commits2026-10-02: 0 commits2026-10-03: 0 commits
184 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 an advanced video processing engine that converts standard video formats into real-time ASCII art animations. Engineered for extreme high performance, it achieves low-latency 30 FPS playback directly on standard HTML5 Canvas elements without requiring heavy GPU rendering. The system includes an isolated Python compiler for generating serverless static payloads and utilizes custom WebSocket binary streaming to deliver live ASCII frames efficiently. It bridges backend OpenCV video processing with highly optimized frontend text rendering, providing developers with a robust toolkit for building complex, retro-styled visual experiences on the web.

Creative developers, frontend engineers, and media artists looking to integrate high-performance text-based visuals into web applications efficiently.

  • Real-Time Rendering: Converts video frames to ASCII characters dynamically with extremely low latency using NumPy mapping.
  • WebSocket Streaming: Efficiently transmits binary ASCII data from the backend server to connected web clients utilizing a custom lightweight protocol.
  • Adaptive Frame Codec: Employs delta encoding, run-length encoding, and zlib compression to minimize bandwidth for static or low-motion frames.
  • Canvas Playback: Renders the text-based frames directly on HTML5 Canvas to ensure smooth, synchronized 30 FPS playback across devices.
  • Serverless Compilation: Includes an isolated compiler that packages videos into static .ascf files for true zero-dependency web deployment.

Where teams use it

Creative Web Design

Embed unique, highly aesthetic ASCII video backgrounds or interactive elements into modern portfolios and landing pages.

Low-Bandwidth Streaming

Transmit video-like visual experiences over extremely constrained networks where standard H.264 video codecs are too heavy.

Terminal-Style Web Apps

Enhance hacker-themed or retro-styled web applications with authentic, moving text-based visuals directly in the DOM.

Zero-GPU Media Playback

Display motion graphics on constrained devices or smart appliances that lack hardware video decoders by rendering text instead.

Getting started: python stream_server.py video.mp4 --host 0.0.0.0

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.

Python JavaScript FastAPI OpenCV NumPy HTML5 Canvas License: AGPL v3 License: MIT

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.py → experiments/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.

u can try it from - https://yusufb5.github.io/ASCILINE/static_player/studio/

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.

JavaScript SDK (asciline-player)

The official JavaScript SDK ships as the asciline-player npm package (MIT license). It provides two complementary APIs: a full-featured AsciiPlayer class for programmatic control, and a zero-config <ascf-player> HTML element for drop-in embedding — both backed by the same high-performance render engine.

Install

npm install asciline-player

Or use the CDN directly (no install):

<script type="module" src="https://cdn.jsdelivr.net/npm/asciline-player/src/asciline-player.js"></script>

1. JS API — Live WebSocket Stream

Connect to a running stream_server.py backend and render in real time:

<div id="player-box" style="position:relative; width:100%; aspect-ratio:16/9; background:#000;">
  <canvas id="ascii-canvas"></canvas>
</div>

<script type="module">
  import { AsciiPlayer } from 'asciline-player';

  const player = new AsciiPlayer('#ascii-canvas', {
    url: 'ws://localhost:8000/ws', // live WebSocket stream
    container: '#player-box',
    audio: true,           // synchronized audio
    selectionLayer: true,  // copyable text overlay
    playOverlay: true,     // auto ▶ button
  });
</script>

2. JS API — Static ASCF File (no backend required)

Play a pre-compiled .ascf file directly from any static host (GitHub Pages, Vercel, Netlify…):

<div id="player-box" style="position:relative; width:100%; aspect-ratio:16/9; background:#000;">
  <canvas id="ascii-canvas"></canvas>
</div>

<script type="module">
  import { AsciiPlayer } from 'asciline-player';

  const player = new AsciiPlayer('#ascii-canvas', {
    src:      'demo.ascf',  // .ascf file URL — no WebSocket server needed
    audioSrc: 'demo.mp3',   // optional paired audio
    container: '#player-box',
    loop:      true,
    playOverlay: true,
  });

  // Or imperatively:
  // player.play('demo.ascf', 'demo.mp3');
</script>

Both modes share identical playback controls: play(), pause(), resume(), togglePlay(), mute(), unmute(), setVolume(), setFilters(), destroy() (filters and seek: WS only).


3. HTML Tag — <ascf-player>

Embed a clip with a single HTML tag. No JavaScript needed:

<!-- Load the element once (CDN or local) -->
<script type="module" src="https://cdn.jsdelivr.net/npm/asciline-player/src/ascf-element.js"></script>

<!-- Then use it anywhere on the page -->
<ascf-player
  src="demo.ascf"
  audio="demo.mp3"
  loop
  style="width:100%; aspect-ratio:16/9;">
</ascf-player>

For live streaming, use the ws attribute instead of src:

<ascf-player ws="ws://localhost:8000/ws" style="width:100%; aspect-ratio:16/9;"></ascf-player>

Supported attributes:

Attribute Description
src URL of the .ascf file
audio URL of the paired .mp3 audio file (optional)
ws WebSocket URL for live streaming (alternative to src)
autoplay Start playback immediately (boolean)
loop Restart on finish — only with src (boolean)
muted Start with audio muted (boolean)

DOM events are dispatched with the ascf- prefix (e.g. ascf-playing, ascf-ended, ascf-timeupdate).


Constructor Options Reference

Option Default Description
url null WebSocket URL for live streaming
src null Static .ascf file URL (no backend needed)
audioSrc null Static audio URL paired with src
loop false Loop ASCF playback on completion
audio true Enable audio (boolean / selector / HTMLAudioElement)
container parent Sizing container element or CSS selector
autoplay false Begin playback immediately
muted false Start playback with audio muted
playOverlay true Auto-create the ▶ overlay button
muteButton true Auto-create the mute/unmute toggle
clickToPlayPause true Click canvas to toggle play/pause
keyboardShortcuts true Spacebar toggles play/pause
selectionLayer false Enable copyable text overlay
bufferSize 4 Jitter buffer depth (frames)
filters {} Initial filter values (contrast, gamma, brightness…)

Programmatic API

player.play('clip.ascf', 'clip.mp3'); // static file
player.play();                         // live WS (uses options.url)
player.pause();
player.resume();
player.togglePlay();
player.mute();
player.unmute();
player.setVolume(0.8);        // 0–1
player.setFilters({ contrast: 1.2, invert: true });
player.setRenderMode(4);      // switch color depth live (WS only)
player.seek(30);              // jump to 30s (WS only)
player.getMasterClock();      // current position in seconds
player.getState();            // 'IDLE' | 'CONNECTING' | 'PLAYING' | 'PAUSED' | 'ENDED' | 'ERROR'
player.destroy();             // clean up all resources

// Event listener API
player.on('init',        ({ fps, cols, rows, duration }) => { ... });
player.on('timeupdate',  (seconds) => { ... });
player.on('fps',         ({ fps, targetFps, buffered }) => { ... });
player.on('statechange', (state) => { ... });
player.on('ended',       () => { ... });
player.on('error',       (err) => { ... });
player.off('timeupdate', handler); // remove listener

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.

Health checks

GET /health returns HTTP 200 with {"status":"ok"} when the HTTP server is responsive. It does not open a WebSocket, access videos, or decode frames.

curl -i http://localhost:8000/health

The Docker image checks this endpoint on container port 8000 every 30 seconds using Python's standard library. Check the container's health status with docker compose ps; a running server should report healthy after a successful check. If you change the server's internal port, update the health check URL too.

Server information

GET /info returns a JSON snapshot of server metadata:

{"queue_size":1,"current_index":0,"cols":200,"rows":0,"loop":false,"debug":false}

current_index is zero-based. cols and rows describe the server's configured grid, with rows: 0 meaning automatic sizing; individual sessions can use different dimensions. If the app is imported without CLI initialization, numeric fields default to 0 and flags to false. Pixel mode is scoped to each WebSocket session and is not included. The endpoint reads existing server state without opening videos or decoding frames.

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 200–240 (recommended starting point for the best balance of detail and 30 FPS performance; can be increased if your hardware allows).
  • Pixel mode: --cols 600–900 (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

Star History Chart

Support ❤️

If you find this project useful, you can support its development: GitHub Sponsors

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

License

ASCILINE is distributed under a split-licensing model:

Community

Join the ASCILINE Discord Server to share ideas, or contribute to ASCILINE.

Contact

[email protected]

View on GitHub

Recent activity

commits and pull requests

Releases and announcements

6 total
  1. ### Patch Fixes in v0.1.5 * **Cross-Origin Audio Support:** Added `crossOrigin = 'anonymous'` to the audio element and `Access-Control-Allow-Origin: *` to the `/audio` endpoint, enabling flawless live audio streaming when embedding the player across different ports or domains. * **Static ASCF Unmute Sync:** Fixed an issue where unmuting a static `.ascf` stream mistakenly targeted the WebSocket server audio URL instead of maintaining local audio synchronization. * **Explicit `muted` Option:** Added first-class `muted: false` option to constructor defaults * **credentials conflict:** Also fixed CORS credentials conflict in stream_server.py ### New Features in v0.1.4 / v0.1.5 * **Zero-Backend Static ASCF Playback:** The SDK is no longer strictly bound to a WebSocket server (`ws://`). You can now stream and play pre-compiled `.ascf` files directly from any static host (GitHub Pages, Vercel, S3, Netlify, or local HTTP). * **`<ascf-player>` Web Component:** A zero-config HTML custom element for drop-in embedding. Embed ASCII video clips into any HTML page with a single tag—no JavaScript required. * **Universal Source Routing:** `AsciiPlayer` automatically detects the source

  2. ### New Features * **Zero-Backend Static ASCF Playback:** The SDK is no longer strictly bound to a WebSocket server (`ws://`). You can now stream and play pre-compiled `.ascf` files directly from any static host (GitHub Pages, Vercel, S3, Netlify, or local HTTP). * **`<ascf-player>` Web Component:** A zero-config HTML custom element for drop-in embedding. Embed ASCII video clips into any HTML page with a single tag—no JavaScript required. * **Universal Source Routing:** `AsciiPlayer` automatically detects the source type (`.ascf` vs `ws://` / `wss://`) and routes playback to the appropriate transport engine. * **Component Subpath Export:** Import the Web Component separately via `import 'asciline-player/element'`. ### Bug Fixes & UX Polishing * **ASCF Pause/Resume State Preservation:** Fixed an issue where resuming static `.ascf` playback would freeze the video while audio continued playing due to premature buffer flushing. * **Text Selection Guard on Controls:** Added `user-select: none` across play/pause overlays and the Web Component container to prevent accidental text highlighting during click interactions. * **Clean Abortable Streaming:** Added `AbortController` i

  3. ### New Features & UX Improvements - **Minimalist Vector Sound Button:** Replaced system emoji buttons with crisp, modern frosted-glass SVG icons (Mute / Unmute). - **Click-to-Play / Pause:** Clicking anywhere on the video player or pressing `Space` toggles playback with a sleek center pause indicator overlay. - **Smart Text Selection Guard:** Pausing is intelligently bypassed while highlighting ASCII characters in the selection layer. - **Auto Codec Negotiation:** Automatically ensures `?codec=adaptive` is used for optimal streaming bandwidth even if omitted in client connection URLs. - **Dual Package Export:** Supports both `import AsciiPlayer from 'asciline-player'` and `import { AsciiPlayer } from 'asciline-player'`. ### Bug Fixes & Performance - **Vertical Video Pixel Optimization:** Fixed high-bandwidth saturation (up to 40 MB/s) on portrait videos in pixel mode by standardizing the adaptive pixel budget, preventing TCP buffer stutter. - **Audio/Video Sync State Isolation:** Fully separated audio mute state from video playback state to prevent audio desync during paused interactions. - **Clean Socket Teardown:** Suppressed harmless underlying WebSocket library `

  4. This patch release fixes a critical frame corruption bug in the SDK's inlined codec, restores standalone showcase stability, and introduces automatic aspect ratio optimization for vertical videos (YouTube Shorts, Instagram Reels, TikTok). (The standalone showcase page (index.html) will migrate to the SDK once it reaches full stability.) ### What's New in v0.1.2 - **Resolved "The Void" (Delta Frame Corruption):** Replaced the faulty inlined delta decoder in `asciline-player` with the verified, bit-accurate `codec.js` engine. Fixed a 4-byte index misalignment that previously caused black rectangular voids and frozen scene fragments during playback. - **Smart Vertical Video (Shorts/Reels) Optimization:** `stream_server.py` now automatically detects vertical video aspect ratios (`vid_h > vid_w`) and dynamically scales columns to 96 (down from 200). This reduces per-frame character count from 35,600 to ~8,160, eliminating canvas text overcrowding and ensuring a solid 30 FPS playback with zero startup lag. - **Showcase Independence:** `app.js` and `codec.js` in the core repository remain decoupled and standalone as the production-grade golden benchmark. - **Synced Unmuting:

  5. # ASCILINE v0.1.1 — Patch Release This patch release improves cross-origin audio streaming support across modern web bundlers (Vite, React, Next.js), adds a dedicated unmuting helper to the JavaScript SDK, and updates YouTube extraction dependencies. ### What's New in v0.1.1 * 🔊 **Automatic Cross-Origin Audio Resolution:** The `AsciiPlayer` SDK now automatically derives the absolute audio stream URL from the provided WebSocket URL (`ws://` / `wss://` ➡️ `http://` / `https://`), preventing 404 / broken audio source errors when the web frontend runs on a different port or domain than the Python streaming server. * **Browser Autoplay Helper (`player.unmute()`):** Added `player.unmute()` to easily handle browser autoplay policies with a single user gesture click. * **Server CORS Support:** Added `CORSMiddleware` to `stream_server.py` so external frontend applications can seamlessly stream audio from `/audio`. * Bumped minimum `yt-dlp` version to `>=2026.08.19` to resolve recent YouTube `HTTP 403: Forbidden` stream throttling. ### Quickstart ```bash npm install asciline-player ``` Minimal (2 Lines of Code) ```bash <canvas id="ascii-canvas"></canvas>

Commits per week

last 52 weeks
280Week 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: 3 commitsWeek of 2026-08-09: 0 commitsWeek of 2026-08-16: 5 commitsWeek of 2026-08-23: 2 commitsWeek of 2026-08-30: 8 commitsWeek of 2026-09-06: 7 commitsWeek of 2026-09-13: 1 commitsWeek of 2026-09-20: 7 commitsWeek of 2026-09-27: 0 commitsOct 5, 2025Sep 27, 2026
184 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 — 1 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 — 1 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 — 1 commitsWed 13:00 — 2 commitsWed 14:00 — 9 commitsWed 15:00 — 5 commitsWed 16:00 — 3 commitsWed 17:00 — 6 commitsWed 18:00 — 0 commitsWed 19:00 — 4 commitsWed 20:00 — 4 commitsWed 21:00 — 1 commitsWed 22:00 — 2 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 — 5 commitsThu 12:00 — 0 commitsThu 13:00 — 4 commitsThu 14:00 — 8 commitsThu 15:00 — 0 commitsThu 16:00 — 1 commitsThu 17:00 — 6 commitsThu 18:00 — 0 commitsThu 19:00 — 1 commitsThu 20:00 — 1 commitsThu 21:00 — 0 commitsThu 22:00 — 2 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 — 2 commitsSat 1:00 — 6 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 — 3 commitsSat 20:00 — 2 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 commits103 (50%)
Community commits104 (50%)

207 commits in total over the last year.

DateListRankStars gained
Jun 14, 2026daily#23+32
  • affaan-m/ECC

    The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.

    272.8K stars · JavaScript

  • react/react

    The library for web and native user interfaces.

    250.9K stars · JavaScript

  • DietrichGebert/ponytail

    Makes your AI agent think like the laziest senior dev in the room. The best code is the code you never wrote.

    154.6K stars · JavaScript

  • vercel/next.js

    The React Framework

    143.2K stars · JavaScript

  • addyosmani/agent-skills

    Production-grade engineering skills for AI coding agents.

    101.1K stars · JavaScript

  • Leonxlnx/taste-skill

    Taste-Skill - gives your AI good taste. stops the AI from generating boring, generic slop

    92.6K stars · JavaScript