Releases: philsherry/playback
Releases · philsherry/playback
Release list
v1.5.0
Added
vhs.preambleinmeta.yaml—VhsOverridesnow accepts an optionalpreamblestring array. Each entry is a raw VHS directive injected verbatim into the generated.tapefile after theSetconfiguration block and before any step content. Enables consumer projects to emitHide/Showsetup blocks (PS1 suppression, stderr redirect, terminal clear) for freeform recordings — typed sentences appear on a clean, promptless surface withoutcommand not founderrors or bash history expansion artefacts
Tests
src/schema/meta.test.ts—vhs.preambleaccepted as string array, accepted as empty array, absent preamble isundefinedsrc/generator/vhs.test.ts— no preamble emitted when absent or empty, directives injected verbatim afterSetblock and before step content, ordering verified
v1.4.1
Added
- Full VHS override coverage —
meta.yamlvhsblock now supports overrides for every previously hardcoded VHS constant:borderRadius,fontFamily,framerate,margin,marginFill,width,windowBar(in addition to the already-supportedfontSize,height,shell,theme,typingSpeed). Enables consumer projects to set any combination of recording dimensions, window chrome, font, and framerate per tape without touching the core defaults - Per-voice VITS tuning in
voices.yaml—VoiceEntrynow accepts optionallengthScale,noiseScale, andnoiseWfields. When present on a catalogue entry, these take precedence over the built-inVOICE_CONFIGtable andDEFAULT_SYNTH_CONFIGfallback. Consumer projects can tune synthesis for their own voices entirely within their ownvoices.yamlwithout modifying this package
Documentation
.agents/TAPES.md—vhsoverrides table now covers all twelve overridable fields; added per-voice VITS tuning section with examplevoices.example.yaml— documents the optionallengthScale,noiseScale,noiseWfields per entry
Tests
src/generator/vhs.test.ts— coverage for all newvhsoverrides (width,framerate,windowBar,borderRadius,margin,marginFill,fontFamily) and default window chrome valuessrc/schema/meta.test.ts— schema acceptance and rejection tests for all newvhsfieldssrc/runner/piper.test.ts— catalogue VITS tuning takes precedence overVOICE_CONFIG; partial catalogue entries fall back toDEFAULT_SYNTH_CONFIGfor unset fields
v1.4.0
Added
- Multi-speaker piper voice support —
VoiceEntrynow accepts an optionalspeakerfield; when set,--speaker <id>is passed to piper at synthesis time. Enables models such asen_GB-semaine-mediumthat pack multiple distinct characters into a single.onnxfile — each character can be defined as a separate named voice entry. Voice selection remains tape-level (one voice narrates the whole tape);speakerselects which character within the model speaks. Single-speaker voices are unaffected — the field is optional and existing entries need no changes getVoiceSpeaker()export — returns the speaker ID for a voice identifier, orundefinedfor single-speaker models; throws for unknown voice identifiersVOICE_CONFIGfallback — voices not listed inVOICE_CONFIGnow use sensible synthesis defaults (lengthScale: 1.0,noiseScale: 0.1,noiseW: 0.6) rather than crashing the runner. Consumer projects can define custom voices in a project-localvoices.yamlwithout this package needing to enumerate them
Tests
src/voices.test.ts—getVoiceSpeakerreturnsundefinedfor single-speaker voices, throws for unknown voice, returns speaker ID for multi-speaker catalogue entriessrc/runner/piper.test.ts—--speakerabsent for single-speaker voices;--speaker <id>present for multi-speaker voices;VOICE_CONFIGfallback used without crash for voices not listed therein
v1.3.0
Added
playback init-agentcommand — installs theplayback-runnerAI agent into any project that uses Playback. Creates.claude/agents/playback-runner.md(Claude Code subagent) and.github/prompts/playback-runner.prompt.md(GitHub Copilot agent). Use--forceto overwrite existing filesplayback-runneragent — a friendly, practical guide for tape authors covering the full Playback feature set: tape and meta authoring, CLI flags, voices, timing, the TUI editor, scaffolding, and playlists. Written for designers and content creators. Ships intemplates/and is copied into the consuming project byplayback init-agentstudio/demo/agent/— demo tape showingplayback init-agentin action, with agum-styled Q&A sequence
v1.2.3
Fixed
vhs.shellschema validation —meta.yamlnow rejectsvhs.shellvalues containing double-quote characters; VHSSet Shell "..."has no escape sequence for"and would silently produce an invalid.tapefile- Shell override in
generateVhsFromTimeline— the timeline VHS generator hard-codedSet Shell "zsh"regardless ofmeta.yaml'svhs.shelloverride, diverging fromgenerateVhsTape; now usesvhsOverrides?.shell ?? SHELL - Chapter marker priority in TUI step list — the
§chapter marker unconditionally overwrote the▸cursor and!overlap markers for selected or flagged chapter steps; cursor and overlap markers now take precedence scaffold.tsYAML frontmatter quoting — episode titles containing:,#, or other YAML-special characters appeared unquoted in output, producing invalid YAML that downstream parsers would reject; titles are now double-quoted with embedded"escaped- FFMETADATA1 chapter title escaping —
=,;,#,\, and newlines in chapter titles went unescaped; ffmpeg rejects files where values contain bare=or;. All four are now escaped per the FFMETADATA1 spec; newlines replaced with a space - Redundant conditional in
scaffold.ts—step.action === 'narrate' ? step.narration : step.narrationsimplified tostep.narration --captions-onlyunnecessarily recorded terminal —runVhsran unconditionally before the voice loop;--captions-onlyskipped ffmpeg inside the loop but still paid the full VHS recording cost and could overwrite existing raw recordings. BothrunVhsand the--webpre-loop video/GIF/poster encoding are now guarded by!captionsOnly--webposter not copied intowebOutputDir— when the tape directory contained aposter.png, the manifest received the source path (outsideweb/) rather than a self-contained web-relative copy, and card generation did not run. The poster is nowcopyFileSync'd intowebOutputDirwith the card generated from the copy- Multi-voice non-web GIF named after primary voice — in multi-voice mode the GIF carried the name
slug.<voice>.gif(e.g.slug.alan.gif) rather than the voice-agnosticslug.gif;runFfmpegnow skips GIF generation for all voices in multi-voice mode, and a singleslug.gifcomes from the primary voice MP4 after the voice loop buildM4aArgsinvalid filter on empty segments — an empty segment list producedamix=inputs=0, an invalid ffmpeg filter; the function now throws a descriptive error before invoking ffmpegmanifest.test.tsindentation corrected from 4-space to tabs (matches rest of codebase)
Tests
meta.test.ts—vhs.shellaccepts plain names and paths; rejects values containing double-quote characterstimeline/index.test.ts—generateVhsFromTimelineemitsSet Shell "zsh"by default and respectsvhs.shelloverridemodel_test.go— unselected chapter step shows§; selected chapter step shows▸not§chapters.test.ts— FFMETADATA1 escaping for=,;,#,\, and newlines in both explicit and auto-generated chapter titles
v1.2.2
Fixed
- MKV container metadata — all three ffmpeg paths (
stitchMp4,stitchMkv,buildMkvMultiVoiceArgs) used-map_metadata ${n}to embed chapter markers; that flag copies global metadata from the source, not chapter markers. Since FFMETADATA1 chapter files carry no global tags, it silently clobbered the explicit-metadata title/artist/…flags. Changed to-map_chapters ${n}throughout so global metadata and chapter embedding stay independent
Added
--webflag — produces a web-optimised output set inside aweb/subdirectory:slug.silent.mp4(shared padded video, no audio, no burned captions),slug.mp4/slug.<voice>.mp4(primary voice with burned captions and baked audio, for download), per-voice M4A audio files (time-locked via silence soaudio.currentTimestays in sync withvideo.currentTime), WebVTT and SRT captions per voice, and amanifest.jsonwithvideo(silent),download(voiced MP4), and per-voiceaudioandcaptionsentries. A web player can switch voices at runtime without re-downloading the video- Multi-voice MKV bundling —
--mkvnow usesstitchMkvMultiVoiceto pack all voice audio tracks and SRT subtitle streams into one MKV container; stream labels carry the voice name so players (VLC, mpv) can switch between them at playback time.--weband--mkvcombine freely - Caption word-wrapping —
wrapCueTextwraps narration text at ≤ 65 characters per line (GOV.UK style guide) with a hard ceiling of 2 lines; applied to all three caption formats (VTT, ASS, SRT). ASS hard line breaks use\N; SRT and VTT use\n ogfield in manifest — placeholder for a 1200×630 Open Graph image alongside the existingdownload,poster, andcardfields; alwaysnulluntil OG generation is implemented--manifest-onlyflag — regeneratesmanifest.jsonfrom existing web output files without re-running the pipelinedocs/OUTPUT_FORMATS.md— reference for all output modes (default,--web,--mkv), caption sizing rules, web player sync example, and flag combination guidancetape.pristine.yamlsupport in playlist —findTapeDirscopiestape.pristine.yamltotape.yamlbefore recording when only the pristine file exists, matching the behaviour ofbuild-studio.sh- TUI
CaptionWarnWordsconstant —tui/tape/timing.gonow exports the caption word-count warning threshold, keeping Go and TypeScript in sync
v1.2.1
Fixed
- Poster extraction no longer crashes the pipeline when
ffmpeg'sselectfilter finds no matching frames.ffmpegexits 0 in that case but produces a missing or zero-byte file;generateCardwould then fail with exit code 254 trying to open it. The pipeline now checks that the extracted poster exists and has content before proceeding to card generation, and skips both silently if it does not - Added test coverage for
runFfmpegposter/card guard: missing file, zero-byte file, valid file, explicitposterSourceFile, and no-poster-at-all cases
v1.2.0
Added
playback playlistsubcommand — batch-build all tapes intapesDirconsecutively. ReadstapesDirfromplayback.config.tsby default; accepts--tapes-dir <path>to override. All remaining flags pass through to eachplayback tapeinvocation (e.g.playback playlist -- --vhs-only). Stops at the first failure.
Changed
cli.tsrefactored into a pure command dispatcher — tape pipeline logic extracted tosrc/commands/tape.ts, playlist logic tosrc/commands/playlist.tscleanscript usesrimrafinstead ofrm -rffor cross-platform compatibility
v1.1.0
Added
- Structured logging —
consola-backed logger with--quiet(warn and above) and--verbose(all levels plus full subprocess output) flags. ffmpeg stderr is now captured and filtered in default and quiet modes, surfacing only actionable warnings;--verbosepasses all output through unfiltered - CLI theming — 11 built-in colour themes:
default, four Tokyo Night variants (tokyo-night,tokyo-night-storm,tokyo-night-moon,tokyo-night-day), four Catppuccin flavours (catppuccin-mocha,catppuccin-macchiato,catppuccin-frappe,catppuccin-latte),dracula, andhigh-contrast; set viathemein XDG config or overlaid per-project withtheme.yaml - XDG user config —
$XDG_CONFIG_HOME/playback/config.yaml(falls back to~/.config/playback/config.yaml); configurestheme,logLevel, and defaultvoicesacross all projects - TUI XDG config — TUI reads the same
config.yamlto select its colour theme; 9 new TUI themes matching the CLI set (all Tokyo Night variants, all Catppuccin variants, Dracula);--high-contrastflag still overrides - XDG voices catalogue —
$XDG_CONFIG_HOME/playback/voices.yamlas the user-level base, merged with an optional per-projectvoices.yamlon top (project entries win on name collision);npm run setupbootstraps the XDG catalogue fromvoices.example.yamlon first run and downloads models from it thereafter
Changed
voices.yamlis now gitignored at the project level;voices.example.yamlis the committed reference thatnpm run setupbootstraps fromrelease:preparenow runslintand a smoke test (test:smoke) before the release metadata check
Fixed
Duped colorand255(+1) colorsGIF palette warnings removed from the surfaced warning list; they are benign palette quantization artefacts that vary by theme and cannot be reliably prevented without degrading quality- Studio directory structure uses nested paths for cleaner paths:
demo-tui/→demo/tui/,demo-accessible/→demo/accessible/,example/→example/tape/,example-skills/→example/skills/ demo/tuimeta.yamlseries field corrected fromdemo-tuitodemo(matches the other demo tapes)
v1.0.6
Added
--tapes-dir <path>flag forplaylist:build— target any tape directory, including absolute paths outside the project rooteslint-plugin-perfectionistenforces alphabetical ordering of object keys, interface properties, and type properties across the TypeScript source*.poster.pngand*.card.png(50% scaled) produced alongside--weboutput; poster renamed from*.pngogfield inmanifest.jsonfor future Open Graph image support (nulluntil we add a generation strategy)
Fixed
- AAC encoder
Too many bits > 6144 per framewarning: added-ar 44100to resample Piper's 22050 Hz mono output to the AAC-standard rate before encoding, keeping bits/frame well under the per-channel limit image2image sequence pattern warning on.card.png: added-frames:v 1 -update 1togenerateCard(matching the flags already used byextractPoster)Guessed Channel Layout: monowarning on each WAV input: added-channel_layout monoper-input so ffmpeg does not need to infer it- GIF palette
Duped colorand255(+1)warnings: addedreserve_transparent=0andstats_mode=difftopalettegen, anddither=bayer:bayer_scale=5:diff_mode=rectangletopaletteuse; also corrected the palette filtergraph to use;between its parallel chains