macOS · Windows · Linux · MIT

Protoke

proto + karaoke. A karaoke and narration renderer with a protogen visor face that mouths the words — drawn as vector outlines by the same subtitle pass that draws the lyrics, so there is no image sequence and no video layer to composite.

14 dials shape the face 11 expressions 4 transcription engines, one free everywhere 173 checks passing Local-first. Nothing leaves the machine

Twenty-eight seconds

Everything moving here was written into a subtitle file

The voice is synthetic, from a locally trained Piper model. The background is one black PNG that never changes. Every eye, every mouth shape and every word of text is a single libass pass over that still — no frames were animated, and nothing was composited.

27.5 seconds, 1920×1080, 1.7 MB. The mouth follows the words from their spelling; the eyes blink on their own schedule, roughly once every seven seconds. There is no beat in this recording, so the head breathes rather than nods — the tool measured that and said so in its log rather than inventing a tempo.

601
subtitle events drawing the face across those 28 seconds
668kB
the face's .ass track — the whole animation, as text
0
image sequences, video layers or overlay renders

Why it works this way

A face is cheaper as geometry than as pictures

The obvious way to put an animated face on a video is to render frames and composite them. That means an image sequence, an alpha channel, a second encoder pass, and a pile of intermediate files that all have to agree about size and frame rate.

ASS subtitles can draw filled vector shapes. libass is already in the ffmpeg command drawing the lyrics. So the face is emitted as drawing commands into a second subtitle track and burned in by the pass that was going to run anyway. One ffmpeg invocation, no intermediates, and the "animation" is a text file you can open and read.

The cost is that everything has to be expressible as outlines and timings, which turned out to be a useful constraint rather than a limit.

It is optional in both directions. Switch the face off for a plain karaoke render, or drop the lyrics and the artwork and put the face alone on black over a voice track — which is what the clip above is.

The face

Eleven expressions, no sprite sheet

Early versions swapped between fixed shapes and looked exactly like that: rigid, held together with tape. The fix was to stop picking expressions and start blending dial values, the way a protogen firmware blends morph weights.

Six numbers describe an eye — openness, bow, slant, hook, roundness, weight. Six more describe the mouth. Two describe the whole face. Every named expression is just a point in that space, and anything between two of them is a valid face, so a blink is a continuous move rather than a cut.

64 frames · one loop

These are the renderer's own frames. The geometry was exported straight out of the Python that writes the subtitle track, so the page cannot drift from the videos — it has no drawing code of its own, only a replay loop. What you are watching is the idle state: breathing, because there is no beat.

◤‿◥visor
◤w◥grin
︿﹀hooked
▶_◀sharp
¬‿¬smug
^_^content
^w^happy
o_owide
-_-sleepy
^o^delighted
owocurious

Each of these is a set of dial values, not a drawing. sharp is the clearest case: anger reads as straight edges and stillness, so it raises the sharpness dial — which bends the curves toward their control polygons — and damps the idle motion, rather than swapping in an angry picture.

The app

A local page, not a cloud service

The interface is a token-protected page on 127.0.0.1. With pywebview installed it opens in the operating system's own webview — a couple of megabytes, rather than the couple of hundred an embedded browser costs — and in your default browser otherwise.

Protoke's main window: source and output panels on the left, and a live 1920x1080 preview frame on the right showing the visor face above five lines of lyrics with one word highlighted

The preview is a real layout at output resolution, with the title-safe area drawn as a guide. Changing a colour, a typeface or the line count redraws it immediately; nothing here needs a render to check.

The Face configurator's Shape tab: a live preview of the face above fourteen labelled sliders grouped into eyes, mouth and whole face

All fourteen dials, with the preview above them rendered by the same code that renders the video — so the shape on screen is the shape you will get, not an approximation drawn a second time in JavaScript. Saved faces become presets you can pick per track.

The Settings sheet showing the transcription engine set to Parakeet ONNX, described as free and running on Windows, Linux and macOS, and vocal separation switched off

Transcription and separation live behind a probe: the app asks each engine whether it can actually run here and reports what it finds, rather than assuming. Vocal separation stays off by default because it pulls PyTorch — about a gigabyte — for an accuracy gain most tracks do not need.

Get it

Python, ffmpeg, and nothing else that is mandatory

The rendering, alignment, face and subtitle code imports only the standard library. Every dependency belongs to an optional transcription or separation engine, so with a timed JSON file beside your track the tool runs with nothing installed at all.

macOS · Linux
git clone https://github.com/snepssen/protoke.git
cd protoke
./setup.sh          # creates .venv, installs Parakeet
./start.sh
Windows
git clone https://github.com/snepssen/protoke.git
cd protoke
setup.bat
start.bat

ffmpeg must be built with the subtitles (libass) filter — brew install ffmpeg, winget install Gyan.FFmpeg, or apt install ffmpeg all provide one.

EngineWhere it runsNeeded forStatus
onnx-parakeetWindows, Linux, macOS · x86 and ArmTranscriptionDefault
mlx-parakeetApple siliconTranscription, fasterOptional
faster-whisperAnywhereFallback model familyOptional
macwhispermacOSTranscription via MacWhisper ProOptional
audio-separatorAnywhereWordless singing — yodels, held notesOptional · ~1 GB

Replacing MacWhisper was the point of the rewrite: it is macOS-only and paid, and the tool used to need it. Parakeet TDT through onnx-asr does the same job free on every desktop platform.

Source

MIT · Python 3.10+

The whole thing, including the tests and the release check that has to pass before anything ships.

Where it came from

Split out of tools-core

It began as one directory of a utility repository and outgrew it. The other field tools are still there.

The voice

Piper · GPL-3.0

The narration in the clip above was synthesised by Voice Forge, the text-to-speech half of this workshop.

The wider workshop

Five tools, one local-first practice.

Move between guided-meditation authoring, measured speech, lyric video, delivery validation, and the small field tools that support all of them.

If something looks wrong

Say so

Bug reports and shape critiques are equally welcome — the face got most of its improvements from someone describing exactly what looked off about it.