record

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package record captures frames from a running visualizer into demo GIFs, MP4 clips and PNG screenshots, the way the family's --record flags and screenshot keys do.

A Recorder (from NewRecorder) accumulates downscaled, dithered frames via Recorder.Add and writes a looping GIF with Recorder.Save. WithVideo — applied automatically by New for an .mp4 path — switches it to an H.264 MP4 instead, which stays small where a long GIF would not. For stills, FromRGBA wraps a software renderer's raw framebuffer as an image and SavePNG writes it out.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoFFmpeg = errors.New("record: ffmpeg not found on PATH (needed for MP4 output)")

ErrNoFFmpeg reports that MP4 encoding was requested but the ffmpeg binary is not on PATH. GIF and PNG output need no external tools; only video does.

Functions

func EncodeMP4 added in v0.14.0

func EncodeMP4(path string, frames [][]byte, w, h int, fps float64) error

EncodeMP4 pipes raw w×h RGBA frames to ffmpeg and writes an H.264 MP4 at path, played back at fps frames per second. Video stays small where a long GIF would not, which is why the family's longer demo clips use it. It returns ErrNoFFmpeg when ffmpeg is unavailable, so a caller can fall back to a GIF or report the missing dependency plainly.

func FromRGBA

func FromRGBA(fb []byte, w, h int) *image.RGBA

FromRGBA wraps a raw RGBA framebuffer (the software renderers' native output) as an image without copying.

func IsVideoPath added in v0.14.0

func IsVideoPath(path string) bool

IsVideoPath reports whether path names a video file, i.e. one this package encodes with ffmpeg rather than as a GIF. Front-ends use it to decide the output format from a --record path alone.

func SavePNG

func SavePNG(path string, img image.Image) error

SavePNG writes img as a PNG at path — the family's screenshot key.

Example

ExampleSavePNG writes a software renderer's raw framebuffer as a screenshot.

package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/danielriddell21/crucible/record"
)

func main() {
	dir, _ := os.MkdirTemp("", "crucible-png-example")
	defer os.RemoveAll(dir)

	const w, h = 320, 200
	fb := make([]byte, w*h*4) // the renderer's RGBA framebuffer
	err := record.SavePNG(filepath.Join(dir, "shot.png"), record.FromRGBA(fb, w, h))
	fmt.Println(err)
}
Output:
<nil>

Types

type Option added in v0.6.0

type Option func(*Recorder)

Option configures a Recorder at construction. See WithPalette, WithFrameDelay, and WithFinalHold.

func WithFinalHold added in v0.6.0

func WithFinalHold(centis int) Option

WithFinalHold holds the last frame for the given number of centiseconds before the GIF loops, so a demo lingers on its final state. Zero (the default) keeps every frame at the uniform delay.

func WithFrameDelay added in v0.6.0

func WithFrameDelay(centis int) Option

WithFrameDelay overrides the per-frame delay (in centiseconds) that fps otherwise derives, for recordings paced one frame per event rather than by a steady frame rate. A non-positive value is ignored.

func WithFrameDiff added in v0.7.0

func WithFrameDiff() Option

WithFrameDiff stores each frame as only the rectangle that changed since the previous one, keeping the earlier pixels via GIF frame disposal. For a mostly-static scene — a dashboard, a map beside a panel — this shrinks the file dramatically. It quantises without dithering so unchanged regions stay byte-identical between frames; pair it with WithPalette when the scene's colours are known.

func WithPalette added in v0.6.0

func WithPalette(p color.Palette) Option

WithPalette quantises frames to a caller-supplied palette instead of the default web-safe palette.Plan9. A palette tuned to the scene's own colours gives a cleaner GIF than the generic one. An empty palette is ignored.

func WithVideo added in v0.14.0

func WithVideo() Option

WithVideo makes the recorder keep raw RGBA frames and write an H.264 MP4 from Recorder.Save instead of a GIF. Video stays small where a long GIF would not, and skips palette quantisation entirely, so the palette and frame-diff options no longer apply. It needs ffmpeg on PATH; without it Save returns ErrNoFFmpeg. New applies this automatically when the recording path ends in .mp4.

type Options added in v0.8.0

type Options struct {
	// Path is the output GIF path. An empty path means "do not record".
	Path string
	// FPS is the recording's playback rate in frames per second.
	FPS int
	// Scale downscales each captured frame by this integer factor.
	Scale int
	// Frames caps the recording; zero means unlimited.
	Frames int
}

Options bundles the recording settings the family's front-ends expose as flags: where to write the GIF, the playback rate, the downscale factor, and how many frames to capture before exiting. It is the shared shape behind every app's --record flags; Options.AddFlags registers them on a pflag set, Options.AddStdFlags on a standard library one, and New turns the result into a Recorder.

func (*Options) AddFlags added in v0.8.0

func (o *Options) AddFlags(fs *pflag.FlagSet)

AddFlags registers the standard --record, --record-fps, --record-scale and --record-frames flags on fs, bound to o. A front-end wires them with cmd.Flags() from its own cobra command, so crucible supplies the flags without owning the command tree. Pre-set a field before calling to change that flag's default (e.g. Options{Scale: 2}); a zero field uses the canonical default.

func (*Options) AddPacedFlags added in v0.9.0

func (o *Options) AddPacedFlags(fs *pflag.FlagSet)

AddPacedFlags registers the --record and --record-frames flags on fs, bound to o, for a recorder whose playback is paced by a fixed per-frame delay (WithFrameDelay) rather than a real-time frame rate. It omits --record-fps and --record-scale, which such a recorder ignores: the frame delay overrides the fps-derived timing, and these event-paced demos capture at full resolution. A zero Frames leaves the cap to the caller (its own default or the whole run); pre-set it to change the flag's default.

func (*Options) AddPacedStdFlags added in v1.0.0

func (o *Options) AddPacedStdFlags(fs *flag.FlagSet)

AddPacedStdFlags registers the same flags as Options.AddPacedFlags on a standard library flag.FlagSet.

func (*Options) AddStdFlags added in v1.0.0

func (o *Options) AddStdFlags(fs *flag.FlagSet)

AddStdFlags registers the same flags as Options.AddFlags on a standard library flag.FlagSet, for a front-end that parses with flag rather than cobra and pflag. The names, defaults and usage strings are the same ones, so every app in the family answers to the same --record contract whichever flag package it is built on.

func (Options) Recording added in v0.8.0

func (o Options) Recording() bool

Recording reports whether a recording was requested (a path was set).

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder accumulates downscaled frames for a demo GIF, or — in video mode, see WithVideo — raw frames for an MP4.

Example

ExampleRecorder captures three frames at 20 fps, half scale, and writes a demo GIF — what a game's --record flag does with each rendered frame.

package main

import (
	"fmt"
	"image"
	"image/color"
	"os"
	"path/filepath"

	"github.com/danielriddell21/crucible/record"
)

func main() {
	dir, _ := os.MkdirTemp("", "crucible-record-example")
	defer os.RemoveAll(dir)

	r := record.NewRecorder(20, 2, 0)
	for i := range 3 {
		frame := image.NewRGBA(image.Rect(0, 0, 64, 48))
		frame.Set(i, i, color.RGBA{R: 255, A: 255})
		r.Add(frame)
	}
	if err := r.Save(filepath.Join(dir, "demo.gif")); err != nil {
		fmt.Println("save:", err)
		return
	}
	fmt.Println(r.Len(), "frames")
}
Output:
3 frames

func New added in v0.8.0

func New(o Options, opts ...Option) *Recorder

New returns a recorder configured from o. Extra options (WithPalette, WithFrameDiff, …) still apply. When o.Path names an .mp4 the recorder is put in video mode (WithVideo), so a front-end's --record flag chooses GIF or MP4 by file extension alone.

func NewRecorder

func NewRecorder(fps, scale, maxFrames int, opts ...Option) *Recorder

NewRecorder returns a recorder that captures at the given frames per second, downscaling each frame by scale. maxFrames caps the recording; zero means unlimited. Out-of-range arguments are clamped to sane values. Options tune the palette and timing.

func (*Recorder) Add

func (r *Recorder) Add(img image.Image)

Add captures one frame. Frames past the cap are dropped.

func (*Recorder) Done

func (r *Recorder) Done() bool

Done reports whether the recorder has reached its frame cap.

func (*Recorder) Len

func (r *Recorder) Len() int

Len returns the number of captured frames.

func (*Recorder) Save

func (r *Recorder) Save(path string) error

Save writes the captured frames to path: a looping GIF, or an H.264 MP4 when the recorder is in video mode (WithVideo). It fails when nothing was captured.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL