Documentation
¶
Overview ¶
Package encode turns captured frames into a shareable artifact: an animated GIF, a numbered PNG directory, or — when ffmpeg is on PATH — an MP4/WebM.
It is deliberately PURE with respect to the browser: the input is a slice of already-captured frames plus options, and the output is bytes. There is no CDP and no knowledge of how the frames were produced. That is the whole reason RFC-0011 splits it out — frame timing through a real Chrome is inherently variable, so the parts that must be exactly right (the palette, the frame delays, the annotation compositing, the --max-size reduction) are tested here on synthetic frames instead.
The one thing that does cross the boundary is a context, because --max-size re-encodes the whole recording up to nine times and the command that asked for it has a --timeout. See reduce().
Two properties are load-bearing and any change must preserve both:
Annotation is composited HERE, at export, never at capture. Without Options.Annotate the exported frames are pixel-identical to what was captured, which is what makes a recording usable as a README asset (RFC-0011 US-5 / VS-13) and what lets one capture be exported both ways.
The GIF path uses only the standard library's image/gif. A Go CLI distributed as a single static binary must be able to produce its default format with no external program; ffmpeg is required only for mp4/webm, and its absence is a NAMED error rather than a silent fallback (VS-10).
Index ¶
- Constants
- Variables
- func AnnotateImage(data []byte, format string, quality int, cssW, cssH float64, labels []Label) ([]byte, []bool, error)
- func Available(f Format) error
- func Extensions() string
- func HAR(entries []NetEntry, opts HAROpts) ([]byte, error)
- func IsNoEncoder(err error) bool
- type File
- type Format
- type Frame
- type HAROpts
- type Label
- type Mark
- type NetEntry
- type Options
- type Result
Constants ¶
const HARTimeLayout = "2006-01-02T15:04:05.000Z07:00"
HARTimeLayout is the RFC 3339 UTC, millisecond-precision layout both `started_at` (RFC-0017) and this encoder's fallback use, so a fallback timestamp is indistinguishable in shape from one filled from a real row.
Variables ¶
var ErrNoEncoder = errors.New("no encoder available for this format")
ErrNoEncoder reports that the requested format needs a program that is not installed. It is a distinct sentinel because the CLI maps it to `usage` / exit 2 with a message naming the requirement, and — crucially — checks it BEFORE draining the recording, so the frames stay exportable as GIF (VS-10).
var ErrNoFrames = errors.New("no frames were captured")
ErrNoFrames reports an export with nothing to export.
Functions ¶
func AnnotateImage ¶ added in v0.3.0
func AnnotateImage(data []byte, format string, quality int, cssW, cssH float64, labels []Label) ([]byte, []bool, error)
AnnotateImage decodes data (png or jpeg), draws each label, and re-encodes as format ("png" | "jpeg"; quality applies to jpeg only). It reports, per label, whether it put any pixel on the canvas — the meaning Annotated has for a recording. It is pure: synthetic-image tests pin the drawing.
A label whose centre falls outside the decoded image draws nothing (disc, ring, AND badge) rather than clamping the badge onto the canvas anyway — internal/chrome has already dropped every candidate outside the capture's clip, so this only matters for a caller (a test, a future format) that hands in a point it never checked.
func Available ¶
Available reports whether this machine can produce the format, wrapping ErrNoEncoder with the missing requirement NAMED.
The CLI calls it before the recording is drained, so a missing ffmpeg costs the user an exit 2 and nothing else — the frames are still in the daemon and still exportable as GIF (VS-10).
func Extensions ¶
func Extensions() string
Extensions lists the file extensions a format is inferred from, for error messages.
func HAR ¶ added in v0.3.0
HAR renders the entries as an HTTP Archive 1.2 document: `json.Encoder` with two-space indent and HTML escaping OFF, so a redacted value ("<redacted>") survives byte-for-byte instead of becoming <redacted> — a reader grepping the file for the marker has to be able to find it.
func IsNoEncoder ¶
IsNoEncoder reports whether err is the missing-encoder condition.
Types ¶
type File ¶
File is one numbered frame of a `frames` export. The caller writes it; this package does no I/O of its own for that format.
type Format ¶
type Format string
Format names an export format.
const ( FormatGIF Format = "gif" FormatMP4 Format = "mp4" FormatWebM Format = "webm" FormatFrames Format = "frames" )
The export formats. gif is the default because it is the one that works with no external dependency and embeds everywhere a bug report or README does (RFC-0011 open question 2).
func FormatFromPath ¶
FormatFromPath infers the format from an output path's extension, reporting false when the path carries no extension this package recognises.
func ParseFormat ¶
ParseFormat resolves a user-supplied format name.
type Frame ¶
type Frame struct {
Data []byte `json:"data"`
TS time.Time `json:"ts"`
// CSSWidth/CSSHeight are the frame's size in page CSS pixels. They exist so
// a Mark's page coordinates map onto image pixels when the capture was
// scaled down. Zero means "the image's own pixel dimensions", i.e. 1:1.
CSSWidth float64 `json:"css_width,omitempty"`
CSSHeight float64 `json:"css_height,omitempty"`
// Marks are the actions that landed while this frame was on screen. They are
// carried, not drawn: Options.Annotate decides at export whether they become
// pixels.
Marks []Mark `json:"marks,omitempty"`
}
Frame is one captured frame plus what is needed to draw on it.
Data is the image exactly as Chrome produced it (a screencast JPEG, or a PNG in tests); it is decoded here and never re-compressed lossily.
type HAROpts ¶ added in v0.3.0
HAROpts parameterises an export. Version is the CLI build version for log.creator; Now is the export instant, used only for a row with no start.
type Label ¶ added in v0.3.0
Label is a numbered position marker: the disc-and-ring of a Mark plus the number N. X, Y are CSS pixels from the capture's top-left, Mark's space.
type Mark ¶
type Mark struct {
X float64 `json:"x"`
Y float64 `json:"y"`
Command string `json:"command,omitempty"`
}
Mark is one action to draw on the frames it overlaps: where the pointer landed, and which command put it there.
The coordinates are PAGE (CSS) pixels, the same space the pointer verbs resolve and report, so nothing here has to know about device scale factors — Frame.CSSWidth/CSSHeight carry the mapping onto image pixels.
type NetEntry ¶ added in v0.3.0
type NetEntry struct {
ID string `json:"id"`
Method string `json:"method"`
URL string `json:"url"`
Type string `json:"type"`
Status *int64 `json:"status"` // nil: no status (pending or network-level failure)
StatusText string `json:"status_text"`
StartedAt string `json:"started_at"` // RFC 3339 UTC, ms; "" when the row carries none
StartedMs int64 `json:"started_ms"`
DurationMs *int64 `json:"duration_ms"` // nil: not finished
RequestSize int64 `json:"request_size"`
ResponseSize int64 `json:"response_size"`
FromCache bool `json:"from_cache"`
Failed bool `json:"failed"`
Error *string `json:"error"`
Pending bool `json:"pending"`
RequestHeaders map[string]string `json:"request_headers"`
ResponseHeaders map[string]string `json:"response_headers"`
RequestBody *string `json:"request_body"`
ResponseBody *string `json:"response_body"`
BodyTruncated bool `json:"body_truncated"`
}
NetEntry is one request as the `net` envelope reports it. The JSON tags ARE the envelope keys (RFC-0003, RFC-0017): the CLI fills it from the rows `Net` returns by a JSON round trip, which also normalises the int64/float64 split between the in-process and daemon paths.
func DecodeNetEntries ¶ added in v0.3.0
DecodeNetEntries turns the `requests` value of a net result into typed rows. It is a JSON round trip (marshal, then unmarshal into []NetEntry) rather than a type switch, because that is what also normalises the int64/float64 split between an in-process call and one that crossed the daemon's RPC.
type Options ¶
type Options struct {
Format Format
// FPS is the playback cadence used when the frames carry no usable
// timestamps, and the floor/ceiling is applied to timestamp-derived delays
// either way.
FPS float64
// Loop is the GIF loop count: 0 loops forever, n > 0 plays n times.
Loop int
// Annotate composites the position markers. Off by default — a README asset
// must not carry the tool's own drawing (US-5).
Annotate bool
// MaxBytes is a best-effort size ceiling. See reduce(): the export is
// re-encoded at reduced scale (and then a reduced frame count) until it
// fits, and the values actually used are reported in Result.
MaxBytes int
}
Options controls one export.
type Result ¶
type Result struct {
Data []byte // gif/mp4/webm bytes (nil for FormatFrames)
Files []File // FormatFrames only
Format Format
Frames int
FPS float64
Scale float64 // 1 unless --max-size forced a reduction
Width int
Height int
Bytes int
DurationMs int64
Annotated bool
Reduced bool // --max-size changed the scale and/or the frame count
// DecodeFailures counts captured frames that did not decode as an image and
// were therefore left out. They are reported rather than fatal: see
// decodeAll.
DecodeFailures int
// WithinMaxSize reports whether the ceiling was actually met. False means
// the reduction ladder bottomed out first — a best-effort bound, reported
// rather than silently missed.
WithinMaxSize bool
// ReductionTimedOut reports that the --max-size ladder was cut short by the
// context rather than by its own bounds, so this is the best COMPLETE
// attempt and not the best attempt available.
ReductionTimedOut bool
}
Result is the artifact plus everything the envelope reports about it. Every field is a fact about what was PRODUCED, not what was requested, which is the point: --max-size can change the scale and the frame count, and a caller that only saw its own flags back would not know.