Video and HTTP(S) streams inside an Emacs buffer as pure characters: density ramp and true-color half blocks
app
  • Emacs Lisp 56.7%
  • Python 42%
  • Makefile 1.2%
  • Shell 0.1%
Find a file
Kinneyzhang 4a52358050
Some checks failed
Repository structure / structure (push) Has been cancelled
refactor(ascii-video): run the player as an ETAF application
The player hand-built its canvas: a buffer keymap installed through
emulation-mode-map-alists, a display table, a global
window-size-change hook with its own minibuffer-borrowing check, and a
teardown reachable only from q or a failed open. Killing the buffer left
the status timer running forever, the area outside the canvas showed the
user's background, and etaf-ascii-video-close outside a player killed
whatever buffer was current.

The player now opens through etaf-app-open. Its keys are root :keys of
the View with the labels help already showed; the canvas surface is the
player's dark background; the fit function refits the grid when the
window's text area settles at a new size, so a grid from size hints, a
preset or set-grid stays until then; and one close function stops the
status timer however the buffer goes away. etaf-ascii-video-close
signals user-error outside a player. The help palette is built with
etaf-ui-theme-tokens (the eleven tokens it provided are unchanged; the
rest of the modal chrome now follows the same dark palette). The mode
line stays visible as before. Requires ETAF 0.3.0 and ETAF UI 0.2.0;
version 0.1.1.

Validation: make -C etaf-ascii-video check (12/12, API 17 verified); the
new canvas-and-kill test fails on the previous tree at each of its three
claims (close kills a non-player buffer, no canvas surface, a leaked
timer after kill); real GUI with ffmpeg: fit 199x48 in 205x50, SPC/n/f/
b/+/-/1-4/F/c drive the player, split refits 93x22 and unsplit 199x48,
a preset survives settles, ? lists all 17 labelled keys in a dark modal,
q and kill-buffer return the timer count to its baseline.
2026-10-20 08:15:21 +08:00
.githooks feat: play video as ASCII pixels in an ETAF buffer 2026-10-03 22:34:03 +08:00
.github/workflows chore(scripts): sync shared workspace tooling 2026-10-19 12:49:25 +08:00
docs refactor(ascii-video): run the player as an ETAF application 2026-10-20 08:15:21 +08:00
examples docs(ascii-video): add the playback screenshot 2026-10-06 12:00:30 +08:00
lisp refactor(ascii-video): run the player as an ETAF application 2026-10-20 08:15:21 +08:00
scripts chore(scripts): sync shared workspace tooling 2026-10-19 12:49:25 +08:00
tests refactor(ascii-video): run the player as an ETAF application 2026-10-20 08:15:21 +08:00
.editorconfig feat: play video as ASCII pixels in an ETAF buffer 2026-10-03 22:34:03 +08:00
.gitignore feat: play video as ASCII pixels in an ETAF buffer 2026-10-03 22:34:03 +08:00
CHANGELOG.md refactor(ascii-video): run the player as an ETAF application 2026-10-20 08:15:21 +08:00
etaf-ascii-video.el refactor(ascii-video): run the player as an ETAF application 2026-10-20 08:15:21 +08:00
LICENSE chore: add the GPL-3.0 license 2026-10-13 00:49:00 +08:00
Makefile refactor: play characters through the native etaf-video Component 2026-10-04 09:51:11 +08:00
README.md feat(ascii-video): replace static help with the shared modal 2026-10-16 00:26:50 +08:00

ETAF ASCII Video

ETAF ASCII Video shows a local video or HTTP(S) stream inside an Emacs buffer as pure characters, in the two character render modes of ETAF's native etaf-video Component: a density ramp (one character per pixel) and true-color half blocks (▀, two full-color pixels per cell). The native Component decodes with ffmpeg and publishes every frame through ETAF's retained content boundary, so one displayed frame is one incremental publication of a fixed canvas Host, not a full re-render. This application owns the buffer, the fitted canvas size, the render mode, the status line and the keys. For the sharp, full-window image player with sound, use the companion etaf-video-player.

A local video playing as true-color half blocks:

A local video playing as true-color half blocks

Capabilities

  • Playback of local video files through ffmpeg; the built-in demo generates a test pattern with ffmpeg's lavfi input, so no file is needed.
  • HTTP(S) video URLs play as streams: ffmpeg decodes them directly, nothing is downloaded first, and a dropped connection is retried for five seconds.
  • Two character render modes. The default monochrome mode maps each pixel to one density-ramp character. True-color half-block mode (c) renders two full color pixels per character cell with ▀ (top pixel as foreground, bottom as background), keeping the video's colors at double the vertical resolution.
  • A canvas that fits the window (and follows resizes), four grid presets, and explicit etaf-ascii-video-set-grid for scripts.
  • Play, pause, single frame stepping, seeking, and 0.5x to 8x rates.
  • Latest-frame-wins display: when decoding outruns display, older frames are dropped instead of replayed as a backlog.
  • etaf-ascii-video-state exposes the live player as plain data for scripts and tests.

Prerequisites

Requirement Why Check
Emacs 29.1 or newer the ETAF runtime emacs --version
etaf, etaf-ui, ebox, ecss the framework; each root entry must be on load-path every checkout is readable
ffmpeg decoding and downsampling ffmpeg -version
ffprobe duration, frame rate and video size ffprobe -version

ffprobe is optional: without it playback uses the source defaults for dimensions and rate and the status line shows no total duration. The decoder programs, the density ramp and the display rate are the etaf-video-* variables documented in ETAF's manual.

Installation

(add-to-list 'load-path "/path/to/ecss")
(add-to-list 'load-path "/path/to/ebox")
(add-to-list 'load-path "/path/to/etaf")
(add-to-list 'load-path "/path/to/etaf-ui")
(add-to-list 'load-path "/path/to/etaf-ascii-video")
(require 'etaf-ascii-video)

(global-set-key (kbd "C-c a") #'etaf-ascii-video-open-file)

Minimal example

(etaf-ascii-video-demo)                 ; generated test pattern, no file
(etaf-ascii-video-open-file "clip.mp4") ; a local video
(etaf-ascii-video-open-url "https://example.com/clip.mp4") ; a network stream
(etaf-ascii-video-open '(:file "clip.mp4" :columns 120 :rows 34 :speed 2))
(etaf-ascii-video-open '(:file "clip.mp4" :mode 'color))  ; half-block color

Keys

Key Action
SPC play or pause
n pause and present one more frame
f / b seek five seconds forward or backward
+ / - raise or lower the playback rate
1 .. 4 grid presets (64, 96, 128, 176 columns, rows follow the aspect)
c toggle true-color half-block rendering
F fit the canvas to the window
o / u / d open a file / open a URL / replay the built-in demo
? / C-c h searchable help inside the player
q close the player

Help is read-only and uses the player's dark palette. / searches its effective bindings; Escape leaves search editing before closing the modal. Clicking the dimmed background also closes it without changing playback.

Checks

make check    # structure + byte compilation (warnings as errors) + acceptance + API boundary
make test     # the public acceptance scenarios only

The acceptance suite drives the repository's fake ffmpeg and ffprobe fixtures, so it needs no real decoder, no network and no frame; one scenario runs the real ffmpeg when one is installed.

Documentation

  • The manual: playback, canvas fitting, scripting, variables and troubleshooting.
  • Architecture: module responsibilities, data flow, invariants and the ownership boundary with ETAF.
  • The root entry Commentary: the authoritative list of supported functions and variables.