Skip to content

Repository files navigation

dvdrw — animated SVG terminal recordings, programmatic

Generate animated SVG terminal recordings from code.

npm versionnpm downloadslicensecli

dvdrw is the Node library behind dvd-cli — call it from your own code, your build pipeline, or your service. Output is a single self-contained animated SVG. No ffmpeg, no headless browser, no shelling out.

npm install dvdrw
importdvdfrom'dvdrw';const{ svg }=awaitdvd(` Type "echo hello world" Enter Sleep 800ms`,{theme: 'dracula',template: 'macos'});

quick start


Contents


Why the library?

The CLI is great when you have a .cd script in a file. The library is for everything else:

  • Programmatic content — render the output of a real test run, a real deploy, or a real benchmark, with frames built from runtime data.
  • Raw stdout capture — feed any ANSI byte stream straight in ({ raw }). Spinners, progress bars, chartscii, lolcat, anything that animates on the terminal.
  • Build-pipeline integration — fully async, optional onProgress callback, no temp files, no subprocesses by default.
  • Embeddable — drop into a docs generator, a service, an Electron app, a serverless function. The output is a string.
  • Composable — the parser, terminal emulator, coalescer, emitter and animator are all exported and usable independently.

If you want a single command on the CLI that takes a .cd file and writes a .svg, use dvd-cli. If you want to call into the engine, you're in the right place.


Inputs

dvd(input, options) accepts four input shapes. Pick whichever matches the data you already have.

1. CD script string

The fastest path. Same syntax as dvd-cli, just inlined.

const{ svg }=awaitdvd(` Type "npm install dvdrw" Sleep 400ms Enter Sleep 800ms`,{theme: 'dracula',template: 'macos',title: 'quick-start'});

2. Programmatic steps

When the content of your animation is computed at runtime — a generated test report, a stream of deploy events, a templated demo — skip the script and pass an array.

// Each Type+Enter is sent through a real shell, so wrap any styled output// in `echo -e "..."` rather than typing raw ANSI as a command.conststeps=[{type: 'Type',text: 'echo -e "\\x1b[2m$\\x1b[0m npm test"'},{type: 'Key',key: 'Enter'},{type: 'Sleep',duration: 600},
...tests.flatMap((t)=>[{type: 'Type',text: `echo -e "\\x1b[32m ✓\\x1b[0m ${t.name} \\x1b[2m(${t.ms}ms)\\x1b[0m"`},{type: 'Key',key: 'Enter'},{type: 'Sleep',duration: 200},]),];const{ svg }=awaitdvd(steps,{theme: 'tokyoNight',template: 'macos',title: 'test runner'});

programmatic test runner output

Full source: examples/02-programmatic-steps.ts

3. Raw terminal output

Capture stdout from any command and hand the bytes over. dvd auto-detects the animation pattern (cursor reset, terminal reset, clear-line, cursor-up) and splits into frames automatically.

importdvdfrom'dvdrw';import{spawnSync}from'node:child_process';constr=spawnSync('myscript.sh',{encoding: 'buffer'});constraw=r.stdout.toString('binary');const{ svg }=awaitdvd({ raw,totalDuration: 2400},{theme: 'catppuccinMocha',template: 'macos',title: 'spinner capture',});

captured spinner output

Full source: examples/03-raw-output.ts

4. Pre-parsed script

If you've already parsed a CD script (e.g., for validation or transformation), pass the AST directly:

importdvd,{parseCDScript}from'dvdrw';constscript=parseCDScript(scriptText);// ...mutate, validate, splice frames...const{ svg }=awaitdvd({ script },{theme: 'nord'});

Themes

37 built-in themes, all exported from themes. Pass by name or as a full Theme object.

awaitdvd(script,{theme: 'tokyoNight'});
dracula
dracula
tokyoNight
tokyoNight
catppuccinMocha
catppuccinMocha
nord
nord
gruvboxDark
gruvboxDark
monokai
monokai
oneDark
oneDark
synthwave84
synthwave84

The full list: a11yDark, base16Dark, base16Light, blackboard, catppuccinMocha, cobalt, dark, dracula, draculaPro, duotoneDark, githubDark, githubLight, gruvboxDark, gruvboxLight, hopscotch, lucario, material, monokai, night3024, nord, oceanicNext, oneDark, oneLight, pandaSyntax, paraisoDark, seti, shadesOfPurple, solarizedDark, solarizedLight, synthwave84, terminal, tokyoNight, twilight, verminal, vscode, yeti, zenburn.

For custom palettes, pass a Theme object directly:

importdvd,{typeTheme}from'dvdrw';constretroGreen: Theme={name: 'retro',background: '#0a0a0a',foreground: '#00ff00',cursor: '#00ff00',// ...the 16 ANSI colors};awaitdvd(script,{theme: retroGreen});

Source: examples/04-themes-gallery.ts


Templates

Window chrome — macos / windows / minimal.

macoswindowsminimal
macos templatewindows templateminimal template

Source: examples/09-templates.ts


Loop styles

Animations loop by default. Choose how the loop behaves at the boundary:

awaitdvd(script,{loopStyle: 'reverse',// 'loop' | 'reverse' | 'rewind' | 'fade'loopPause: 600,rewindSpeed: 6,// for 'rewind'fadeDuration: 1200,// for 'fade'});
loop — restart from frame 0
loop style: loop
reverse — play forward then back
loop style: reverse
rewind — fast reverse like rewinding tape
loop style: rewind
fade — fade to black, fade back in
loop style: fade

Source: examples/05-loop-styles.ts


Branded output

Gradient backgrounds, watermarks, custom borders — for docs sites and landing pages where the SVG carries product weight.

const{ svg }=awaitdvd(script,{theme: 'tokyoNight',template: 'macos',background: 'gradient(#7c5fff, #ff6ec7:diagonal)',backgroundPadding: 48,borderRadius: 12,watermark: 'made with dvd',});

Backgrounds accept solid colors (#1a1a2e) or gradients in the form gradient(<color>, <color>[:vertical|horizontal|diagonal]) with as many stops as you need.

branded output

Source: examples/06-branding.ts


Progress tracking

Wire dvd into your build pipeline or TUI. The onProgress callback fires for every step the executor runs.

awaitdvd(script,{onProgress: (current,total,description)=>{constpct=Math.round((current/total)*100);process.stdout.write(`\r[${pct}%] ${description??''}`);},});

The returned result also carries metadata — frame count, total duration, effective FPS — for logging and CI annotations:

constresult=awaitdvd(script);console.log(result.metadata);// { duration: 2118, frameCount: 33, fps: 15.6 }

Source: examples/07-progress.ts


Low-level API

Skip the executor when you want to render arbitrary terminal state directly — for static badges, dashboards, CI annotations, or content that doesn't fit the script model.

import{coalesce,createGridState,emit,processInput,themes}from'dvdrw';constfontSize=16;consttheme=themes.draculaPro;letstate=createGridState(44,9);state=processInput(state,'\x1b[1;38;5;213m dvdrw\x1b[0m · terminal recordings as svg');constspans=coalesce(state,theme);const{ svg }=emit(spans,state.cursor,false,{
theme,template: 'minimal',width: 460,height: 240,
fontSize,lineHeight: fontSize*1.4,charWidth: fontSize*0.6,padding: 16,});

low-level API output

The exposed building blocks:

ModuleSymbols
Terminal emulatorcreateGridState, processInput, applyCommand, applyCommands, parseInput
Text processingcoalesce
Emissionemit, emitAnimated, emitFilmstripAnimated
AnimationcreateAnimatedSVG, createFilmstripSVG, optimizeSvg
Raw outputprocessRawOutput, detectAnimationType, splitIntoFrames
Cast filesparseCastFile, RecordingPlayer, generateFramesFromRecording
Script parsingparseCDScript, CDParseError
ExecutorCDExecutor

Source: examples/08-low-level-api.ts


Rendering modes: filmstrip vs SMIL

Two animation engines are available. The default (filmstrip) is what you almost always want.

Filmstrip (default)SMIL (smil: true)
EngineCSS @keyframes over a deduped row poolNative SVG <animate> per frame
File sizeSmaller — scales with unique rows, not total framesLarger — scales with total frames
Best forREADME embeds, docs, long recordings, mostly-static contentShort animations, high-FPS smoothness on iOS Safari / 120Hz
awaitdvd(script,{smil: true});

Options reference

const{ svg, frames, frameData, metadata }=awaitdvd(input,{// Window chrome
theme,// Theme name (string) or Theme object — default 'dark'
template,// 'macos' | 'windows' | 'minimal' — default 'macos'
title,// window title text
watermark,// string or SVG markup// Dimensions
width, height,// omit for auto-sizing from script content
fontSize,// default 14
lineHeight,// multiplier — default 1.4
letterSpacing,
fontFamily,
padding,// default 16// Borders
borderRadius,// default 8
borderColor, borderWidth,// Background (outside the terminal window)
background,// '#hex' or 'gradient(#a, #b[:horizontal|vertical|diagonal])'
backgroundPadding,
backgroundRadius,// Header / footer
headerHeight, headerBackground, headerBorder,
headerBorderColor, headerBorderWidth,
footerHeight, footerBackground, footerBorder,
footerBorderColor, footerBorderWidth,// Cursor
cursorStyle,// 'block' | 'bar' | 'underline'
cursorColor,
cursorBlink,// default false// Animation
fps,
loop,// default true
loopStyle,// 'loop' | 'reverse' | 'rewind' | 'fade'
loopPause,// ms between cycles
pauseAtEnd,// ms hold on last frame — default 1000
fadeDuration,// ms for 'fade' style — default 1500
rewindSpeed,// multiplier for 'rewind' — default 5
playbackSpeed,// 1 = normal, 2 = 2x, 0.5 = half speed// Renderer
smil,// false = filmstrip (default), true = SMIL
optimize,// SVGO post-pass — default true
customGlyphs,// box-drawing as geometric shapes — default true// Callbacks
onFrame,// (frame: TerminalFrame) => void
onProgress,// (current, total, description?) => void});

The result:

result.svg// animated SVG stringresult.metadata.duration// total msresult.metadata.frameCount// number of framesresult.metadata.fps// effective fpsresult.frames// TerminalFrame[]result.frameData// FrameData[] — raw row data, useful for custom emitters

Steps reference

When using the programmatic-steps input, each entry conforms to one of these shapes:

TypeFieldsExample
Typetext, optional speed (ms/char){ type: 'Type', text: 'hello', speed: 50 }
Keykey{ type: 'Key', key: 'Enter' }
Sleepduration (ms){ type: 'Sleep', duration: 1000 }
Shortcutkey + modifier flags{ type: 'Shortcut', ctrl: true, key: 'c' }
Screenshotpath{ type: 'Screenshot', path: 'frame.svg' }
Copy / Pastetext (Copy only){ type: 'Copy', text: 'hi' }
Setsetting, value{ type: 'Set', setting: 'Theme', value: 'dracula' }
Envkey, value{ type: 'Env', key: 'NODE_ENV', value: 'prod' }

Keys: Enter, Backspace, Tab, Space, Left, Right, Up, Down.


Comparison

dvdrwVHSasciinema
OutputSVGGIF / MP4asciicast
Native APITypeScript libCLIJSON + player
Dependenciesnoneffmpeg, ttydplayer embed
Scalableyesnoyes
GitHub READMEperfectworksembed only
Editableyes (XML)noyes (JSON)
Offlineyesyesno
Loop styles4 modesbasicbasic
Programmaticyeslimitedyes

Examples

All runnable. Each writes its SVG into examples/svgs/.

npx ts-node -P tsconfig.dev.json examples/01-quick-start.ts
npx ts-node -P tsconfig.dev.json examples/02-programmatic-steps.ts
npx ts-node -P tsconfig.dev.json examples/03-raw-output.ts
npx ts-node -P tsconfig.dev.json examples/04-themes-gallery.ts
npx ts-node -P tsconfig.dev.json examples/05-loop-styles.ts
npx ts-node -P tsconfig.dev.json examples/06-branding.ts
npx ts-node -P tsconfig.dev.json examples/07-progress.ts
npx ts-node -P tsconfig.dev.json examples/08-low-level-api.ts
npx ts-node -P tsconfig.dev.json examples/09-templates.ts
# or render them all in one go:
npx ts-node -P tsconfig.dev.json examples/10-render-all.ts

Related

  • dvd-cli — the CLI front-end (.cd scripts, pipe mode, rec / render sub-commands)
  • shellfie — terminal screenshots in code
  • shellfie-cli — terminal screenshots CLI
  • shellfied — terminal screenshots web service

License

MIT

About

Create simple animated SVGs from your terminal

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages