media

package
v0.58.3 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 42 Imported by: 0

Documentation

Overview

Package media stores host content files in per-item folders of one private bucket: library-built keys, the generic manifest with conditional-write edits, and the Store port. See media/s3 for the S3 implementation and media/token for access tokens.

Index

Constants

View Source
const (
	HLSContentType = "application/vnd.apple.mpegurl"
	VTTContentType = "text/vtt; charset=utf-8"
)

Playlist content types.

View Source
const (
	IngestPartSize    = 32 << 20
	IngestConcurrency = 3
)

Ingest defaults: parts are buffered in memory, so memory is about (IngestConcurrency+1) × IngestPartSize.

View Source
const (
	AreaManifest  = layout.AreaManifest
	AreaOriginals = layout.AreaOriginals
	AreaTemp      = layout.AreaTemp
	AreaPrivate   = layout.AreaPrivate
	AreaPublic    = layout.AreaPublic
)

Folder areas.

View Source
const (
	VideoLive      = ""
	VideoAnimation = "animation"
)

Video.Profile values.

View Source
const (
	DefaultMinAspect = 1 / 2.4
	DefaultMaxAspect = 2.4
)

Default aspect bounds: 1:2.4 vertical to 2.4:1 wide, which admits "21:9" content (2560×1080 at 2.37, 2.39:1 cinema) and its vertical equivalents.

View Source
const (
	PhaseQueued      = "queued"
	PhaseDownloading = "downloading" // fetching the original
	PhaseProbing     = "probing"
	PhaseEncoding    = "encoding" // the single ffmpeg pass: ladder, audio, subtitles, sprite
	PhaseMuxing      = "muxing"   // per-quality MP4 downloads
	PhaseUploading   = "uploading"
	PhasePublishing  = "publishing" // the manifest edit
	PhaseImages      = "images"     // item-wide, after every file: poster frame from the renditions
)

Encode phases, in order. Queued covers a job waiting for a worker and a file waiting behind another file of the same job.

View Source
const (
	AccessFull    = "full"    // every file; downloads
	AccessPreview = "preview" // files [0, preview_limit) plus teasers
	AccessNone    = "none"    // teasers only
)

Access levels in ReadResult.

View Source
const (
	StateReady      = "ready"
	StateProcessing = "processing"
	StateFailed     = "failed"
)

Processing states of a file, a slot or an item.

View Source
const (
	MetaLang    = "lang"    // BCP 47 (normalized by processing); also picks a non-UTF-8 file's charset
	MetaLabel   = "label"   // track name; default the language's English name
	MetaForced  = "forced"  // bool: forced-narrative track
	MetaFor     = "for"     // the video file it subtitles; default the manifest's first video
	MetaCharset = "charset" // IANA charset overriding detection, e.g. "shift_jis"
)

Subtitle sidecar meta (Op.Meta on insert or replace). To change it, remove and insert the file in one commit.

View Source
const (
	MaxSinglePut = 64 << 20
	MinPartSize  = 8 << 20
	MaxPartSize  = 16 << 20
)

Upload size rules. Files up to MaxSinglePut are one checksum-bound PUT to originals/sha256-{hex}; larger ones are multipart to temp/u-{uuid} with parts of MinPartSize growing up to MaxPartSize (the last part may be smaller), until the media worker hashes and places them (Manifests.Place).

View Source
const (
	OpInsert  = "insert"  // add Name at Index (default: append), Unattached if set; a retry with the same Original is a no-op
	OpAttach  = "attach"  // make an unattached Name part of the item, after the attached files or at Index, merging Meta; idempotent
	OpReplace = "replace" // swap Name's original; variants are regenerated, a stale hls plays until re-encoded
	OpMove    = "move"    // move Name to Index
	OpRename  = "rename"  // rename Name to To
	OpRemove  = "remove"  // drop Name
	OpEdit    = "edit"    // set Name's Edit (an image); nil clears it. Its variants are regenerated
)

Commit operations. Insert and Replace take an uploaded Original.

View Source
const (
	CodeInvalid      = "invalid_request"   // 400
	CodeForbidden    = "forbidden"         // 403
	CodeNotFound     = "not_found"         // 404: unknown kind, file or upload
	CodeConflict     = "conflict"          // 409: file name taken, or a new item's folder not empty
	CodeIncomplete   = "incomplete"        // 409: multipart parts missing
	CodeNotUploaded  = "not_uploaded"      // 409: commit before the object landed
	CodeTooManyFiles = "too_many_files"    // 409: the commit would exceed the kind's file caps
	CodeTooLarge     = "too_large"         // 413: over the kind's cap
	CodeQuota        = "quota_exceeded"    // 413: owner quota
	CodeType         = "type_not_allowed"  // 415
	CodeChecksum     = "checksum_mismatch" // 422: stored bytes differ from the declared hash
	CodeRate         = "rate_limited"      // 429
	CodeUnavailable  = "unavailable"       // 503: the bucket cannot be reached; retry

	// Image refusals (ImageError): the rules refuse the image; never a server fault.
	CodeImageTooSmall   = "image_too_small"  // 422: edited narrower than the slot's minimum
	CodeImageTooLarge   = "image_too_large"  // 413: more pixels than the processor decodes
	CodeImageUnreadable = "image_unreadable" // 422: not a decodable image of its declared type

	CodeAnimationNotAllowed  = "animation_not_allowed" // 422: an animated image where the policy is AnimationReject
	CodeAnimationTooLong     = "animation_too_long"    // 422: more frames or seconds than the processor allows
	CodeAnimationUnsupported = "animation_unsupported" // 415: an AVIF/HEIF image sequence (decoded as one frame)
)

Stable upload error codes: clients (the browser SDK) branch on Code.

View Source
const (
	FrameDefaultWidth = 320
	FrameMaxWidth     = 1280
)

Frame endpoint bounds.

View Source
const (
	PosterSourceFrame  = "frame"
	PosterSourceUpload = "upload"
	PosterSourceAuto   = "auto"
)

Poster sources.

View Source
const AudioVariant = "audio"

AudioVariant is an encoded audio file's M4A variant (read API ?variant=audio).

View Source
const CookieName = token.CookieName

CookieName is the access worker's cookie.

View Source
const DefaultQueue = "contentkit_media"

DefaultQueue is JobsConfig.Queue's default.

View Source
const EditorVariant = "editor"

EditorVariant is the read API's name for the editor view (Kind.Editor).

View Source
const ItemProgressKey = ""

ItemProgressKey carries Item in a job's reported map.

View Source
const PosterSlot = "poster"

PosterSlot is the video kinds' item-level cover. Players preview the video itself (the SDK plays its HLS inline), so there is no separate preview clip.

View Source
const StartRung = 1080

StartRung is the short side of the variant listed first in each codec: native HLS players (Safari, iOS) start there before measuring.

View Source
const SubtitleVariant = "vtt"

SubtitleVariant is a subtitle file's WebVTT variant (read API ?variant=vtt).

View Source
const UserKind = "user"

UserKind is the kind of per-user folders ({tenant}/user/{id}/), erased by EraseUserTx.

Variables

View Source
var (
	AspectNative = Aspect{}
	Aspect1x1    = Aspect{1, 1}
	Aspect3x1    = Aspect{3, 1}
	Aspect4x5    = Aspect{4, 5}
	Aspect16x9   = Aspect{16, 9}
	Aspect9x16   = Aspect{9, 16}
	Aspect21x9   = Aspect{7, 3} // "21:9" reduces to 7:3
)
View Source
var (
	ErrUnknownKind = errors.New("media: unknown kind")
	ErrType        = errors.New("media: content type not allowed")
	ErrTooLarge    = errors.New("media: file too large")
)
View Source
var (
	// ErrNotVisible hides an item the viewer may not see, or that does not exist.
	ErrNotVisible = errors.New("media: not found")
	// ErrResolve wraps a resolver failure; it always denies.
	ErrResolve = errors.New("media: resolve failed")
	// ErrInvalidRequest is a malformed read request.
	ErrInvalidRequest = errors.New("media: invalid request")
)
View Source
var (
	ErrNotFound           = errors.New("media: object not found")
	ErrPreconditionFailed = errors.New("media: precondition failed")
	ErrNotModified        = errors.New("media: not modified")
	// ErrUnavailable marks a store that could not be reached or answered 5xx;
	// HTTP handlers answer 503.
	ErrUnavailable = errors.New("media: store unavailable")
	// ErrChecksumMismatch: the backend rejected a PUT whose body does not
	// match its x-amz-checksum-sha256.
	ErrChecksumMismatch = errors.New("media: checksum mismatch")
	// ErrNotImplemented: the backend does not support a request feature (501).
	ErrNotImplemented = errors.New("media: not implemented by the store")
)
View Source
var DefaultLadder = []int{2160, 1080, 480}

DefaultLadder is the default ladder by short side.

View Source
var DefaultPosterWidths = []int{640, 960, 1280, 1920, 2560}

DefaultPosterWidths cover a full-width column at 2–3× density.

EncodePhases lists every phase in order.

View Source
var ErrFolderNotEmpty = errors.New("media: new item's folder is not empty")

ErrFolderNotEmpty: a new item's folder already holds objects. Content ids are never reused, so leftovers mean a host bug (a reset id, a restored database); they are never adopted. Purge them deliberately with Jobs.Purge.

View Source
var ErrJobsNotBound = errors.New("media: River jobs are not composed into a client")
View Source
var ErrManifestConflict = errors.New("media: manifest edit kept conflicting")
View Source
var ErrNotAllowed = errors.New("media: not allowed")

ErrNotAllowed is a URL request for a file the viewer may not have, or a blob that file does not reference.

View Source
var ErrPending = errors.New("media: not rendered yet")

ErrPending: an inline image is not rendered (or exposed) yet.

View Source
var ErrStagedGone = errors.New("media: staged upload is gone")

ErrStagedGone is Place's answer when neither the staged upload nor its placed original exists: the file was replaced, removed or swept.

View Source
var ErrSuperseded = errors.New("media: record changed")

ErrSuperseded reports a record that changed while a job worked from it; the job for the newer record does the work instead.

View Source
var MaxOutageSnoozes = 2880

MaxOutageSnoozes caps the snoozes of one job (a day at UnavailableSnooze); past it, outage errors spend attempts again.

PendingOnce dedupes a job per args while one is waiting or running.

View Source
var RedisErrors = expvar.NewInt("contentkit_media_ratelimit_redis_errors")

RedisErrors counts shared-limit Redis failures (each served by the per-process limit instead); published as expvar "contentkit_media_ratelimit_redis_errors".

View Source
var SubtitleTypes = []string{"text/vtt", "application/x-subrip", "text/x-ssa", "text/x-ass"}

SubtitleTypes are the uploaded subtitle sidecars' types: WebVTT, SubRip and SubStation Alpha (SSA/ASS). A kind listing one needs Video. The media worker converts each to WebVTT (the file's SubtitleVariant) and its video lists it as a subtitle track after the source's own, without re-encoding.

View Source
var UnavailableSnooze = 30 * time.Second

UnavailableSnooze is how long a job waits after the store was unreachable.

View Source
var VideoPoster = (*Video)(nil).Poster()

VideoPoster is the default poster slot (Video.Poster): every video kind gets one at its Video.PosterWidths. Its original is an uploaded image or a frame the video worker grabbed; the image job encodes either through the slot's Edit. It is native: the poster keeps the frame's (or upload's) own aspect unless an edit crops it.

Functions

func AudioDownloadKey added in v0.56.0

func AudioDownloadKey(file string) string

AudioDownloadKey is the manifest downloads key of an audio file's M4A.

func Encoded added in v0.26.0

func Encoded(f File) bool

Encoded reports a video file whose current source has an HLS ladder.

func InsertOnce added in v0.43.0

func InsertOnce(ctx context.Context, insert InsertFunc, args RerunArgs, o river.InsertOpts) error

InsertOnce enqueues args after the caller's change to their inputs. An equal job still waiting to run absorbs it. River's uniqueness also covers running jobs, which may have read the inputs before the change, so one follow-up is queued behind a running equal job; a burst shares it. The follow-up's worker calls WaitFor first.

func NewInlineName added in v0.17.0

func NewInlineName() string

NewInlineName names a new inline image: "i-{uuid}".

func NewUploadName

func NewUploadName() string

NewUploadName names a multipart upload whose hash is unknown: "u-{uuid}".

func SHA256Name

func SHA256Name(sum []byte) string

SHA256Name names content-addressed files: "sha256-{hex}".

func SnoozeUnavailable added in v0.58.0

func SnoozeUnavailable(ctx context.Context, store Store, job *rivertype.JobRow, err error) error

SnoozeUnavailable turns a real store outage into a River snooze, which does not count as an attempt, so an outage longer than the retry budget never discards a job. It snoozes only when the job's own context is still live (a job that outran its timeout spends its attempt) and a fresh, bounded store.Check fails too (one bad object on a healthy bucket spends its attempt); River's snooze count in job metadata is capped by MaxOutageSnoozes. Other errors pass through.

func SubtitleType added in v0.57.0

func SubtitleType(name string) string

SubtitleType is the subtitle type of a file name's extension, or "".

func UploadHandler

func UploadHandler(u *Uploads, o UploadHandlerOptions) http.Handler

UploadHandler serves the upload API the browser SDK calls. All routes are POST with JSON bodies; SHA-256 values are lowercase hex. Errors are ErrorReply with the status of its code (Retry-After on 429).

POST /presign      PresignBody  -> PresignReply
POST /parts        PartsBody    -> PartsReply     presign multipart parts
POST /parts/list   TicketBody   -> PartsReply     parts that landed (resume)
POST /complete     TicketBody   -> CompleteReply
POST /abort        TicketBody   -> 204
POST /commit       CommitBody   -> CommitReply
POST /files        FilesBody    -> FilesReply     an editor's files, unattached ones included: processing state and progress
POST /commit-slot            SlotBody         -> SlotManifest
POST /commit-slot-from-file  SlotFromFileBody -> SlotManifest
POST /edit-slot              SlotEditBody     -> SlotManifest   re-edit the committed original
POST /slot                   SlotRefBody      -> SlotManifest
POST /video-images   VideoImagesBody  -> VideoImages   poster, with selections
POST /video-poster   VideoPosterBody  -> VideoImages
GET  /frame?kind=&id=&version=&file=&t=&w= -> image/jpeg   poster picker frame (UploadOptions.Frames)

func WaitFor added in v0.43.0

func WaitFor(ctx context.Context, c *river.Client[pgx.Tx], id int64) error

WaitFor snoozes a follow-up (InsertOnce) while the job it follows still runs, so jobs for the same inputs do not overlap. The client is the one running the job (river.ClientFromContext).

Types

type Animation added in v0.39.0

type Animation string

Animation is a policy for animated images (GIF, WebP; AVIF/HEIF sequences are refused as animation_unsupported, never flattened).

const (
	// AnimationAllow keeps every frame, delay and the loop count in each
	// rendition; edits and resizes apply per frame. The default.
	AnimationAllow Animation = ""
	// AnimationReject refuses an animated upload with animation_not_allowed.
	AnimationReject Animation = "reject"
)

type Aspect added in v0.40.0

type Aspect struct{ W, H int }

Aspect is a width:height ratio in lowest terms, written "W:H" ("3:1", "9:16"). The zero value is AspectNative: the source's own shape.

func AspectOf added in v0.40.0

func AspectOf(w, h int) Aspect

AspectOf is the ratio of a w×h size, reduced (native for an empty size).

func ParseAspect added in v0.40.0

func ParseAspect(s string) (Aspect, error)

ParseAspect parses "W:H" with positive integer terms up to 10000 and reduces it ("6:2" is "3:1"); "" and "native" are AspectNative.

func Ratio added in v0.40.0

func Ratio(s string) Aspect

Ratio parses "W:H" and panics on an invalid one (for constants in code).

func (Aspect) Height added in v0.40.0

func (a Aspect) Height(width int) int

Height is width's height at a, rounded half up, at least 1 (0 when native).

func (Aspect) MarshalText added in v0.40.0

func (a Aspect) MarshalText() ([]byte, error)

MarshalText writes "W:H" (native: empty), so JSON and config carry the string.

func (Aspect) Native added in v0.40.0

func (a Aspect) Native() bool

Native reports the source's own shape.

func (Aspect) Rotated added in v0.40.0

func (a Aspect) Rotated() Aspect

Rotated is a turned a quarter (90 or 270 degrees).

func (Aspect) String added in v0.40.0

func (a Aspect) String() string

func (*Aspect) UnmarshalText added in v0.40.0

func (a *Aspect) UnmarshalText(b []byte) error

func (Aspect) Valid added in v0.40.0

func (a Aspect) Valid() bool

Valid reports a native or reduced, bounded ratio.

func (Aspect) Width added in v0.40.0

func (a Aspect) Width(height int) int

Width is height's width at a, rounded half up, at least 1 (0 when native).

type Audio added in v0.56.0

type Audio struct {
	// Loudness normalizes each file to this integrated loudness in LUFS
	// (EBU R128 two-pass linear loudnorm, true peak at most -1.5 dBTP), e.g.
	// -16; 0 keeps the source's level.
	Loudness float64 `json:"loudness,omitempty"`
}

Audio configures a kind's audio files, encoded by the media worker (media/video) to AAC-LC at 128 kbit/s, 48 kHz stereo: a one-track HLS audio ladder (File.HLS.Audio, played through the master playlist) and a faststart M4A, the file's AudioVariant and its download AudioDownloadKey(file).

func (Audio) Validate added in v0.56.0

func (a Audio) Validate() error

Validate requires Loudness 0 or within -70..-5 LUFS.

type AudioTrack

type AudioTrack struct {
	ID        string    `json:"id"`
	Lang      string    `json:"lang,omitempty"`
	Label     string    `json:"label,omitempty"`
	Default   bool      `json:"default,omitempty"`
	Bandwidth int       `json:"bandwidth,omitempty"`
	Codecs    string    `json:"codecs,omitempty"`
	Blob      string    `json:"blob"`
	Segments  []Segment `json:"segments"`
}

type Capabilities

type Capabilities struct {
	ConditionalPut bool // If-Match / If-None-Match on PUT
	ChecksumSHA256 bool // x-amz-checksum-sha256 enforced on PUT
}

Capabilities are backend features the library depends on, established by Probe.

func Probe

func Probe(ctx context.Context, s Store, prefix string) (Capabilities, error)

Probe measures the backend's capabilities with scratch objects under prefix, which it removes. Results depend on the backend release (RGW vs MinIO). Every step must succeed or be refused cleanly (412, a checksum mismatch, 501); anything else (throttling, timeouts, a proxy's 403, cancellation) is an error and nothing is recorded, so a transient failure never reads as a missing capability.

type Codec added in v0.53.0

type Codec string

Codec is a video codec of a ladder: each rung is encoded in each codec the media worker is configured with.

const (
	CodecH264 Codec = "h264"
	CodecHEVC Codec = "hevc"
	CodecAV1  Codec = "av1"
)

type CommitBody

type CommitBody struct {
	Ref RefBody `json:"ref"`
	Ops []Op    `json:"ops"`
}

type CommitFile

type CommitFile struct {
	Name       string         `json:"name"`
	Original   string         `json:"original"`
	Type       string         `json:"type,omitempty"`
	Size       int64          `json:"size,omitempty"`
	Edit       *Edit          `json:"edit,omitempty"`
	Meta       map[string]any `json:"meta,omitempty"`
	Unattached bool           `json:"unattached,omitempty"`
}

type CommitReply

type CommitReply struct {
	Files []CommitFile `json:"files"`
}

CommitReply is the committed file order.

type CompleteReply

type CompleteReply struct {
	Name string `json:"name"`
	Type string `json:"type"`
	Size int64  `json:"size"`
}

type CopyOptions added in v0.40.0

type CopyOptions struct {
	IfMatch string
}

CopyOptions: IfMatch copies only while the source's ETag is this one (ErrPreconditionFailed otherwise).

type Crop added in v0.17.0

type Crop struct {
	X int `json:"x"`
	Y int `json:"y"`
	W int `json:"w"`
	H int `json:"h"`
}

Crop is a rectangle in source pixels.

type Deletion

type Deletion struct {
	Ref   contentref.ContentRef
	Owner string
}

Deletion is one item to delete. Owner is its quota owner (UploadGrant.Owner), "" for none; with a Limiter configured the owner's usage is released.

type Delivery

type Delivery struct {
	Mode DeliveryMode
	// BaseURL is the access worker origin for this site, e.g.
	// "https://media.doujins.com".
	BaseURL string
	// CookieDomain is the site's registrable domain, e.g. "doujins.com";
	// required in cookie mode, because a host-only cookie never reaches media.
	CookieDomain string
	// SigningKey is the current key of the ring the access worker verifies.
	SigningKey token.Key
	// TTL is the minimum token lifetime (default 1h); expiries round up to
	// Window (default token.DefaultWindow).
	TTL    time.Duration
	Window time.Duration
}

Delivery is the host's per-site delivery configuration.

type DeliveryMode

type DeliveryMode string

DeliveryMode is how viewers with full access present their token.

const (
	// DeliverCookie (default) returns plain URLs plus a folder cookie scoped to
	// the item's blobs/, so browsers cache immutable blobs normally.
	DeliverCookie DeliveryMode = "cookie"
	// DeliverURL appends ?t= to every URL: apps and clients without cookies.
	DeliverURL DeliveryMode = "url"
)

type Dims added in v0.17.0

type Dims struct {
	W int `json:"w"`
	H int `json:"h"`
}

Dims is a source's size with EXIF orientation applied.

func PosterFrameSize added in v0.26.0

func PosterFrameSize(poster Slot, w, h int) Dims

PosterFrameSize is the size of a grabbed poster frame from a w×h rendition: as is, or upscaled until it reaches the poster slot's Min() wide, so every poster has its smallest width. Poster edits are in these pixels.

type Download

type Download struct {
	Blob   string `json:"blob"`
	Type   string `json:"type,omitempty"`
	Size   int64  `json:"size,omitempty"`
	Spec   string `json:"spec,omitempty"`
	Inputs string `json:"inputs,omitempty"` // hash of the ordered input blobs
}

type DownloadInfo

type DownloadInfo struct {
	Key  string `json:"key"`
	Name string `json:"name"`
	Type string `json:"type,omitempty"`
	Size int64  `json:"size,omitempty"`
	URL  string `json:"url"`
}

type Edit added in v0.17.0

type Edit struct {
	Crop   *Crop `json:"crop,omitempty"`
	Rotate int   `json:"rotate,omitempty"` // clockwise degrees: 0, 90, 180 or 270
}

Edit is a non-destructive image edit: Crop, in the source's pixels (EXIF orientation applied), then a clockwise Rotate. Variants derive from the source through it; the source is never modified.

func (*Edit) Check added in v0.17.0

func (e *Edit) Check(w, h int) error

Check validates the edit's shape and, with a known size (w, h > 0), that the crop lies inside it.

func (*Edit) Hash added in v0.17.0

func (e *Edit) Hash() string

Hash is the edit's stable identity; "" for no edit.

func (*Edit) Normalize added in v0.17.0

func (e *Edit) Normalize() *Edit

Normalize returns nil for an identity edit, else a copy.

func (*Edit) Size added in v0.17.0

func (e *Edit) Size(w, h int) (int, int)

Size is the edited size of a w×h source.

type EncodeProgress added in v0.29.0

type EncodeProgress struct {
	Phase         string  `json:"phase"`
	QueuePosition int     `json:"queue_position,omitempty"` // 1 = next; 0 = unknown or behind a file of the same job
	SegmentsDone  int     `json:"segments_done,omitempty"`
	SegmentsTotal int     `json:"segments_total,omitempty"`
	Percent       float64 `json:"percent"`
	Speed         float64 `json:"speed,omitempty"`
	ETA           float64 `json:"eta,omitempty"`
	At            int64   `json:"at"` // unix milliseconds of the measurement
	Stalled       bool    `json:"stalled,omitempty"`
	// Stage of Stages: each rung is published before the next one starts.
	Stage  int `json:"stage,omitempty"`
	Stages int `json:"stages,omitempty"`
}

EncodeProgress is a pending video file's live encode state. Segments count HLS segments of the source's duration; Speed is the encode's smoothed ×realtime; ETA is seconds from At to publish, including uploads. Percent never decreases within a run. Stalled marks a report older than a minute from a running job (a dead worker until River rescues the job).

type EncodeStatus added in v0.29.0

type EncodeStatus struct {
	Files  map[string]EncodeProgress
	Queued *EncodeProgress
	Item   *EncodeProgress
}

EncodeStatus is an item's encode progress: Files from the running job, Queued for pending files it does not cover (a waiting job), Item for the job's item-wide step (PhaseImages).

func (EncodeStatus) Current added in v0.29.0

func (s EncodeStatus) Current() *EncodeProgress

Current is the item's most advanced step: the item-wide one, a running file, else the wait.

type ErrorDetails added in v0.38.0

type ErrorDetails struct {
	Width      int      `json:"width,omitempty"`       // image_too_small: the edited width; image_too_large: the source's
	Height     int      `json:"height,omitempty"`      // image_too_large: the source's
	MinWidth   int      `json:"min_width,omitempty"`   // image_too_small
	MaxPixels  int      `json:"max_pixels,omitempty"`  // image_too_large
	Type       string   `json:"type,omitempty"`        // the declared type (image_unreadable, type_not_allowed, too_large)
	Allowed    []string `json:"allowed,omitempty"`     // type_not_allowed: the kind's types
	Size       int64    `json:"size,omitempty"`        // too_large: bytes
	MaxBytes   int64    `json:"max_bytes,omitempty"`   // too_large
	Frames     int      `json:"frames,omitempty"`      // animation_too_long, image_too_large: the animation's
	MaxFrames  int      `json:"max_frames,omitempty"`  // animation_too_long
	Seconds    float64  `json:"seconds,omitempty"`     // animation_too_long: running time
	MaxSeconds float64  `json:"max_seconds,omitempty"` // animation_too_long
}

ErrorDetails qualifies an image refusal so clients can state the rule.

type ErrorReply

type ErrorReply struct {
	Error      string        `json:"error"`
	Code       string        `json:"code"`
	RetryAfter int           `json:"retry_after,omitempty"` // seconds, with 429
	Originals  []string      `json:"originals,omitempty"`   // not_uploaded at commit: the originals to upload again
	Details    *ErrorDetails `json:"details,omitempty"`     // image refusals
}

ErrorReply is the error body; Code is one of the media Code* constants, "unauthorized" or "internal_error".

type File

type File struct {
	Name     string             `json:"name"`
	Original string             `json:"original"`
	Master   string             `json:"master,omitempty"`
	Type     string             `json:"type,omitempty"`
	Size     int64              `json:"size,omitempty"`
	Edit     *Edit              `json:"edit,omitempty"`
	Dims     *Dims              `json:"dims,omitempty"`
	Meta     map[string]any     `json:"meta,omitempty"`
	Variants map[string]Variant `json:"variants,omitempty"`
	HLS      *HLS               `json:"hls,omitempty"`
	// Failure is why the image processor cannot derive this source through
	// this edit (Of); a new source or edit clears it.
	Failure *FileFailure `json:"failure,omitempty"`
	// Derived is the FailureKey the image processor last derived every
	// variant for; the file's images are current while it matches.
	Derived string `json:"derived,omitempty"`
	// Unattached marks a file processed on upload (UploadOptions.ProcessOnUpload)
	// that is not part of the item yet: reads leave it out (editors may ask
	// for it) until an attach op. Removing it discards its objects at once.
	Unattached bool `json:"unattached,omitempty"`
}

File is one manifest entry. Original and Master live in originals/ (a multipart upload's Original is its temp/ "u-{uuid}" until the worker places it); variants, HLS and downloads in private/. Image variants derive from Source() through Edit; Dims is Source()'s size, recorded by processing, and edits are validated against it. meta w/h is the edited size.

func VideoFile added in v0.26.0

func VideoFile(m *Manifest, name string) (File, bool)

VideoFile is the named file when it is a video, or with name "" the first attached video file.

func (File) Failed added in v0.39.0

func (f File) Failed() *FileFailure

Failed is the file's failure for its current source and edit, or nil.

func (File) FailureKey added in v0.39.0

func (f File) FailureKey() string

FailureKey identifies the source and edit a Failure applies to.

func (File) Servable added in v0.50.0

func (f File) Servable() bool

Servable reports a file viewers may be shown: processed, or still serving the outputs of a source or edit being replaced (a stale ladder plays until its successor is promoted; old variants stay until new ones land). A file that has nothing complete to serve, or failed, is not.

func (File) Source

func (f File) Source() string

Source is the file variants derive from: Master when present, else Original.

func (File) State added in v0.50.0

func (f File) State() string

State is the file's processing state. A video is ready when its current source's ladder has every stage (no hls.pending) and failed on hls.error; audio when its current source's track is encoded, failed on hls.error; a subtitle when converted from its current source (Derived), failed on Failure; an image when its variants were derived from its current source and edit (Derived) and failed on Failure; a staged upload is processing. Other types need no processing.

func (File) Teaser

func (f File) Teaser() bool

Teaser reports meta.teaser, a file served to any viewer who can see the item.

type FileFailure added in v0.39.0

type FileFailure struct {
	Of      string        `json:"of"`             // File.FailureKey it was recorded for
	Message string        `json:"message"`        // what went wrong, or the rule an ImageError states
	Code    string        `json:"code,omitempty"` // an ImageError's code
	Details *ErrorDetails `json:"details,omitempty"`
}

FileFailure is a file's permanent processing failure.

func NewFileFailure added in v0.39.0

func NewFileFailure(f File, err error) *FileFailure

NewFileFailure records err for f: an ImageError keeps its code and details.

type FileInfo

type FileInfo struct {
	Index    int     `json:"index"`
	Name     string  `json:"name,omitempty"`
	Type     string  `json:"type,omitempty"`
	Width    int     `json:"w,omitempty"`
	Height   int     `json:"h,omitempty"`
	Duration float64 `json:"duration,omitempty"`
	Edit     *Edit   `json:"edit,omitempty"` // editors only: with Dims, what re-cropping needs
	Dims     *Dims   `json:"dims,omitempty"` // editors only: the source's size; w/h is the edited size
	Teaser   bool    `json:"teaser,omitempty"`
	Locked   bool    `json:"locked,omitempty"`
	HLS      bool    `json:"hls,omitempty"` // playable at .../hls/{file}/master.m3u8 (video, or audio only)
	// Ready: the file is processed (File.State): encoded, derived or converted.
	Ready bool `json:"ready,omitempty"`
	// Unattached is an editor's file processed on upload, not yet attached
	// (ReadOptions.Unattached).
	Unattached bool   `json:"unattached,omitempty"`
	Failed     string `json:"failed,omitempty"` // editors only: why the file cannot be processed (video encode, image derive)
	// FailedCode and FailedDetails type an image refusal (image_too_large,
	// animation_not_allowed, …); editors only.
	FailedCode    string        `json:"failed_code,omitempty"`
	FailedDetails *ErrorDetails `json:"failed_details,omitempty"`
	// Progress of a pending encode (none yet, or a replaced source); served
	// with the file, as it reveals only timing and queue depth.
	Progress *EncodeProgress `json:"progress,omitempty"`
	Variant  string          `json:"variant,omitempty"`
	URL      string          `json:"url,omitempty"`
}

type FilesBody added in v0.43.0

type FilesBody struct {
	Ref   RefBody  `json:"ref"`
	Names []string `json:"names,omitempty"`
}

FilesBody names files of an item; empty names lists them all.

type FilesReply added in v0.43.0

type FilesReply struct {
	Files []FileInfo `json:"files"`
}

FilesReply is the named files as an editor reads them (unattached ones included): dimensions once derived, hls once encoded, failed, progress.

type Fit

type Fit string

Fit is how an image spec fits its box.

const (
	FitInside Fit = "inside"
	FitCover  Fit = "cover"
)

type FolderNotEmptyError added in v0.45.0

type FolderNotEmptyError struct {
	Prefix string
	Keys   []string
}

FolderNotEmptyError names the folder and a few of its keys.

func (*FolderNotEmptyError) Error added in v0.45.0

func (e *FolderNotEmptyError) Error() string

func (*FolderNotEmptyError) Unwrap added in v0.45.0

func (e *FolderNotEmptyError) Unwrap() error

type FrameGrabber added in v0.26.0

type FrameGrabber interface {
	Frame(ctx context.Context, item Item, f File, t float64, width int) ([]byte, error)
}

FrameGrabber renders a small JPEG of an encoded video file's frame for the poster picker (media/video.Frames).

type GetOptions

type GetOptions struct {
	IfNoneMatch string
	Range       string
}

GetOptions: IfNoneMatch returns ErrNotModified on a match; Range is an HTTP Range value.

type Grant

type Grant struct {
	Item       Item
	Resolution access.Resolution
	Manifest   *Manifest
	Expires    time.Time
	// contains filtered or unexported fields
}

Grant is one viewer's resolved access to one item or version: the read API and HLS playlists sign every URL through it.

func (*Grant) Allowed

func (g *Grant) Allowed(i int) bool

Allowed reports whether file i is served to this viewer: within the preview cut, or a teaser of a visible item.

func (*Grant) AudioPlaylist

func (g *Grant) AudioPlaylist(file, id string) ([]byte, error)

AudioPlaylist is the byte-range media playlist of one audio track.

func (*Grant) Cookie

func (g *Grant) Cookie() *http.Cookie

Cookie is the folder cookie to set in cookie mode with full access, else nil.

func (*Grant) DownloadURL

func (g *Grant) DownloadURL(ctx context.Context, key string) (name, u string, err error)

DownloadURL signs a manifest download under its display name; full access only, and always a URL token because the worker must see the signed dl=.

func (*Grant) Editor added in v0.19.0

func (g *Grant) Editor() bool

Editor reports an editor's grant: editor views are signed.

func (*Grant) Full

func (g *Grant) Full() bool

Full reports full access: one folder token covers every blob.

func (*Grant) MasterPlaylist

func (g *Grant) MasterPlaylist(file string, o MasterOptions) ([]byte, error)

MasterPlaylist is the multivariant playlist of file: one variant per video rendition (rung and codec), with alternative audio and subtitle groups. Codecs are listed in the ladder's order, so a player that decodes the first starts on it; players drop variants whose CODECS they cannot decode. Subtitles are the source's tracks, then its sidecar subtitle files. An audio file's is one audio-only variant over its track.

func (*Grant) SpriteVTT

func (g *Grant) SpriteVTT(file string) ([]byte, error)

SpriteVTT is the seek-preview track: one cue per sprite tile, each pointing at its tile with a #xywh fragment.

func (*Grant) SubtitlePlaylist

func (g *Grant) SubtitlePlaylist(file, id string) ([]byte, error)

SubtitlePlaylist is a one-segment playlist over the whole WebVTT blob of one of the file's tracks (its source's or a sidecar's).

func (*Grant) URL

func (g *Grant) URL(i int, blob string) (string, error)

URL signs rendition blob of file i: plain in cookie mode with full access, the folder token in URL mode, else a token for exactly that key.

func (*Grant) VideoPlaylist

func (g *Grant) VideoPlaylist(file string, rung int, codec Codec) ([]byte, error)

VideoPlaylist is the byte-range media playlist of one video rung in codec.

type HLS

type HLS struct {
	Source string       `json:"source"`
	Spec   string       `json:"spec,omitempty"`
	Error  string       `json:"error,omitempty"`
	Video  []Rendition  `json:"video,omitempty"`
	Audio  []AudioTrack `json:"audio,omitempty"`
	Subs   []Subtitle   `json:"subs,omitempty"`
	// SubsSpec is the conversion of the source's text tracks in Subs: a new
	// one re-extracts them without re-encoding the ladder.
	SubsSpec string  `json:"subs_spec,omitempty"`
	Sprite   *Sprite `json:"sprite,omitempty"`
	// Pending lists the rungs of later encode stages, smallest first: the
	// file plays at the rungs in Video until they are added.
	Pending []int `json:"pending,omitempty"`
}

HLS is a byte-range ladder: each rendition is one fMP4 blob. Source is the original it was encoded from; when it differs from the file's, the ladder is stale but still served until its replacement is promoted. Error, with no renditions, records why Source can never be encoded.

type HandlerOptions

type HandlerOptions struct {
	Tenant   string
	Identity Identity
	Logger   *slog.Logger
	// Limit is the per-viewer rate limit (default ViewerLimit{}: 2/s, burst
	// 120, per process; set Limit.Redis to share it across replicas).
	// Viewers are keyed by tenant and Actor.ID, anonymous ones by Actor.IP,
	// else by the connection's address: behind a proxy, set Actor.IP from
	// the client address the proxy forwards.
	Limit ViewerLimit
}

HandlerOptions scope the read API to one tenant.

type Hooks

type Hooks struct {
	// DownloadName returns the display name a download is saved under, e.g.
	// "[Artist] Title (English).zip". Default: "{content_id}-{key}{ext}".
	DownloadName func(ctx context.Context, ref contentref.ContentRef, key string, d Download) (string, error)
	// Failed reports a file a processor cannot derive (an undecodable image,
	// say); file is the manifest file name, or the slot name. The job does not
	// retry it; a new commit does.
	Failed func(ctx context.Context, ref contentref.ContentRef, file string, err error)
	// SlotEncoded reports a slot's new outputs: hosts store the listing to
	// list the slot with Reader.ListedSlot without reads.
	SlotEncoded func(ctx context.Context, ref contentref.ContentRef, slot string, l SlotListing)
	// PublicRemoved reports public/ keys deleted (a hidden item, replaced
	// outputs), for a CDN purge; optional.
	PublicRemoved func(ctx context.Context, ref contentref.ContentRef, keys []string)
	// ItemReady reports an item whose processing settled (Readiness ready,
	// or failed with nothing still processing) after a media worker job, in a
	// transaction on the host database (worker.Config.Pool), e.g. to publish
	// it and enqueue HostQueue.ExposeTx. It runs after every job that leaves
	// the item settled, so it must be idempotent; an error rolls back and
	// retries the job.
	ItemReady func(ctx context.Context, tx pgx.Tx, ref contentref.ContentRef, r Readiness) error
}

Hooks are optional host callbacks.

type HostQueue added in v0.43.0

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

HostQueue is the media worker's handle on the host's River schema: it inserts the jobs the host runs on the worker's behalf: a folder's sweep after the worker edits a manifest. It inserts only.

func NewHostQueue added in v0.43.0

func NewHostQueue(pool *pgxpool.Pool, kinds *Registry, schema, queue string, grace time.Duration) (*HostQueue, error)

NewHostQueue targets the host's River schema ("" is the connection's search path) and media queue ("" is DefaultQueue); grace is the host's JobsConfig.Grace (default 24 h).

func (*HostQueue) ExposeTx added in v0.50.0

func (h *HostQueue) ExposeTx(ctx context.Context, tx pgx.Tx, refs ...contentref.ContentRef) error

ExposeTx enqueues the host's Expose of refs in tx, a transaction on the host database, like Jobs.ExposeTx: Hooks.ItemReady making an item visible.

func (*HostQueue) ScheduleSweep added in v0.43.0

func (h *HostQueue) ScheduleSweep(ctx context.Context, ref contentref.ContentRef) error

ScheduleSweep implements SweepScheduler like Jobs.ScheduleSweep.

type Identity

type Identity interface {
	Actor(ctx context.Context) (access.Actor, bool)
}

Identity reads the authenticated actor the host's middleware put in the context; the same shape as content.Identity.

type ImageError added in v0.38.0

type ImageError struct {
	Code    string
	Message string
	Details ErrorDetails
}

ImageError is an image the rules refuse, synchronously (an edit checked against known dims) or in the image job (recorded on the slot result).

func AsImageError added in v0.38.0

func AsImageError(err error) *ImageError

AsImageError returns the refusal in err's chain, or nil.

func (*ImageError) Error added in v0.38.0

func (e *ImageError) Error() string

type IngestRequest added in v0.18.0

type IngestRequest struct {
	Ref  contentref.ContentRef
	Name string
	Type string
	Body io.Reader // read once, sequentially
	Size int64     // expected size, checked when > 0; required when the actor is rate- or quota-limited
	Op   string    // OpInsert (default) or OpReplace
	Meta map[string]any

	// Resume continues a multipart upload a previous Ingest left behind:
	// parts already stored with the same bytes are not uploaded again (the
	// body is still read and hashed). An unknown upload starts afresh.
	Resume *IngestUpload
	// OnUpload receives the multipart upload once created, for the host to
	// persist for Resume. With it set a failed Ingest keeps the upload (the
	// bucket's abort-incomplete rule removes it after a day); without it the
	// upload is aborted.
	OnUpload func(IngestUpload) error

	PartSize    int64 // default IngestPartSize; at least 5 MiB
	Concurrency int   // parallel part uploads; default IngestConcurrency
}

IngestRequest streams one file of unknown or huge size from the host (a server-side import, not a browser upload) into Ref's originals and commits it as Name.

type IngestResult added in v0.18.0

type IngestResult struct {
	Original string
	Size     int64
	SHA256   []byte // of the whole file
	Manifest *Manifest
}

IngestResult is the committed original.

type IngestUpload added in v0.18.0

type IngestUpload struct {
	Original string `json:"original"` // u-{uuid}
	UploadID string `json:"upload_id"`
}

IngestUpload identifies an in-progress multipart ingest.

type InsertFunc added in v0.43.0

type InsertFunc func(ctx context.Context, args river.JobArgs, o *river.InsertOpts) (*rivertype.JobInsertResult, error)

InsertFunc inserts one job (a River client's Insert, or InsertTx bound to a transaction).

type Item

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

Item is a validated content item and the keys of its folder (see media/layout):

{tenant}/{kind}/{content_id}/manifest.json
                            /originals/sha256-{hex}
                            /temp/u-{uuid}, temp/e-{hex}
                            /private/sha256-{hex}
                            /public/sha256-{hex}

func (Item) EditorView added in v0.52.0

func (i Item) EditorView(source string) string

EditorView is the key of source's editor view (Kind.Editor), temp/e-{hex} keyed by the source and the spec, or "" when the kind has none or source is not a placed original.

func (Item) Inline added in v0.20.0

func (i Item) Inline(name string) bool

Inline reports whether name is an inline image id the kind accepts.

func (Item) Kind

func (i Item) Kind() Kind

func (Item) ManifestKey

func (i Item) ManifestKey() string

ManifestKey is the folder's manifest.json; versions are sections of it.

func (Item) Original

func (i Item) Original(name string) (string, error)

Original is the key of an uploaded file (never served): originals/sha256-{hex}, or temp/u-{uuid} for a multipart upload the worker has not placed yet.

func (Item) OriginalsPrefix

func (i Item) OriginalsPrefix() string

func (Item) Poster added in v0.37.0

func (i Item) Poster() Slot

Poster is the item's poster slot (zero for non-video kinds).

func (Item) Prefix

func (i Item) Prefix() string

Prefix is the folder, "{tenant}/{kind}/{id}/"; versions share it.

func (Item) Private added in v0.48.0

func (i Item) Private(name string) (string, error)

Private is the key of a rendition: private/sha256-{hex}, token-gated.

func (Item) PrivatePrefix added in v0.48.0

func (i Item) PrivatePrefix() string

func (Item) Public

func (i Item) Public(name string) (string, error)

Public is the key of an exposed rendition's copy: public/sha256-{hex}.

func (Item) PublicPrefix

func (i Item) PublicPrefix() string

func (Item) Ref

func (i Item) Ref() contentref.ContentRef

func (Item) Section added in v0.48.0

func (i Item) Section() (string, error)

Section is the manifest section the ref's files live in: its version for a versioned kind (required), "" otherwise.

func (Item) TempPrefix added in v0.52.0

func (i Item) TempPrefix() string

type Jobs

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

Jobs is media's River contribution to the host: sweep, folder deletion and publishing. Processing runs in the media worker (media/worker). Compose RiverJobs once into the host client.

func NewJobs

func NewJobs(cfg JobsConfig) (*Jobs, error)

func (*Jobs) DeleteItemsTx

func (j *Jobs) DeleteItemsTx(ctx context.Context, tx pgx.Tx, items ...Deletion) error

DeleteItemsTx deletes each item's whole folder (every version) through a job enqueued in the host's delete transaction. A second pass after LateUploadWindow removes uploads that land after the first. The quota to release is measured here from the item's manifests, so a retried job settles it once.

func (*Jobs) EraseUserTx

func (j *Jobs) EraseUserTx(ctx context.Context, tx pgx.Tx, tenant, userID string, items ...Deletion) error

EraseUserTx erases a user's media: the items the host maps to them plus their user folder, {tenant}/user/{id}/.

func (*Jobs) Expose added in v0.48.0

func (j *Jobs) Expose(ctx context.Context, ref contentref.ContentRef) error

Expose brings an item's public/ copies to its visibility: it resolves the item for an anonymous actor (JobsConfig.Resolver), records Hidden when anonymous viewers cannot see it, and syncs public/ (SyncPublic): a hidden item's copies are deleted at once and reported to Hooks.PublicRemoved; an unhidden item's are copied back. It re-resolves after writing and repeats until the state holds, so an Expose racing a visibility change ends at the newer one. An item without a manifest is left alone.

func (*Jobs) ExposeTx added in v0.48.0

func (j *Jobs) ExposeTx(ctx context.Context, tx pgx.Tx, refs ...contentref.ContentRef) error

ExposeTx enqueues Expose for each item in the host's transaction. Call it whenever whether anonymous viewers may see an item changes: create (a draft), publish, unpublish, hide, soft delete, restore.

func (*Jobs) Insert

Insert enqueues a job on the bound client; an empty queue means Queue().

func (*Jobs) InsertTx

func (j *Jobs) InsertTx(ctx context.Context, tx pgx.Tx, args river.JobArgs, o *river.InsertOpts) (*rivertype.JobInsertResult, error)

InsertTx enqueues a job in the host's transaction.

func (*Jobs) Purge added in v0.45.0

func (j *Jobs) Purge(ctx context.Context, d Deletion) error

Purge deletes an item's whole folder (every version) now: the explicit reset before deliberately recreating an item, or an operator cleanup. It releases the owner's quota for the folder's manifests when Owner is set. Hosts deleting content use DeleteItemsTx.

func (*Jobs) Queue

func (j *Jobs) Queue() string

Queue is the shared media queue; registered workers may use it or add their own.

func (*Jobs) Register

func (j *Jobs) Register(fn func(*river.Config) error) error

Register adds workers, queues or periodic jobs to the contribution. Media packages (image variants, video) call it before RiverJobs is composed.

func (*Jobs) RiverJobs

func (j *Jobs) RiverJobs() riverhelpers.Contribution

RiverJobs contributes media's workers, queue and periodic sweep to the host's helpers/river composition. It composes once.

func (*Jobs) ScheduleSweep

func (j *Jobs) ScheduleSweep(ctx context.Context, ref contentref.ContentRef) error

ScheduleSweep sweeps the item's folder after the grace period; a sweep already waiting for the folder absorbs it. Manifests calls it on every edit.

func (*Jobs) Sweep

func (j *Jobs) Sweep(ctx context.Context, ref contentref.ContentRef) (SweepResult, error)

Sweep deletes what the item's manifest does not keep: originals/, private/ and public/ objects outside its index once the manifest is older than the grace period, and temp/ at any time: editor views past JobsConfig.EditorTTL and staged uploads no file references past JobsConfig.TempUploadTTL. Only objects past their retention (deleteAt) go. Removed public/ keys go to Hooks.PublicRemoved.

Invariant: the sweep deletes only objects the manifest does not reference and that no in-flight commit or job can newly reference. Uploads keeps the second half: presign reuses an existing original, and a commit accepts one, only while it is referenced or well before deleteAt (see protected); jobs write renditions before the edit that lists them, well within grace. Nothing references an editor view: it is rendered again when missing.

func (*Jobs) SweepAll

func (j *Jobs) SweepAll(ctx context.Context) error

SweepAll sweeps every folder of the configured tenants whose kind is registered: the periodic backstop for missed schedules and abandoned uploads.

func (*Jobs) SweepOrphans added in v0.45.0

func (j *Jobs) SweepOrphans(ctx context.Context, s OrphanSweep) (OrphanReport, error)

SweepOrphans lists the kind's folders in a tenant and reports, or with Delete removes, those the host says do not exist, once past the grace period. The host runs it (a command or a periodic job): only it knows which items exist.

type JobsConfig

type JobsConfig struct {
	Store Store
	Kinds *Registry
	// Tenants are the folders the periodic sweep pass covers; folders of
	// kinds missing from Kinds are skipped.
	Tenants []string
	// Grace protects in-flight uploads, jobs and mid-stream viewers: a folder
	// is swept only when its manifests are this old, and only objects this old
	// are deleted. Default 24 h.
	Grace time.Duration
	// TempUploadTTL is how long a staged upload (temp/u-) no file references
	// is kept. Keep it above the bucket's AbortIncompleteMultipartUpload age
	// (1 day) plus the longest commit delay: backends may date a completed
	// multipart object at its initiation. Default 48 h.
	TempUploadTTL time.Duration
	// EditorTTL is how long an editor view (temp/e-) is kept; an editor's
	// read renders a swept one again. Default 7 days.
	EditorTTL time.Duration
	// SweepInterval is the periodic pass interval. Default 24 h.
	SweepInterval time.Duration
	// LateUploadWindow delays the second pass of a folder deletion, which
	// removes PUTs and multipart completions that land after the first. It
	// must exceed the longest upload presign TTL and the 1-day multipart
	// abort rule. Default 25 h.
	LateUploadWindow time.Duration
	Limiter          UploadLimiter // releases a deleted item's quota; optional
	// Locker serializes Expose's manifest edits (ManifestOptions.Locker); required.
	Locker Locker
	// Resolver decides, with an anonymous actor, whether an item is hidden
	// (Expose); required for Expose.
	Resolver access.ContentResolver
	// Hooks.PublicRemoved hears of deleted public/ keys (CDN purge).
	Hooks      Hooks
	Queue      string // default "contentkit_media"
	MaxWorkers int    // default 2
	Logger     *slog.Logger
	Now        func() time.Time // clock for grace decisions; default time.Now
}

JobsConfig configures media's River jobs.

type Kind

type Kind struct {
	Name      string
	Versioned bool // manifests live at manifests/{version_id}.json
	// Types are the accepted content types (empty: any); image processing
	// also requires the bytes to be the declared format.
	Types    []string
	MaxBytes int64
	// MaxFiles caps a manifest's files; 0 is unlimited.
	MaxFiles int
	// TypeLimits are caps per top-level type ("image", "video"): a set
	// MaxBytes replaces the kind's for that type, and MaxFiles caps that
	// type's files. A kind may mix images (Specs) and videos (Video).
	TypeLimits map[string]Limit
	Specs      map[string]Spec // variant name → spec ("editor" is reserved)
	Slots      map[string]Slot // public slot name → outputs
	// Editor is the editor view: the whole source (EXIF-oriented, ignoring
	// crop and rotate) the cropper draws on, for image files and slot
	// originals. It is an input-keyed cache in temp/ (Item.EditorView), never
	// in the manifest: the image job renders it, the sweep deletes it after
	// JobsConfig.EditorTTL, and an editor's read renders it again. Only
	// editors get its URL, under an editor token. nil: no editor views.
	Editor *Spec
	// Inline enables inline images: write-once public images with random ids
	// ("i-{uuid}", from NewInlineName), each re-encoded with this spec from
	// originals/{id} to public/{id}.webp. Post bodies and poll options use them.
	Inline *Spec
	// Animation is the policy for animated images (GIF, WebP) in files and
	// inline images; slots set their own.
	Animation Animation
	Video     *Video // nil: no video encoding
	Audio     *Audio // nil: no audio encoding; required to accept audio/ types
	// Zip names the variant packed, in file order, into downloads["zip"];
	// "" offers no zip.
	Zip string
}

Kind is a host's per-kind rule set, registered once at startup.

func (Kind) Allows

func (k Kind) Allows(contentType string, size int64) error

Allows checks a file against the kind's types and size cap.

type Limit added in v0.17.0

type Limit struct {
	MaxBytes int64
	MaxFiles int
}

Limit is a per-type cap; zero values are unlimited.

type Locker

type Locker interface {
	Lock(ctx context.Context, key string) (unlock func(), err error)
}

Locker serializes manifest edits across every process sharing the bucket.

func PGLocker

func PGLocker(pool *pgxpool.Pool) Locker

PGLocker serializes manifest edits with a per-manifest session advisory lock in the host database: the fallback for backends without conditional PUT. The lock holds its own connection, never one of pool's, so an edit may use the pool (quota settlement) without starving it.

type Manifest

type Manifest struct {
	Files     []File              `json:"files"`
	Meta      map[string]any      `json:"meta,omitempty"`
	Downloads map[string]Download `json:"downloads,omitempty"`
}

Manifest is the ordered file list of an item or of one version (a section of the item's Root). List order is display order. References are names within the item's folder.

func (*Manifest) Attached added in v0.43.0

func (m *Manifest) Attached() *Manifest

Attached is the manifest without its unattached files and their downloads, as readers see it; m itself when it has none.

func (*Manifest) File

func (m *Manifest) File(name string) int

File returns the index of the named file, or -1.

func (*Manifest) OriginalBytes

func (m *Manifest) OriginalBytes() int64

OriginalBytes is the storage charged for a manifest: the sizes of its distinct originals. Deleting or erasing an item releases it.

func (*Manifest) Readiness added in v0.50.0

func (m *Manifest) Readiness() Readiness

Readiness is the attached files' readiness.

func (*Manifest) Renditions added in v0.48.0

func (m *Manifest) Renditions() []string

Renditions returns every private/ name the manifest references.

func (*Manifest) Servable added in v0.50.0

func (m *Manifest) Servable() *Manifest

Servable is the manifest without the files viewers may not be shown (File.Servable) and their video downloads, as non-editors read it; m itself when every file is servable.

func (*Manifest) Sources added in v0.48.0

func (m *Manifest) Sources() []string

Sources returns every uploaded file's name the manifest references (originals/ hashes and staged "u-{uuid}" names).

func (*Manifest) Validate

func (m *Manifest) Validate() error

Validate requires unique, non-empty file names and well-formed references.

type ManifestOptions

type ManifestOptions struct {
	// Locker is required: every edit runs under it (so processes that have
	// and have not probed the store never diverge), and also writes with
	// If-Match once the store reports ConditionalPut. See PGLocker.
	Locker     Locker
	CacheSize  int // manifests kept in process, revalidated by ETag; default 4096
	MaxRetries int // CAS attempts per edit; default 16
	// Sweeps, when set, schedules the folder's sweep after every written
	// edit: the host's *Jobs, or in the media worker a *HostQueue. Scheduling
	// is best-effort (logged); the periodic sweep pass backs it up.
	Sweeps SweepScheduler
}

ManifestOptions configure Manifests.

type Manifests

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

Manifests reads and edits item manifests.

func NewManifests

func NewManifests(store Store, kinds *Registry, opts ManifestOptions) (*Manifests, error)

func (*Manifests) Create added in v0.45.0

func (m *Manifests) Create(ctx context.Context, ref contentref.ContentRef) (*Manifest, error)

Create starts a new item: it writes the item's empty manifest, and fails with ErrFolderNotEmpty if the folder already holds any object (a manifest, an upload, an output). Hosts call it when they create the item's row, so a reused id surfaces there instead of showing another item's files.

func (*Manifests) Edit

func (m *Manifests) Edit(ctx context.Context, ref contentref.ContentRef, fn func(*Manifest) error) (*Manifest, error)

Edit applies fn to ref's files (see Get; a new version starts empty) like EditRoot.

func (*Manifests) EditRoot added in v0.48.0

func (m *Manifests) EditRoot(ctx context.Context, ref contentref.ContentRef, fn func(*Root) error) (*Root, error)

EditRoot applies fn to the item's manifest (empty if none) and writes it with If-Match on the ETag it read (If-None-Match for a new one), re-reading and re-applying fn on conflict. fn must be safe to run more than once; an error from fn aborts the edit. The index is rebuilt; an unchanged manifest is not written. A folder's first manifest is refused (ErrFolderNotEmpty) over a previous item's renditions.

func (*Manifests) Get

Get returns ref's files (its version's section for a versioned kind) and the manifest's ETag, or ErrNotFound. Cached copies are revalidated with a conditional GET, so a read is never stale.

func (*Manifests) Place added in v0.40.0

func (m *Manifests) Place(ctx context.Context, ref contentref.ContentRef, s Staged) (string, error)

Place moves a staged upload (temp/u-{uuid}) to its content address, originals/sha256-{hex}, and returns that name. The media worker calls it with the hash it computed while reading the upload for processing:

  1. copy temp → originals server-side, unless the folder already holds the hash (dedupe), and verify the copy's size (and full-object SHA-256 when the store reports one);
  2. rename every reference in the manifest (original, master, hls source, download inputs) in one conditional edit;
  3. delete the staged object.

Each step is idempotent and the manifests switch only after a verified copy, so a crash anywhere converges on the next run; the sweep removes a staged object left unreferenced.

func (*Manifests) Readiness added in v0.50.0

func (m *Manifests) Readiness(ctx context.Context, ref contentref.ContentRef) (Readiness, error)

Readiness reads the item's readiness (Root.Readiness); ErrNotFound when it has no manifest.

func (*Manifests) Root added in v0.48.0

func (m *Manifests) Root(ctx context.Context, ref contentref.ContentRef) (*Root, string, error)

Root returns the item's manifest.json and its ETag, or ErrNotFound.

func (*Manifests) Slot added in v0.20.0

func (m *Manifests) Slot(ctx context.Context, ref contentref.ContentRef, slot string) (*SlotRecord, error)

Slot returns a slot's record, or ErrNotFound before its first commit.

func (*Manifests) SlotManifest added in v0.20.0

func (m *Manifests) SlotManifest(ctx context.Context, urls OutputURLs, ref contentref.ContentRef, slot string) (SlotManifest, error)

SlotManifest reads a slot (or inline image) from the item's manifest, building output URLs with urls. A slot never committed has no outputs.

func (*Manifests) SyncPublic added in v0.48.0

func (m *Manifests) SyncPublic(ctx context.Context, ref contentref.ContentRef) ([]string, error)

SyncPublic makes public/ match the manifest: every exposed rendition (the slot and inline outputs of an item that is not hidden) is copied from private/ under the same name, and a hidden item's public/ is emptied at once. It returns the deleted keys. A visible item's unlisted copies (older outputs) are left to the sweep, so pages rendered a moment ago still load.

func (*Manifests) UpdateSlot added in v0.20.0

func (m *Manifests) UpdateSlot(ctx context.Context, ref contentref.ContentRef, slot string, fn func(*SlotRecord) error) error

UpdateSlot applies fn to the slot record (zero before the first commit) in one EditRoot; a record left zero is not added.

func (*Manifests) VideoImages added in v0.26.0

func (m *Manifests) VideoImages(ctx context.Context, urls OutputURLs, ref contentref.ContentRef, uploader bool, file string) (VideoImages, error)

VideoImages builds output URLs with urls. With uploader set it adds the selections and, when ref addresses a manifest, the video file (file "" is the first).

type MasterOptions

type MasterOptions struct {
	Audio, Subs []string
}

MasterOptions filter a master playlist's renditions by id or BCP 47 tag; nil keeps every track, an empty non-nil slice none.

type Multipart

type Multipart struct {
	Ticket      string
	MinPartSize int64
	MaxPartSize int64
	MaxParts    int
}

Multipart carries the opaque ticket for PresignParts, ListParts, Complete and Abort, and the part-size bounds the client adapts within.

type MultipartReply

type MultipartReply struct {
	Ticket      string `json:"ticket"`
	MinPartSize int64  `json:"min_part_size"`
	MaxPartSize int64  `json:"max_part_size"`
	MaxParts    int    `json:"max_parts"`
}

type Object

type Object struct {
	Key            string
	Size           int64
	ETag           string
	ContentType    string
	CacheControl   string
	ContentRange   string
	LastModified   time.Time
	ChecksumSHA256 []byte            // set when the backend stored a full-object SHA-256
	Metadata       map[string]string // user metadata (x-amz-meta-*), lower-case keys; Head and Get
}

Object is object metadata. For a ranged Get, Size is the body length and ContentRange is set.

type Op

type Op struct {
	Op       string         `json:"op"`
	Name     string         `json:"name"`
	Original string         `json:"original,omitempty"`
	Index    *int           `json:"index,omitempty"`
	To       string         `json:"to,omitempty"`
	Meta     map[string]any `json:"meta,omitempty"` // insert, replace: the file's meta; attach: merged into it
	Edit     *Edit          `json:"edit,omitempty"` // edit, insert, replace: the image's edit (replace drops the old one)
	// Unattached inserts the file processed but not yet part of the item
	// (UploadOptions.ProcessOnUpload); attach makes it one, remove discards it.
	Unattached bool `json:"unattached,omitempty"`
}

Op is one manifest edit.

type OriginalEntry added in v0.48.0

type OriginalEntry struct {
	Name       string   `json:"name,omitempty"`
	Slot       string   `json:"slot,omitempty"`
	Type       string   `json:"type,omitempty"`
	Size       int64    `json:"size,omitempty"`
	Renditions []string `json:"renditions,omitempty"`
}

OriginalEntry is one uploaded file: its upload name, type and size, and the renditions derived from it.

type OrphanFolder added in v0.45.0

type OrphanFolder struct {
	Prefix  string
	ID      string
	ValidID bool
	Objects int
	Newest  time.Time
	Deleted bool
}

OrphanFolder is a folder no host item owns. ValidID false: its id is not a content id (e.g. a legacy integer), so no item can ever address it.

type OrphanReport added in v0.45.0

type OrphanReport struct {
	Folders int
	Orphans []OrphanFolder
}

OrphanReport lists a kind's orphans; Folders counts every folder seen.

type OrphanSweep added in v0.45.0

type OrphanSweep struct {
	Tenant, Kind string
	// Exists reports which of ids the host still has (a batch of at most
	// 500). An id it omits is an orphan.
	Exists func(ctx context.Context, ids []string) (map[string]bool, error)
	// Grace skips folders with an object newer than this (default the Jobs
	// grace), so an item created meanwhile is never taken for an orphan.
	Grace time.Duration
	// Delete removes orphans; otherwise they are only reported.
	Delete bool
}

OrphanSweep configures SweepOrphans.

type OutputURLs added in v0.33.0

type OutputURLs struct {
	BaseURL string // the access worker origin
	Token   string // the item's private/ folder token; editors only
	Editor  string // the item's temp/ editor token; editors only
}

OutputURLs builds slot output URLs for one caller: public/ copies for an item that is not hidden; private/ files under Token (editors) otherwise, and editor views under Editor.

type PGLimiter

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

PGLimiter is the default UploadLimiter over ContentKit's Postgres schema: hourly counters per uploader (bytes/day sums the last 24), one usage total per owner and short-lived pending reservations. There are no rows per file.

func NewPGLimiter

func NewPGLimiter(pool *pgxpool.Pool, schema string, limits PGLimits) (*PGLimiter, error)

func (*PGLimiter) Reserve

func (l *PGLimiter) Reserve(ctx context.Context, r Reservation) error

func (*PGLimiter) Settle

func (l *PGLimiter) Settle(ctx context.Context, s Settlement) error

func (*PGLimiter) Usage

func (l *PGLimiter) Usage(ctx context.Context, tenant, owner string) (used, pending int64, err error)

Usage reports an owner's stored bytes and unexpired pending reservations.

type PGLimits

type PGLimits struct {
	FilesPerHour int
	BytesPerDay  int64
	// Quota is the owner's storage cap in bytes (<= 0 unlimited); nil disables quotas.
	Quota func(ctx context.Context, tenant, owner string) (int64, error)
	// ReservationTTL drops unsettled reservations (commit still enforces the
	// quota); default 24h.
	ReservationTTL time.Duration
}

PGLimits configure PGLimiter. Zero limits are off.

type Part

type Part struct {
	Number int32
	Size   int64
	ETag   string
	SHA256 []byte
}

Part is one uploaded multipart part.

type PartBody

type PartBody struct {
	Number int32  `json:"number"`
	Size   int64  `json:"size"`
	SHA256 string `json:"sha256"`
}

type PartReply

type PartReply struct {
	Number  int32         `json:"number"`
	Size    int64         `json:"size,omitempty"`
	SHA256  string        `json:"sha256,omitempty"`
	Request *RequestReply `json:"request,omitempty"`
}

PartReply is a presigned part (parts) or a landed part (parts/list).

type PartRequest

type PartRequest struct {
	Number int32
	Size   int64
	SHA256 []byte
}

PartRequest is one part to presign: its exact length and SHA-256.

type PartsBody

type PartsBody struct {
	Ticket string     `json:"ticket"`
	Parts  []PartBody `json:"parts"`
}

type PartsReply

type PartsReply struct {
	Parts []PartReply `json:"parts"`
}

type PosterFrame added in v0.26.0

type PosterFrame struct {
	Version string  `json:"version,omitempty"` // manifest holding File, for versioned kinds
	File    string  `json:"file"`
	Time    float64 `json:"time"`
	Auto    bool    `json:"auto,omitempty"`   // chosen by the worker (first non-flat frame)
	Source  string  `json:"source,omitempty"` // original grabbed from; "" until grabbed
}

PosterFrame is a poster grabbed from a video frame (SlotRecord.Frame).

func (PosterFrame) Same added in v0.26.0

func (f PosterFrame) Same(o PosterFrame) bool

Same reports the same selection; an automatic one at any time.

type PosterManifest added in v0.26.0

type PosterManifest struct {
	SlotManifest
	File      string           `json:"file,omitempty"`
	Time      *float64         `json:"time,omitempty"`
	Selection *PosterSelection `json:"selection,omitempty"`
}

PosterManifest is the poster slot's manifest plus its selection. File and Time are the video and second a frame poster was cut from, for every caller that sees the poster: a gallery draws it on that video only and starts its inline preview there ("" for an uploaded poster, which belongs to the first video).

type PosterRequest added in v0.26.0

type PosterRequest struct {
	Source string
	File   string // default: the first video file
	Time   float64
	SHA256 []byte
	Edit   *Edit
}

PosterRequest selects a video's poster: Source "frame" grabs File at Time (seconds); "upload" commits the image uploaded to the poster slot (SHA256 as presigned); "auto" returns to the worker's choice. Edit is the slot edit, in the grabbed frame's pixels (VideoInfo.W×H) or the upload's; nil centres.

type PosterSelection added in v0.26.0

type PosterSelection struct {
	Source  string   `json:"source"`
	Version string   `json:"version,omitempty"`
	File    string   `json:"file,omitempty"`
	Time    *float64 `json:"time,omitempty"`
}

PosterSelection is the current poster choice; Source is auto, frame or upload.

type PresignBody

type PresignBody struct {
	Ref    RefBody `json:"ref"`
	Type   string  `json:"type"`
	Size   int64   `json:"size"`
	SHA256 string  `json:"sha256,omitempty"` // required up to 64 MiB, for slots and inline images
	Slot   string  `json:"slot,omitempty"`
	Inline bool    `json:"inline,omitempty"` // a new inline image; the reply names it
}

type PresignPut

type PresignPut struct {
	ContentType string
	Size        int64
	SHA256      []byte
	TTL         time.Duration
}

PresignPut binds a direct upload to its exact type, length and SHA-256.

type PresignReply

type PresignReply struct {
	Name            string          `json:"name"`
	Exists          bool            `json:"exists,omitempty"`
	Put             *RequestReply   `json:"put,omitempty"`
	Multipart       *MultipartReply `json:"multipart,omitempty"`
	ProcessOnUpload bool            `json:"process_on_upload,omitempty"`
}

PresignReply: exists (commit directly), put (one PUT) or multipart. ProcessOnUpload asks the client to commit the file unattached as soon as it is uploaded (UploadOptions.ProcessOnUpload).

type PresignRequest

type PresignRequest struct {
	Ref    contentref.ContentRef
	Type   string
	Size   int64
	SHA256 []byte
	Slot   string
	Inline bool
}

PresignRequest declares one file. SHA256 is required up to MaxSinglePut and for slots; Slot targets the kind's fixed slot original, and Inline a new inline image, named in Presigned.Name. Both commit with CommitSlot.

type Presigned

type Presigned struct {
	Name            string
	Exists          bool
	Put             *PresignedRequest
	Multipart       *Multipart
	ProcessOnUpload bool
}

Presigned is the upload plan: Exists (already in the folder; commit it), a single Put, or a Multipart upload. ProcessOnUpload is the host's UploadOptions.ProcessOnUpload, for manifest files.

type PresignedPart

type PresignedPart struct {
	Number int32
	PresignedRequest
}

PresignedPart is a part URL.

type PresignedRequest

type PresignedRequest struct {
	Method  string
	URL     string
	Header  http.Header
	Expires time.Time
}

PresignedRequest is a request a browser sends as is, with exactly Header.

type PrivateEntry added in v0.48.0

type PrivateEntry struct {
	File      string `json:"file,omitempty"`
	Version   string `json:"version,omitempty"`
	Slot      string `json:"slot,omitempty"`
	Rendition string `json:"rendition"`
	W         int    `json:"w,omitempty"`
	Type      string `json:"type,omitempty"`
	Size      int64  `json:"size,omitempty"`
	Public    bool   `json:"public,omitempty"`
}

PrivateEntry is one rendition: the file (in Version) or slot it belongs to, which rendition it is, and whether it is exposed in public/.

type ProcessCanceler added in v0.43.0

type ProcessCanceler interface {
	Cancel(ctx context.Context, ref contentref.ContentRef) (int, error)
}

ProcessCanceler is a ProcessQueue that can cancel an item's queued and running jobs (workqueue.Queue); a discard uses it.

type ProcessJob

type ProcessJob struct {
	Ref   contentref.ContentRef
	Slot  string
	Class VideoJobClass
}

ProcessJob asks for derivatives after a commit: the item's manifest, or one public slot when Slot is set.

type ProcessQueue

type ProcessQueue interface {
	Enqueue(ctx context.Context, job ProcessJob) error
}

ProcessQueue enqueues processing in the media worker (workqueue.Queue).

type ProgressSource added in v0.29.0

type ProgressSource interface {
	EncodeProgress(ctx context.Context, ref contentref.ContentRef) (EncodeStatus, error)
}

ProgressSource reads encode progress (workqueue.NewProgressSource). The read API asks only when a visible video file is pending.

type PutOptions

type PutOptions struct {
	ContentType    string
	CacheControl   string
	ChecksumSHA256 []byte
	IfMatch        string
	IfNoneMatch    string
	Metadata       map[string]string
}

PutOptions are conditions and headers for Put. IfNoneMatch "*" creates only.

type ReadOptions

type ReadOptions struct {
	// Variants in preference order: each file in range gets a URL for the
	// first one it has. EditorVariant is the editor view, for editors only:
	// a missing one is rendered again (ReaderOptions.Queue) and the file
	// falls through to the next variant meanwhile. Empty returns metadata only.
	Variants      []string
	Offset, Limit int
	// Unattached also lists an editor's unattached files (FileInfo.Unattached).
	Unattached bool
}

ReadOptions select the URLs a read returns.

type ReadResult

type ReadResult struct {
	Access       string         `json:"access"`
	Total        int            `json:"total"`
	PreviewLimit int            `json:"preview_limit"`
	Offset       int            `json:"offset"`
	Limit        int            `json:"limit"`
	Expires      int64          `json:"expires"` // unix seconds; URLs and cookie stop working then
	Meta         map[string]any `json:"meta,omitempty"`
	Files        []FileInfo     `json:"files"`
	Downloads    []DownloadInfo `json:"downloads,omitempty"`
	// Cookie must be set on the response (cookie mode, full access).
	Cookie *http.Cookie `json:"-"`
}

ReadResult is the read API response. Files lists every file; only allowed files inside [offset, offset+limit) carry a URL, and files past the cut omit their name.

type Reader

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

Reader answers the read API: one Resolve per item, metadata for every file, and signed URLs for the requested range.

func NewReader

func NewReader(o ReaderOptions) (*Reader, error)

func (*Reader) EditorURLs added in v0.33.0

func (r *Reader) EditorURLs(ref contentref.ContentRef) (OutputURLs, error)

EditorURLs are the OutputURLs of an item's editors (uploaders).

func (*Reader) Grant

func (r *Reader) Grant(ctx context.Context, ref contentref.ContentRef, actor access.Actor) (*Grant, error)

Grant resolves ref for actor exactly once and loads its manifest. A resolver error denies (ErrResolve); an invisible item is ErrNotVisible. A visible item without a manifest yet has no files.

func (*Reader) Handler

func (r *Reader) Handler(o HandlerOptions) http.Handler

Handler serves the read API. The host mounts it under a prefix such as "/media/" after its auth middleware. {id} is "{content_id}" or "{content_id}@{version_id}" for a versioned kind. Errors are JSON {"error", "code"}: 400 invalid_request, 404 not_found (also for hidden items), 429 rate_limited (Retry-After; HandlerOptions.Limit), 500 internal_error (a resolver error denies this way).

GET /{kind}/{id}?variant=high,thumb&offset=0&limit=50 -> ReadResult (+ Set-Cookie mt)
GET /{kind}/{id}/hls/{file}/master.m3u8?audio=ja&subs=en,s2 (filters optional; empty = none)
GET /{kind}/{id}/hls/{file}/video/{rung}-{codec}.m3u8, audio/{track}.m3u8, subs/{track}.m3u8
GET /{kind}/{id}/hls/{file}/sprite.vtt
GET /{kind}/{id}/download/{key} -> 302 to the signed download URL
GET /{kind}/{id}/slots/{slot} -> SlotManifest
GET /{kind}/{id}/video-images -> VideoImages without selections

Every request resolves the item once and is "private, no-store"; playlists and redirects carry the folder cookie in cookie mode. Each request that signs URLs logs the viewer, item, access and expiry, and a short hash of a folder token, so a leaked URL can be traced to its viewer.

func (*Reader) InlineURL added in v0.48.0

func (r *Reader) InlineURL(ctx context.Context, ref contentref.ContentRef, id string) (string, error)

InlineURL is an inline image's public URL, or ErrPending until the worker has rendered it.

func (*Reader) ListedSlot added in v0.40.0

func (r *Reader) ListedSlot(ref contentref.ContentRef, slot string, l SlotListing) (SlotManifest, error)

ListedSlot is a slot's manifest built without reads, for listings, from the SlotListing the host stored: every output's public URL. Hosts list only items that are not hidden.

func (*Reader) Read

Read resolves ref once and answers the read API.

func (*Reader) Slot added in v0.20.0

func (r *Reader) Slot(ctx context.Context, ref contentref.ContentRef, actor access.Actor, slot string) (SlotManifest, error)

Slot resolves ref for actor and reads a slot: ErrNotVisible for an item actor may not see. Editors also see a hidden item's outputs.

func (*Reader) VideoImages added in v0.26.0

func (r *Reader) VideoImages(ctx context.Context, ref contentref.ContentRef, actor access.Actor) (VideoImages, error)

VideoImages resolves ref for actor and reads a video item's poster: ErrNotVisible for an item actor may not see; editors also see a hidden item's poster.

type ReaderOptions

type ReaderOptions struct {
	Manifests *Manifests
	Kinds     *Registry
	Resolver  access.ContentResolver
	Delivery  Delivery
	Hooks     Hooks
	// Progress adds live encode progress to pending video files; optional.
	Progress ProgressSource
	// Queue renders an editor view an editor asks for that is missing
	// (never rendered, or swept); optional (the editor then waits for the
	// next processing job).
	Queue ProcessQueue
	// MaxLimit caps ReadOptions.Limit (default 200); DefaultLimit is used when
	// Limit is 0 (default 50).
	MaxLimit, DefaultLimit int
	Now                    func() time.Time
}

ReaderOptions configure a Reader.

type Readiness added in v0.50.0

type Readiness struct {
	State      string   `json:"state"`
	Processing []string `json:"processing,omitempty"`
	Failed     []string `json:"failed,omitempty"`
}

Readiness is whether an item's media is fully processed: ready when every attached file, set slot and video poster is; processing while any is still being derived or encoded; failed once nothing is processing and some could not be. Processing and Failed name them ("{version}/{file}" for a versioned kind's files, the slot name for slots).

func (Readiness) Ready added in v0.50.0

func (r Readiness) Ready() bool

type RefBody

type RefBody struct {
	Kind    string `json:"kind"`
	ID      string `json:"id"`
	Version string `json:"version,omitempty"`
}

RefBody names an item within the handler's tenant.

type Registry

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

Registry is the host's set of kinds.

func NewRegistry

func NewRegistry(kinds ...Kind) (*Registry, error)

NewRegistry validates and registers kinds.

func (*Registry) Item

func (r *Registry) Item(ref contentref.ContentRef) (Item, error)

Item validates ref against the registry. A version is required to address a versioned kind's files, and refused for an unversioned kind.

func (*Registry) Kind

func (r *Registry) Kind(name string) (Kind, error)

Kind returns a registered kind.

type Rendition

type Rendition struct {
	Rung      int       `json:"rung"`
	Codec     Codec     `json:"codec"`
	Width     int       `json:"w"`
	Height    int       `json:"h"`
	Bandwidth int       `json:"bandwidth"`
	Average   int       `json:"avg,omitempty"`
	Codecs    string    `json:"codecs"`
	Blob      string    `json:"blob"`
	Segments  []Segment `json:"segments"`
}

Rendition is one video-only fMP4 blob: its init segment is bytes [0, Segments[0].Offset) and the segments follow contiguously. Rung is its ladder label (the short side it was asked for, e.g. 1080 for "1080p"); Width and Height are the encoded frame. Each rung has one rendition per codec; Codecs is its RFC 6381 value.

func FrameRendition added in v0.26.0

func FrameRendition(f File) (Rendition, bool)

FrameRendition is the rendition poster frames are grabbed from: the widest, H.264 when there is one (any ffmpeg decodes it).

type RequestReply

type RequestReply struct {
	Method  string            `json:"method"`
	URL     string            `json:"url"`
	Headers map[string]string `json:"headers"`
	Expires time.Time         `json:"expires"`
}

RequestReply is a presigned request: send exactly these headers (the browser adds Content-Length, which is signed too).

type RerunArgs added in v0.43.0

type RerunArgs interface {
	river.JobArgs
	FollowUp(id int64) river.JobArgs
}

RerunArgs are job args that can name the running job they follow.

type Reservation

type Reservation struct {
	Tenant   string
	Uploader string
	Owner    string
	Key      string
	Size     int64
}

Reservation is one presigned upload. Uploader is rate-limited; Owner ("" for none) has Size reserved against its quota until the upload is settled or the reservation expires.

type Root added in v0.48.0

type Root struct {
	Manifest                        // an unversioned kind's files
	Versions map[string]*Manifest   `json:"versions,omitempty"`
	Slots    map[string]*SlotRecord `json:"slots,omitempty"` // registered slots and inline images ("i-{uuid}")
	Hidden   bool                   `json:"hidden,omitempty"`
	// Originals indexes originals/ by name.
	Originals map[string]OriginalEntry `json:"originals"`
	// Private indexes private/ by name; Public entries are copied to public/
	// under the same name.
	Private map[string]PrivateEntry `json:"private"`
}

Root is manifest.json, an item's one canonical manifest (never served): its files (a section per version for versioned kinds), its slots and inline images, whether it is hidden, and an index of every object the folder keeps. Originals, Private and the Public flags are rebuilt from the rest on every write; the sweep deletes what they do not list.

func (*Root) PublicNames added in v0.48.0

func (r *Root) PublicNames() []string

PublicNames lists the renditions exposed in public/.

func (*Root) Readiness added in v0.50.0

func (r *Root) Readiness(k Kind) Readiness

Readiness is the item's readiness under kind k (the registry's, so a video kind has its poster slot): every section's attached files, every set slot and a video kind's poster.

func (*Root) Refs added in v0.48.0

func (r *Root) Refs() map[string]bool

Refs is every object the manifest keeps, as "{area}/{name}": the indexed originals, renditions and public copies, and staged uploads.

func (*Root) Section added in v0.48.0

func (r *Root) Section(v string) *Manifest

Section is version v's files ("" for an unversioned kind), or nil.

type Segment

type Segment struct {
	Offset  int64
	Length  int64
	Seconds float64
}

Segment is one EXT-X-BYTERANGE segment, encoded as [offset, length, seconds].

func (Segment) MarshalJSON

func (s Segment) MarshalJSON() ([]byte, error)

func (*Segment) UnmarshalJSON

func (s *Segment) UnmarshalJSON(b []byte) error

type Settlement

type Settlement struct {
	Tenant  string
	Owner   string
	Keys    []string
	Delta   int64
	Enforce bool
}

Settlement releases reservations and moves an owner's usage. With Enforce, a positive Delta that takes usage over the owner's quota is refused with CodeQuota and changes nothing.

type Slot

type Slot struct {
	Aspect    Aspect
	Widths    []int
	MinWidth  int
	Quality   int // WebP quality; default 80
	Animation Animation
}

Slot is a fixed public image at Aspect (the edited image's width:height), or at the edited image's own shape when Aspect is AspectNative (no crop by default, any crop shape), rendered at each of Widths (the host's rungs, e.g. a small and a large one) to hash-named WebP renditions in private/, copied to public/ unless the item is hidden. A change writes new names. Nothing is upscaled: a rung wider than the edited image is rendered at the edited width, so every rung exists once the slot is set. Its original and Edit are recorded in the manifest (Root.Slots). An edit narrower than Min fails.

func InlineSlot added in v0.48.0

func InlineSlot(s Spec) Slot

InlineSlot is the Slot an inline image renders as: its spec's width at its own aspect.

func (Slot) Hash added in v0.20.0

func (s Slot) Hash() string

Hash is the slot spec's stable identity; outputs under another are stale.

func (Slot) Height added in v0.20.0

func (s Slot) Height(width int) int

Height is the output height of a width at Aspect (0 for a native slot).

func (Slot) Min added in v0.20.0

func (s Slot) Min() int

Min is the narrowest edited width accepted: MinWidth, else the smallest width.

func (Slot) Native added in v0.37.0

func (s Slot) Native() bool

Native reports a slot at its edited image's own aspect.

func (Slot) OutputWidth added in v0.40.0

func (s Slot) OutputWidth(rung, edited int) int

OutputWidth is the width rendered for rung from an image edited pixels wide: the rung, or edited when narrower (never upscaled).

func (Slot) Resolve added in v0.20.0

func (s Slot) Resolve(e *Edit, w, h int) (*Edit, error)

Resolve is the edit the slot applies to a w×h source (EXIF-oriented): e with its height fitted, or without a crop the largest centred one at Aspect (a native slot: the whole source). It must lie inside the source and be at least Min wide once edited.

func (Slot) Size added in v0.37.0

func (s Slot) Size(width int, edited Dims) Dims

Size is the output of a width from an edited image of size edited.

type SlotBody

type SlotBody struct {
	Ref      RefBody `json:"ref"`
	Slot     string  `json:"slot"`
	SHA256   string  `json:"sha256"`
	Edit     *Edit   `json:"edit,omitempty"`
	Filename string  `json:"filename,omitempty"`
}

SlotBody commits an uploaded slot (or inline image) original. Edit's crop is in the EXIF-oriented original's pixels, its height derived from its width at the slot's aspect; omitted crops centred at the aspect.

type SlotCommit added in v0.48.0

type SlotCommit struct {
	Ref      contentref.ContentRef
	Slot     string
	SHA256   []byte
	Edit     *Edit // registered slots; nil: centred
	Filename string
}

SlotCommit names an uploaded slot or inline original: SHA256 is the one its upload was presigned with, Filename the uploaded file's name.

type SlotEditBody added in v0.20.0

type SlotEditBody struct {
	Ref  RefBody `json:"ref"`
	Slot string  `json:"slot"`
	Edit *Edit   `json:"edit,omitempty"`
}

SlotEditBody re-edits the committed original; omitted crops centred.

type SlotFromFile added in v0.19.0

type SlotFromFile struct {
	Ref  contentref.ContentRef // the slot's item; a version ref also names From's manifest
	Slot string
	// From is the manifest holding File when it is not Ref's: another item
	// (or version) of the same tenant, e.g. a post image for a channel avatar.
	From contentref.ContentRef
	File string
	// Edit crops the copy (default: the file's own edit; an empty Edit clears it).
	Edit *Edit
}

SlotFromFile names a slot and the manifest image to fill it from.

type SlotFromFileBody added in v0.17.0

type SlotFromFileBody struct {
	Ref  RefBody  `json:"ref"`
	Slot string   `json:"slot"`
	From *RefBody `json:"from,omitempty"`
	File string   `json:"file"`
	Edit *Edit    `json:"edit,omitempty"`
}

SlotFromFileBody makes File (a manifest image of From, default Ref) the slot's original, through Edit (default: the file's edit; {} clears it).

type SlotImage added in v0.20.0

type SlotImage struct {
	W   int    `json:"w"`
	H   int    `json:"h"`
	URL string `json:"url"`
}

SlotImage is one produced output.

type SlotListing added in v0.48.0

type SlotListing struct {
	Aspect  Aspect          `json:"aspect"`
	Outputs []SlotRendition `json:"outputs"`
}

SlotListing is what a host stores from Hooks.SlotEncoded to list a slot without reads (Reader.ListedSlot).

type SlotManifest added in v0.20.0

type SlotManifest struct {
	Aspect  Aspect      `json:"aspect"`         // "W:H"; a native slot's from its outputs ("" before any)
	Edit    *Edit       `json:"edit,omitempty"` // nil: the centred crop at aspect (native: the whole image)
	Dims    *Dims       `json:"dims,omitempty"` // the committed original, EXIF-oriented, once measured
	Outputs []SlotImage `json:"outputs"`
	Pending bool        `json:"pending"`         // a commit, edit or spec change is not encoded yet
	Error   string      `json:"error,omitempty"` // the latest encode failed; the outputs are older
	// ErrorCode is an image refusal's code (image_too_small, …) with its
	// details; empty when Error is a processing fault.
	ErrorCode    string        `json:"error_code,omitempty"`
	ErrorDetails *ErrorDetails `json:"error_details,omitempty"`
	// MinWidth is the narrowest edited width the slot accepts: croppers
	// keep crops at or above it.
	MinWidth int `json:"min_width,omitempty"`
	// Animation is the slot's policy: "reject" refuses animated images.
	Animation Animation `json:"animation,omitempty"`
	// EditorURL is the committed original's editor view (Kind.Editor), what
	// the cropper draws on; editors only, "" while it renders.
	EditorURL string `json:"editor_url,omitempty"`
	// contains filtered or unexported fields
}

SlotManifest describes a slot: its outputs by ascending width. Every output URL names an immutable file; a change lists new URLs.

type SlotRecord added in v0.20.0

type SlotRecord struct {
	Original string       `json:"original"` // originals/ name
	Filename string       `json:"filename,omitempty"`
	Type     string       `json:"type,omitempty"`
	Size     int64        `json:"size,omitempty"`
	Edit     *Edit        `json:"edit,omitempty"`  // nil: the centred crop at the slot's Aspect
	Frame    *PosterFrame `json:"frame,omitempty"` // a video poster grabbed from a frame; nil for uploads
	Result   *SlotResult  `json:"result,omitempty"`
}

SlotRecord is a registered slot's or inline image's entry in Root.Slots. Commits and edits set Original (with its upload name, type and size) and Edit; the image job sets Result.

func (SlotRecord) Fingerprint added in v0.20.0

func (rec SlotRecord) Fingerprint(s Slot) string

Fingerprint identifies the outputs the record yields under slot spec s.

type SlotRefBody added in v0.20.0

type SlotRefBody struct {
	Ref  RefBody `json:"ref"`
	Slot string  `json:"slot"`
}

type SlotRendition added in v0.40.0

type SlotRendition struct {
	Rung int    `json:"rung"`
	W    int    `json:"w"`
	H    int    `json:"h"`
	Blob string `json:"blob"`
	Size int64  `json:"size,omitempty"`
}

SlotRendition is one output: private/{Blob}, the rung it renders and its size (narrower than the rung when the edited image is).

type SlotResult added in v0.20.0

type SlotResult struct {
	Of      string          `json:"of"`              // the Fingerprint last encoded
	Source  string          `json:"source"`          // the original Dims measure
	Dims    Dims            `json:"dims"`            // EXIF-oriented; zero when undecodable
	Outputs []SlotRendition `json:"outputs"`         // one per rung, ascending
	Error   string          `json:"error,omitempty"` // Of failed; Outputs are older
	// An image refusal's code and details; empty for a processing fault.
	Code    string        `json:"code,omitempty"`
	Details *ErrorDetails `json:"details,omitempty"`
}

SlotResult is what the current renditions were derived from.

func (*SlotResult) Listing added in v0.48.0

func (res *SlotResult) Listing(s Slot) SlotListing

Listing is the slot's current outputs, for Hooks.SlotEncoded.

type Spec

type Spec struct {
	Width   int
	Height  int
	Fit     Fit
	Quality int
	Blur    float64
}

Spec describes one derived image. Zero Width and Height keep full resolution.

func (Spec) For added in v0.17.0

func (s Spec) For(e *Edit) string

For is the identity of s derived through e, recorded as Variant.Spec: a variant is stale when its spec or its file's edit changes.

func (Spec) Hash

func (s Spec) Hash() string

Hash is the spec's stable identity; a variant whose recorded spec differs is stale (see For, which adds the file's edit).

type Sprite

type Sprite struct {
	Blob     string  `json:"blob"`
	Cols     int     `json:"cols"`
	Rows     int     `json:"rows"`
	Width    int     `json:"w"`
	Height   int     `json:"h"`
	Interval float64 `json:"interval"`
}

type Staged added in v0.40.0

type Staged struct {
	Name   string
	ETag   string
	SHA256 []byte
}

Staged is a staged upload the caller has read in full: its name, the ETag it read and the SHA-256 of the bytes.

type Store

type Store interface {
	Put(ctx context.Context, key string, body io.Reader, size int64, opts PutOptions) (Object, error)
	Get(ctx context.Context, key string, opts GetOptions) (io.ReadCloser, Object, error)
	Head(ctx context.Context, key string) (Object, error)
	Delete(ctx context.Context, key string) error
	List(ctx context.Context, prefix string) iter.Seq2[Object, error]
	// Copy copies src to dst server-side, keeping its content type, cache
	// control and metadata; objects past the backend's single-copy limit
	// (5 GiB on S3) copy in parts. The returned Object has dst's size.
	Copy(ctx context.Context, src, dst string, o CopyOptions) (Object, error)

	PresignPut(ctx context.Context, key string, p PresignPut) (PresignedRequest, error)
	// PresignGet is for a worker reading an original over HTTP ranges. Its URL
	// uses the store's internal endpoint and must not be returned to browsers.
	PresignGet(ctx context.Context, key string, ttl time.Duration) (PresignedRequest, error)
	CreateMultipart(ctx context.Context, key, contentType string) (uploadID string, err error)
	PresignPart(ctx context.Context, key, uploadID string, number int32, size int64, sha256 []byte, ttl time.Duration) (PresignedRequest, error)
	// PutPart uploads one part from the server, bound to its length and SHA-256.
	PutPart(ctx context.Context, key, uploadID string, number int32, body io.Reader, size int64, sha256 []byte) (Part, error)
	ListParts(ctx context.Context, key, uploadID string) ([]Part, error)
	CompleteMultipart(ctx context.Context, key, uploadID string, parts []Part) (Object, error)
	AbortMultipart(ctx context.Context, key, uploadID string) error

	Capabilities() Capabilities
	// Check reports whether the backend answers. The first success also
	// establishes Capabilities (Probe under prefix) when they were not given.
	Check(ctx context.Context, prefix string) error
}

Store is the bucket. Keys are built by Item; implementations do not interpret them.

type Subtitle

type Subtitle struct {
	ID     string `json:"id"`
	Lang   string `json:"lang,omitempty"`
	Label  string `json:"label,omitempty"`
	Forced bool   `json:"forced,omitempty"`
	Blob   string `json:"blob"`
}

type SweepResult

type SweepResult struct {
	Deleted []string
	Wait    time.Duration
}

SweepResult reports one folder sweep. Wait > 0 means a manifest changed within the grace period and nothing was deleted; sweep again after Wait.

type SweepScheduler added in v0.43.0

type SweepScheduler interface {
	ScheduleSweep(ctx context.Context, ref contentref.ContentRef) error
}

SweepScheduler schedules a folder's sweep after an edit.

type TicketBody

type TicketBody struct {
	Ticket string `json:"ticket"`
}

type UploadAuthorizer

type UploadAuthorizer interface {
	CanUpload(ctx context.Context, actor access.Actor, ref contentref.ContentRef) (UploadGrant, error)
}

UploadAuthorizer is the host's upload permission check (AuthKit), run at presign and commit against the ref whose folder is written: the version for manifest uploads, the work (ref.Content()) for slots and inline images.

type UploadError

type UploadError struct {
	Code       string
	Message    string
	RetryAfter time.Duration // CodeRate: when the window frees
	Originals  []string      // CodeNotUploaded at commit: the originals to upload again
	Details    *ErrorDetails // image refusals
}

UploadError is a refused upload request. UploadLimiter implementations refuse with CodeRate or CodeQuota.

func AsUploadError

func AsUploadError(err error) (*UploadError, bool)

AsUploadError classifies err: an *UploadError, or the kind and registry sentinels. ok is false for internal failures.

func (*UploadError) Error

func (e *UploadError) Error() string

func (*UploadError) Is added in v0.38.0

func (e *UploadError) Is(target error) bool

Is matches the kind sentinels by code.

func (*UploadError) Status

func (e *UploadError) Status() int

Status is the HTTP status for Code.

type UploadGrant

type UploadGrant struct {
	Allowed bool
	Exempt  bool
	Owner   string
}

UploadGrant is the host's verdict. Exempt (trusted roles) skips the UploadLimiter checks; Owner is the quota owner (creator, channel), "" for none.

type UploadHandlerOptions

type UploadHandlerOptions struct {
	Tenant string                                   // every ref is scoped to it
	Actor  func(*http.Request) (access.Actor, bool) // the host's authenticated caller; false answers 401
	Logger *slog.Logger                             // 5xx causes; default slog.Default()
	// Reader builds slot and video-image reply URLs (the access worker
	// origin; editor tokens for unpublished video posters); required for
	// slot and video routes.
	Reader *Reader
}

UploadHandlerOptions configure UploadHandler.

type UploadLimiter

type UploadLimiter interface {
	Reserve(ctx context.Context, r Reservation) error
	Settle(ctx context.Context, s Settlement) error
}

UploadLimiter is the optional anti-abuse port. Quota is enforced when bytes are committed: a commit that grows its owner's stored originals past the quota is refused. Reserve runs at presign (skipped for exempt uploaders) and refuses with an *UploadError coded CodeRate or CodeQuota before any bytes move; a reservation is only that early refusal and may expire. Settle runs at commit, abort and item deletion: it drops the reservations of Keys and adds Delta (the change in stored originals, negative on removal) to the owner's usage.

type UploadOptions

type UploadOptions struct {
	Store      Store
	Kinds      *Registry
	Manifests  *Manifests
	Authorizer UploadAuthorizer
	Tickets    *token.Ring   // signs multipart tickets (domain-separated from access tokens); required for files over MaxSinglePut
	Limiter    UploadLimiter // optional
	Queue      ProcessQueue  // optional
	PresignTTL time.Duration // PUT and part URLs; default 15m
	// Grace and TempUploadTTL are the sweep's (JobsConfig): the Manifests'
	// Sweeps' when it is *Jobs, else 24 h and 48 h.
	Grace         time.Duration
	TempUploadTTL time.Duration
	TicketTTL     time.Duration // multipart ticket; default 24h, the abort-incomplete rule
	// Frames serves the video poster picker's frame grabs (media/video.Frames;
	// needs ffmpeg); nil answers not_found. FrameConcurrency bounds concurrent
	// grabs per process; default 2.
	Frames           FrameGrabber
	FrameConcurrency int
	// ProcessOnUpload tells the SDK to commit each manifest file as soon as
	// it is uploaded, unattached (Op.Unattached): the worker processes it
	// while the user is still arranging the upload, readers leave it out
	// until an attach op, and removing it discards its jobs and objects.
	// Quota is charged at that commit.
	ProcessOnUpload bool
}

UploadOptions configure Uploads.

type UploadedObject

type UploadedObject struct {
	Name string
	Type string
	Size int64
}

UploadedObject is a completed multipart original.

type Uploads

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

Uploads presigns direct-to-bucket uploads and commits them into manifests. It keeps no state: a multipart upload is its S3 UploadId, carried in a signed ticket.

func NewUploads

func NewUploads(o UploadOptions) (*Uploads, error)

func (*Uploads) Abort

func (u *Uploads) Abort(ctx context.Context, actor access.Actor, sealed string) error

Abort cancels a multipart upload and drops its reservation.

func (*Uploads) Commit

func (u *Uploads) Commit(ctx context.Context, actor access.Actor, ref contentref.ContentRef, ops []Op) (*Manifest, error)

Commit applies ops to the manifest in one conditional write. Every new original is HEAD-checked against the kind's type and size cap (and re-hashed when the store does not enforce checksums). The owner is charged the change in distinct originals the manifest references; growth past its quota fails with CodeQuota (not for exempt grants). Then processing is enqueued.

func (*Uploads) CommitSlot

func (u *Uploads) CommitSlot(ctx context.Context, actor access.Actor, c SlotCommit) error

CommitSlot validates an uploaded slot or inline original and enqueues the encode of its renditions. A registered slot records it with its edit (nil: the centred crop at the slot's Aspect); its crop's height follows its width. Inline images take no edit.

func (*Uploads) Complete

func (u *Uploads) Complete(ctx context.Context, actor access.Actor, sealed string) (UploadedObject, error)

Complete assembles the parts server-side. Missing parts answer CodeIncomplete and keep the upload; parts that cannot add up to the declared size abort it.

func (*Uploads) EditSlot added in v0.20.0

func (u *Uploads) EditSlot(ctx context.Context, actor access.Actor, ref contentref.ContentRef, slot string, edit *Edit) error

EditSlot re-edits the committed original (nil: centred) without a new upload; the job re-encodes every output from it. Once the original's size is known the edit is checked against it here, else by the job.

func (*Uploads) Frame added in v0.26.0

func (u *Uploads) Frame(ctx context.Context, actor access.Actor, ref contentref.ContentRef, file string, t float64, width int) ([]byte, error)

Frame renders a small JPEG of the encoded file's frame at t for the poster picker. t is clamped into the video, width into 64-FrameMaxWidth (0 is FrameDefaultWidth). At most UploadOptions.FrameConcurrency run at once; others wait briefly, then answer CodeRate.

func (*Uploads) Ingest added in v0.18.0

func (u *Uploads) Ingest(ctx context.Context, actor access.Actor, req IngestRequest) (IngestResult, error)

Ingest uploads req.Body into the item's originals (one checksum-bound PUT when it fits in one part, else multipart with per-part SHA-256 and retries) and commits it with Commit, which re-checks the upload and enqueues processing. Every check Commit makes on a browser upload applies.

func (*Uploads) ListParts

func (u *Uploads) ListParts(ctx context.Context, actor access.Actor, sealed string) ([]Part, error)

ListParts reports the parts that landed, for resuming.

func (*Uploads) Presign

func (u *Uploads) Presign(ctx context.Context, actor access.Actor, r PresignRequest) (Presigned, error)

func (*Uploads) PresignParts

func (u *Uploads) PresignParts(ctx context.Context, actor access.Actor, sealed string, parts []PartRequest) ([]PresignedPart, error)

PresignParts signs parts of a multipart upload, each bound to its length and SHA-256. Parts re-signed after a failure replace the earlier attempt.

func (*Uploads) SetSlotFromFile added in v0.17.0

func (u *Uploads) SetSlotFromFile(ctx context.Context, actor access.Actor, r SlotFromFile) error

SetSlotFromFile makes an image file's source the slot's original (copied into the slot's folder when From is another item) with the edit, and re-encodes the slot's renditions through it. The crop's height follows its width at the slot's Aspect; it is checked against the file's Dims once processing has recorded them, else by the slot job. The actor must be allowed to upload to Ref's work and, when From names another item, to From.

func (*Uploads) SetVideoPoster added in v0.26.0

func (u *Uploads) SetVideoPoster(ctx context.Context, actor access.Actor, ref contentref.ContentRef, r PosterRequest) error

SetVideoPoster records a poster selection and enqueues its work: a frame grab by the video worker (which then hands the frame to the image job), or the upload's encode. Frame selections need the ref's manifest (a version for versioned kinds) and an encoded file.

type Variant

type Variant struct {
	Blob string `json:"blob"`
	Spec string `json:"spec,omitempty"`
	Type string `json:"type,omitempty"`
	Size int64  `json:"size,omitempty"`
	W    int    `json:"w,omitempty"`
	H    int    `json:"h,omitempty"`
}

type Video added in v0.19.0

type Video struct {
	// Ladder is the rendition short sides (height of landscape, width of
	// vertical video), largest first; rungs above the source's short side
	// are dropped, and a source below 1080 that is not a rung also gets one
	// at its own short side. Empty is DefaultLadder.
	Ladder []int `json:"ladder,omitempty"`
	// MinAspect and MaxAspect bound a source's display width/height; a
	// source outside fails permanently. Zero is DefaultMinAspect/DefaultMaxAspect.
	MinAspect float64 `json:"min_aspect,omitempty"`
	MaxAspect float64 `json:"max_aspect,omitempty"`
	// PosterWidths are the cover (poster slot) output widths, the host's
	// display sizes × densities; widths wider than the frame or upload are
	// skipped. Empty is DefaultPosterWidths.
	PosterWidths []int `json:"poster_widths,omitempty"`
	// Profile tunes the encode to the content: VideoLive (default) or
	// VideoAnimation (x264 tune animation, lower CRF, lower caps).
	Profile string `json:"profile,omitempty"`
}

Video configures a kind's video encoding (media/video).

func (*Video) Aspects added in v0.22.0

func (v *Video) Aspects() (lo, hi float64)

Aspects are the aspect bounds in effect.

func (*Video) Poster added in v0.37.0

func (v *Video) Poster() Slot

Poster is the kind's poster slot: native aspect at PosterWidths.

func (*Video) Rungs added in v0.22.0

func (v *Video) Rungs() []int

Rungs is the ladder in effect.

func (Video) Validate added in v0.22.0

func (v Video) Validate() error

Validate requires even rungs of 2–4320, largest first, without repeats, aspect bounds with MinAspect ≤ 1 ≤ MaxAspect, and a known Profile.

type VideoImages added in v0.26.0

type VideoImages struct {
	Poster PosterManifest `json:"poster"`
	Video  *VideoInfo     `json:"video,omitempty"`
	// Progress of the encode the outputs wait on (GET only, with
	// ReaderOptions.Progress): a file's run, then PhaseImages.
	Progress *EncodeProgress `json:"progress,omitempty"`
}

VideoImages is a video item's poster. Selections and Video are only in the uploader's reply (POST /video-images).

type VideoImagesBody added in v0.26.0

type VideoImagesBody struct {
	Ref  RefBody `json:"ref"`
	File string  `json:"file,omitempty"`
}

VideoImagesBody names a video item; with a version (versioned kinds) the reply describes its file (file, default the first video file).

type VideoInfo added in v0.26.0

type VideoInfo struct {
	Version  string  `json:"version,omitempty"`
	File     string  `json:"file"`
	Duration float64 `json:"duration"`
	W        int     `json:"w"`
	H        int     `json:"h"`
	Encoded  bool    `json:"encoded"`
}

VideoInfo is the picker's video: the selected (or first) file of the ref's manifest. W×H is the grabbed poster frame's size, the space of frame poster edits.

type VideoJobClass added in v0.58.0

type VideoJobClass string

VideoJobClass selects the priority of a new video run. An empty class is an upload; re-encodes and backfills yield to new playable videos.

const (
	VideoReencode VideoJobClass = "reencode"
	VideoBackfill VideoJobClass = "backfill"
)

type VideoPosterBody added in v0.26.0

type VideoPosterBody struct {
	Ref    RefBody  `json:"ref"`
	Source string   `json:"source"`
	File   string   `json:"file,omitempty"`
	Time   *float64 `json:"time,omitempty"`
	SHA256 string   `json:"sha256,omitempty"`
	Edit   *Edit    `json:"edit,omitempty"`
}

VideoPosterBody selects the poster. source "frame" needs time (seconds, in the video) and a ref version for versioned kinds, its edit in the grabbed frame's pixels (VideoInfo w×h); "upload" needs the sha256 of the image presigned with slot "poster", its edit in that image's pixels; "auto" returns to the default. Omitted edits keep the whole image (native aspect).

type ViewerLimit added in v0.33.0

type ViewerLimit struct {
	PerSecond float64
	Burst     int
	// Disabled turns limiting off, e.g. when the host limits upstream.
	Disabled bool
	// Redis is a Redis or Microsoft Garnet client shared by the replicas;
	// pass the host's own. Only INCR, PEXPIRE, GET, DECR and MULTI/EXEC are
	// used (no Lua: Garnet ships with scripting off).
	Redis redis.UniversalClient
	// KeyPrefix namespaces the Redis keys (default "contentkit:media:rl:").
	KeyPrefix string
}

ViewerLimit is the read API's per-viewer limit: every read, playlist, download, slot and video-images request counts. Zero fields take the defaults (2/s sustained, burst 120: a page of reads and an HLS session each fit, bulk link harvesting does not).

With Redis set, every replica shares one limit per viewer; otherwise each process keeps its own (a single-replica assumption, logged at Handler), so N replicas allow N times the limit. Redis errors fail open to the per-process limit (see RedisErrors): the limit is abuse protection, and tokens and visibility checks still gate every file.

Directories

Path Synopsis
Package accessworker is the media access worker's HTTP handler, run by cmd/media-access: it checks the token for a private/ path (URL `?t=` or cookie `mt`), serves public/ paths without one and temp/ editor views only under an editor token (URL `?t=`, token.EditorScope, which viewer tokens never carry), refuses the manifest, originals/ and staged uploads, and streams the object from the private bucket with its own read-only key.
Package accessworker is the media access worker's HTTP handler, run by cmd/media-access: it checks the token for a private/ path (URL `?t=` or cookie `mt`), serves public/ paths without one and temp/ editor views only under an editor token (URL `?t=`, token.EditorScope, which viewer tokens never carry), refuses the manifest, originals/ and staged uploads, and streams the object from the private bucket with its own read-only key.
Package image derives WebP variants, slot and inline renditions and zip downloads with libvips (CGO).
Package image derives WebP variants, slot and inline renditions and zip downloads with libvips (CGO).
internal
s3test
Package s3test opens the test bucket from the environment:
Package s3test opens the test bucket from the environment:
uploadtestserver command
Command uploadtestserver serves media.UploadHandler over a fresh MinIO/RGW bucket for the browser SDK's integration tests (sdk/upload/test).
Command uploadtestserver serves media.UploadHandler over a fresh MinIO/RGW bucket for the browser SDK's integration tests (sdk/upload/test).
videotest
Package videotest builds synthetic videos whose frames identify their time and orientation, and classifies decoded pixels, for poster and preview tests.
Package videotest builds synthetic videos whose frames identify their time and orientation, and classifies decoded pixels, for poster and preview tests.
wirets
Package wirets renders the upload API wire types as TypeScript for the browser SDK (sdk/upload/src/wire.gen.ts).
Package wirets renders the upload API wire types as TypeScript for the browser SDK (sdk/upload/src/wire.gen.ts).
Package layout defines media object keys, dependency-free so the access worker can classify paths without importing the media runtime:
Package layout defines media object keys, dependency-free so the access worker can classify paths without importing the media runtime:
Package s3 implements media.Store over aws-sdk-go-v2 for Ceph RGW (production) and MinIO (tests).
Package s3 implements media.Store over aws-sdk-go-v2 for Ceph RGW (production) and MinIO (tests).
Package tiered is an optional visibility policy: it maps an item's level to entitlement keys and asks a Checker which ones the actor holds.
Package tiered is an optional visibility policy: it maps an item's level to entitlement keys and asks a Checker which ones the actor holds.
Package token signs and verifies media access tokens, shared by the host signer and the access worker so the format cannot drift:
Package token signs and verifies media access tokens, shared by the host signer and the access worker so the format cannot drift:
Package video encodes an item's video files with ffmpeg into a byte-range HLS ladder (one single-file fMP4 blob per rendition and audio track), WebVTT subtitles, a thumbnail sprite and one muxed MP4 download per quality, and records them in the manifest's hls and downloads.
Package video encodes an item's video files with ffmpeg into a byte-range HLS ladder (one single-file fMP4 blob per rendition and audio track), WebVTT subtitles, a thumbnail sprite and one muxed MP4 download per quality, and records them in the manifest's hls and downloads.
Package worker is the media worker: the one process that does all media work, from the host's worker River schema (Config.Schema) in its database.
Package worker is the media worker: the one process that does all media work, from the host's worker River schema (Config.Schema) in its database.
Package workqueue is the host's side of the media worker (media/worker): the River schema it drains in the host database, insert-only enqueueing and cancelling of its jobs, and processing progress for the read API.
Package workqueue is the host's side of the media worker (media/worker): the River schema it drains in the host database, insert-only enqueueing and cancelling of its jobs, and processing progress for the read API.
metrics
Package metrics exports the host's media worker queue health.
Package metrics exports the host's media worker queue health.

Jump to

Keyboard shortcuts

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