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
- Variables
- func Encoded(f File) bool
- func InsertOnce(ctx context.Context, insert InsertFunc, args RerunArgs, o river.InsertOpts) error
- func NewInlineName() string
- func NewUploadName() string
- func SHA256Name(sum []byte) string
- func SlotOutput(slot string, width int) string
- func UploadHandler(u *Uploads, o UploadHandlerOptions) http.Handler
- func WaitFor(ctx context.Context, c *river.Client[pgx.Tx], id int64) error
- type Animation
- type Aspect
- type AudioTrack
- type Capabilities
- type CommitBody
- type CommitFile
- type CommitReply
- type CompleteReply
- type CopyOptions
- type Crop
- type Deletion
- type Delivery
- type DeliveryMode
- type Dims
- type Download
- type DownloadInfo
- type Edit
- type EncodeProgress
- type EncodeStatus
- type ErrorDetails
- type ErrorReply
- type Exposure
- type ExposurePolicy
- type File
- type FileFailure
- type FileInfo
- type FilesBody
- type FilesReply
- type Fit
- type FolderNotEmptyError
- type FrameGrabber
- type GetOptions
- type Grant
- func (g *Grant) Allowed(i int) bool
- func (g *Grant) AudioPlaylist(file, id string) ([]byte, error)
- func (g *Grant) Cookie() *http.Cookie
- func (g *Grant) DownloadURL(ctx context.Context, key string) (name, u string, err error)
- func (g *Grant) Editor() bool
- func (g *Grant) Full() bool
- func (g *Grant) MasterPlaylist(file string, o MasterOptions) ([]byte, error)
- func (g *Grant) SpriteVTT(file string) ([]byte, error)
- func (g *Grant) SubtitlePlaylist(file, id string) ([]byte, error)
- func (g *Grant) URL(i int, blob string) (string, error)
- func (g *Grant) VideoPlaylist(file string, rung int) ([]byte, error)
- type HLS
- type HandlerOptions
- type Hooks
- type HostQueue
- type Identity
- type ImageError
- type IngestRequest
- type IngestResult
- type IngestUpload
- type InsertFunc
- type Item
- func (i Item) Blob(name string) (string, error)
- func (i Item) BlobsPrefix() string
- func (i Item) EditorBlob(name string) (string, error)
- func (i Item) EditorPrefix() string
- func (i Item) ExposureRecord() string
- func (i Item) Gated(slot string) bool
- func (i Item) Inline(name string) bool
- func (i Item) Kind() Kind
- func (i Item) ManifestKey() (string, error)
- func (i Item) ManifestsPrefix() string
- func (i Item) Original(name string) (string, error)
- func (i Item) OriginalsPrefix() string
- func (i Item) Poster() Slot
- func (i Item) Prefix() string
- func (i Item) Public(name string) (string, error)
- func (i Item) PublicPrefix() string
- func (i Item) Ref() contentref.ContentRef
- func (i Item) SlotOriginal(slot string) (string, error)
- func (i Item) SlotOutput(slot string, width int) (string, error)
- func (i Item) SlotPublic(slot string, width int) (string, error)
- func (i Item) SlotRecord(slot string) (string, error)
- func (i Item) StagingPrefix() string
- type Jobs
- func (j *Jobs) DeleteItemsTx(ctx context.Context, tx pgx.Tx, items ...Deletion) error
- func (j *Jobs) EraseUserTx(ctx context.Context, tx pgx.Tx, tenant, userID string, items ...Deletion) error
- func (j *Jobs) Insert(ctx context.Context, args river.JobArgs, o *river.InsertOpts) (*rivertype.JobInsertResult, error)
- func (j *Jobs) InsertTx(ctx context.Context, tx pgx.Tx, args river.JobArgs, o *river.InsertOpts) (*rivertype.JobInsertResult, error)
- func (j *Jobs) Publish(ctx context.Context, ref contentref.ContentRef) error
- func (j *Jobs) PublishTx(ctx context.Context, tx pgx.Tx, refs ...contentref.ContentRef) error
- func (j *Jobs) Purge(ctx context.Context, d Deletion) error
- func (j *Jobs) Queue() string
- func (j *Jobs) Register(fn func(*river.Config) error) error
- func (j *Jobs) RiverJobs() riverhelpers.Contribution
- func (j *Jobs) ScheduleSweep(ctx context.Context, ref contentref.ContentRef) error
- func (j *Jobs) Sweep(ctx context.Context, ref contentref.ContentRef) (SweepResult, error)
- func (j *Jobs) SweepAll(ctx context.Context) error
- func (j *Jobs) SweepOrphans(ctx context.Context, s OrphanSweep) (OrphanReport, error)
- type JobsConfig
- type Kind
- type Limit
- type Locker
- type Manifest
- type ManifestOptions
- type Manifests
- func (m *Manifests) Create(ctx context.Context, ref contentref.ContentRef) (*Manifest, error)
- func (m *Manifests) Edit(ctx context.Context, ref contentref.ContentRef, fn func(*Manifest) error) (*Manifest, error)
- func (m *Manifests) Exposure(ctx context.Context, ref contentref.ContentRef) (Exposure, error)
- func (m *Manifests) Get(ctx context.Context, ref contentref.ContentRef) (*Manifest, string, error)
- func (m *Manifests) Place(ctx context.Context, ref contentref.ContentRef, s Staged) (string, error)
- func (m *Manifests) Slot(ctx context.Context, ref contentref.ContentRef, slot string) (*SlotRecord, error)
- func (m *Manifests) SlotManifest(ctx context.Context, urls OutputURLs, ref contentref.ContentRef, slot string) (SlotManifest, error)
- func (m *Manifests) UpdateSlot(ctx context.Context, ref contentref.ContentRef, slot string, ...) error
- func (m *Manifests) VideoImages(ctx context.Context, urls OutputURLs, ref contentref.ContentRef, uploader bool, ...) (VideoImages, error)
- type MasterOptions
- type Multipart
- type MultipartReply
- type Object
- type Op
- type OrphanFolder
- type OrphanReport
- type OrphanSweep
- type OutputURLs
- type PGLimiter
- type PGLimits
- type Part
- type PartBody
- type PartReply
- type PartRequest
- type PartsBody
- type PartsReply
- type PosterFrame
- type PosterManifest
- type PosterRequest
- type PosterSelection
- type PresignBody
- type PresignPut
- type PresignReply
- type PresignRequest
- type Presigned
- type PresignedPart
- type PresignedRequest
- type ProcessCanceler
- type ProcessJob
- type ProcessQueue
- type ProgressSource
- type PutOptions
- type ReadOptions
- type ReadResult
- type Reader
- func (r *Reader) EditorURLs(ref contentref.ContentRef) (OutputURLs, error)
- func (r *Reader) Grant(ctx context.Context, ref contentref.ContentRef, actor access.Actor) (*Grant, error)
- func (r *Reader) Handler(o HandlerOptions) http.Handler
- func (r *Reader) ListedSlot(ref contentref.ContentRef, slot string, aspect Aspect) (SlotManifest, error)
- func (r *Reader) PublicURL(ref contentref.ContentRef, name string) (string, error)
- func (r *Reader) Read(ctx context.Context, ref contentref.ContentRef, actor access.Actor, ...) (*ReadResult, error)
- func (r *Reader) Slot(ctx context.Context, ref contentref.ContentRef, actor access.Actor, ...) (SlotManifest, error)
- func (r *Reader) VideoImages(ctx context.Context, ref contentref.ContentRef, actor access.Actor) (VideoImages, error)
- type ReaderOptions
- type RefBody
- type Registry
- type Rendition
- type RequestReply
- type RerunArgs
- type Reservation
- type Segment
- type Settlement
- type Slot
- type SlotBody
- type SlotEditBody
- type SlotFromFile
- type SlotFromFileBody
- type SlotImage
- type SlotManifest
- type SlotRecord
- type SlotRefBody
- type SlotRendition
- type SlotResult
- type Spec
- type Sprite
- type Staged
- type Store
- type Subtitle
- type SweepResult
- type SweepScheduler
- type TicketBody
- type UploadAuthorizer
- type UploadError
- type UploadGrant
- type UploadHandlerOptions
- type UploadLimiter
- type UploadOptions
- type UploadedObject
- type Uploads
- func (u *Uploads) Abort(ctx context.Context, actor access.Actor, sealed string) error
- func (u *Uploads) Commit(ctx context.Context, actor access.Actor, ref contentref.ContentRef, ops []Op) (*Manifest, error)
- func (u *Uploads) CommitSlot(ctx context.Context, actor access.Actor, ref contentref.ContentRef, ...) error
- func (u *Uploads) Complete(ctx context.Context, actor access.Actor, sealed string) (UploadedObject, error)
- func (u *Uploads) EditSlot(ctx context.Context, actor access.Actor, ref contentref.ContentRef, ...) error
- func (u *Uploads) Frame(ctx context.Context, actor access.Actor, ref contentref.ContentRef, ...) ([]byte, error)
- func (u *Uploads) Ingest(ctx context.Context, actor access.Actor, req IngestRequest) (IngestResult, error)
- func (u *Uploads) ListParts(ctx context.Context, actor access.Actor, sealed string) ([]Part, error)
- func (u *Uploads) Presign(ctx context.Context, actor access.Actor, r PresignRequest) (Presigned, error)
- func (u *Uploads) PresignParts(ctx context.Context, actor access.Actor, sealed string, parts []PartRequest) ([]PresignedPart, error)
- func (u *Uploads) SetSlotFromFile(ctx context.Context, actor access.Actor, r SlotFromFile) error
- func (u *Uploads) SetVideoPoster(ctx context.Context, actor access.Actor, ref contentref.ContentRef, ...) error
- func (u *Uploads) SlotOriginal(ctx context.Context, actor access.Actor, ref contentref.ContentRef, ...) (io.ReadCloser, Object, error)
- type Variant
- type Video
- type VideoImages
- type VideoImagesBody
- type VideoInfo
- type VideoPosterBody
- type ViewerLimit
Constants ¶
const ( HLSContentType = "application/vnd.apple.mpegurl" VTTContentType = "text/vtt; charset=utf-8" )
Playlist content types.
const ( IngestPartSize = 32 << 20 IngestConcurrency = 3 )
Ingest defaults: parts are buffered in memory, so memory is about (IngestConcurrency+1) × IngestPartSize.
const ( AreaManifest = layout.AreaManifest AreaOriginals = layout.AreaOriginals AreaBlobs = layout.AreaBlobs AreaPublic = layout.AreaPublic AreaEditor = layout.AreaEditor AreaStaging = layout.AreaStaging )
Folder areas.
const ( VideoLive = "" VideoAnimation = "animation" )
Video.Profile values.
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.
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.
const ( AccessFull = "full" // every file; downloads AccessPreview = "preview" // files [0, preview_limit) plus teasers AccessNone = "none" // teasers only )
Access levels in ReadResult.
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 staging/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).
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.
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 // 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.
const ( FrameDefaultWidth = 320 FrameMaxWidth = 1280 )
Frame endpoint bounds.
const ( PosterSourceFrame = "frame" PosterSourceUpload = "upload" PosterSourceAuto = "auto" )
Poster sources.
const CookieName = token.CookieName
CookieName is the access worker's cookie.
const DefaultQueue = "contentkit_media"
DefaultQueue is JobsConfig.Queue's default.
const ItemProgressKey = ""
ItemProgressKey carries Item in a job's reported map.
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.
const StartRung = 1080
StartRung is the short side of the variant listed first: native HLS players (Safari, iOS) start there before measuring.
const UserKind = "user"
UserKind is the kind of per-user folders ({tenant}/user/{id}/), erased by EraseUserTx.
Variables ¶
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 )
var ( ErrUnknownKind = errors.New("media: unknown kind") ErrType = errors.New("media: content type not allowed") ErrTooLarge = errors.New("media: file too large") )
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") )
var ( ErrNotFound = errors.New("media: object not found") ErrPreconditionFailed = errors.New("media: precondition failed") ErrNotModified = errors.New("media: not modified") )
var DefaultLadder = []int{2160, 1440, 1080, 720, 480}
DefaultLadder is the default H.264 ladder by short side.
var DefaultPosterWidths = []int{640, 960, 1280, 1920, 2560}
DefaultPosterWidths cover a full-width column at 2–3× density.
var EncodePhases = []string{PhaseQueued, PhaseDownloading, PhaseProbing, PhaseEncoding, PhaseMuxing, PhaseUploading, PhasePublishing, PhaseImages}
EncodePhases lists every phase in order.
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.
var ErrJobsNotBound = errors.New("media: River jobs are not composed into a client")
var ErrManifestConflict = errors.New("media: manifest edit kept conflicting")
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.
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.
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.
var PendingOnce = river.UniqueOpts{ByArgs: true, ByState: []rivertype.JobState{rivertype.JobStateAvailable, rivertype.JobStatePending, rivertype.JobStateRunning, rivertype.JobStateRetryable, rivertype.JobStateScheduled}}
PendingOnce dedupes a job per args while one is waiting or running.
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".
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 Encoded ¶ added in v0.26.0
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 ¶
SHA256Name names content-addressed files: "sha256-{hex}".
func SlotOutput ¶ added in v0.20.0
SlotOutput names a slot's output of one width: "{slot}_{width}".
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 (204 for an inline image) POST /commit-slot-from-file SlotFromFileBody -> SlotManifest POST /edit-slot SlotEditBody -> SlotManifest re-edit the committed original POST /slot SlotRefBody -> SlotManifest POST /slot-original SlotRefBody -> the committed original's bytes (editor) 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)
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).
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
AspectOf is the ratio of a w×h size, reduced (native for an empty size).
func ParseAspect ¶ added in v0.40.0
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
Ratio parses "W:H" and panics on an invalid one (for constants in code).
func (Aspect) Height ¶ added in v0.40.0
Height is width's height at a, rounded half up, at least 1 (0 when native).
func (Aspect) MarshalText ¶ added in v0.40.0
MarshalText writes "W:H" (native: empty), so JSON and config carry the string.
func (*Aspect) UnmarshalText ¶ added in v0.40.0
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.
type CommitBody ¶
type CommitFile ¶
type CommitReply ¶
type CommitReply struct {
Files []CommitFile `json:"files"`
}
CommitReply is the committed file order.
type CompleteReply ¶
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 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
Dims is a source's size with EXIF orientation applied.
func PosterFrameSize ¶ added in v0.26.0
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 DownloadInfo ¶
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
Check validates the edit's shape and, with a known size (w, h > 0), that the crop lies inside it.
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: a file with rungs above 1080 plays after stage 1 while
// stage 2 encodes them.
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 Exposure ¶ added in v0.33.0
type Exposure struct {
Poster bool `json:"poster"`
}
Exposure is what a video item publishes to public/, where anyone can fetch it without a token. Its poster is rendered to editor/ and copied to public/ only as its Exposure allows.
func DefaultExposure ¶ added in v0.33.0
func DefaultExposure(_ context.Context, _ contentref.ContentRef, res access.Resolution) Exposure
DefaultExposure publishes nothing for an item anonymous viewers cannot see (a draft, a deleted item) and the poster of every visible one (for paid or preview-cut items, as their teaser).
type ExposurePolicy ¶ added in v0.33.0
type ExposurePolicy func(ctx context.Context, ref contentref.ContentRef, anonymous access.Resolution) Exposure
ExposurePolicy decides an item's Exposure from its anonymous Resolution; hosts may vary it per item (a paid post's teaser, say).
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"`
// 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/; variants, HLS and downloads in blobs/, EditorOnly variants in editor/. 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
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
FailureKey identifies the source and edit a Failure applies to.
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
Locked bool `json:"locked,omitempty"`
HLS bool `json:"hls,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 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 FolderNotEmptyError ¶ added in v0.45.0
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 ¶
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 ¶
Allowed reports whether file i is served to this viewer: within the preview cut, or a teaser of a visible item.
func (*Grant) AudioPlaylist ¶
AudioPlaylist is the byte-range media playlist of one audio track.
func (*Grant) Cookie ¶
Cookie is the folder cookie to set in cookie mode with full access, else nil.
func (*Grant) DownloadURL ¶
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
Editor reports an editor's grant: EditorOnly variants are signed.
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, with alternative audio and subtitle groups.
func (*Grant) SpriteVTT ¶
SpriteVTT is the seek-preview track: one cue per sprite tile, each pointing at its tile with a #xywh fragment.
func (*Grant) SubtitlePlaylist ¶
SubtitlePlaylist is a one-segment playlist over the whole WebVTT blob.
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"`
Sprite *Sprite `json:"sprite,omitempty"`
// Pending lists the rungs of a later encode stage: 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 that a slot's outputs are written, with their
// aspect (for native slots, whose shape follows the image): hosts record
// it to list the slot with Reader.ListedSlot without reads.
SlotEncoded func(ctx context.Context, ref contentref.ContentRef, slot string, aspect Aspect)
}
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 video item's Publish after its poster changes and 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) Publish ¶ added in v0.43.0
func (h *HostQueue) Publish(ctx context.Context, ref contentref.ContentRef) error
Publish enqueues the host's Publish of a video item.
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 ¶
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:
{tenant}/{kind}/{content_id}/manifest.json | manifests/{version}.json
/originals/{sha256-hex | slot | slot.json | i-uuid}
/staging/{u-uuid} multipart uploads until placed
/blobs/{sha256-hex | u-uuid} viewers (folder or file token)
/editor/{sha256-hex | output.webp | .mp4} editors only (editor token)
/public/{output}.webp | .mp4 anyone
func (Item) BlobsPrefix ¶
func (Item) EditorBlob ¶ added in v0.33.0
EditorBlob is the key of an EditorOnly variant: outside the viewers' blobs/, so no viewer token covers it.
func (Item) EditorPrefix ¶ added in v0.33.0
func (Item) ExposureRecord ¶ added in v0.33.0
ExposureRecord is originals/exposure.json, the Exposure last published.
func (Item) Gated ¶ added in v0.33.0
Gated reports a slot whose outputs are published per the item's Exposure rather than on encode: a video kind's poster.
func (Item) Inline ¶ added in v0.20.0
Inline reports whether name is an inline image id the kind accepts.
func (Item) ManifestKey ¶
ManifestKey is manifest.json, or manifests/{version}.json for versioned kinds.
func (Item) ManifestsPrefix ¶
ManifestsPrefix lists every manifest of a versioned kind.
func (Item) Original ¶
Original is the key of an uploaded file or master (never served): originals/sha256-{hex}, or staging/u-{uuid} for a multipart upload the worker has not placed yet.
func (Item) OriginalsPrefix ¶
func (Item) PublicPrefix ¶
func (Item) Ref ¶
func (i Item) Ref() contentref.ContentRef
func (Item) SlotOriginal ¶
SlotOriginal is the original of a registered public slot, overwritten in place, or of an inline image.
func (Item) SlotOutput ¶ added in v0.20.0
SlotOutput is where the image job writes a slot's output of one width: public/{slot}_{width}.webp, or editor/ for a Gated slot, which Publish copies to public/ only as the item's Exposure allows.
func (Item) SlotPublic ¶ added in v0.33.0
SlotPublic is public/{slot}_{width}.webp, the output's tokenless URL key.
func (Item) SlotRecord ¶ added in v0.20.0
SlotRecord is originals/{slot}.json, a registered slot's record (SlotRecord).
func (Item) StagingPrefix ¶ added in v0.40.0
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 ¶
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) Insert ¶
func (j *Jobs) Insert(ctx context.Context, args river.JobArgs, o *river.InsertOpts) (*rivertype.JobInsertResult, error)
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) Publish ¶ added in v0.33.0
func (j *Jobs) Publish(ctx context.Context, ref contentref.ContentRef) error
Publish brings a video item's public/ poster to its Exposure: it resolves the item for an anonymous actor, applies JobsConfig.Exposure, copies the allowed outputs from editor/ and deletes the rest. It re-resolves after writing and repeats until the Exposure holds, so a publish racing a visibility change ends at the newer one. Other kinds are left alone. Without JobsConfig.Resolver nothing is public.
func (*Jobs) PublishTx ¶ added in v0.33.0
func (j *Jobs) PublishTx(ctx context.Context, tx pgx.Tx, refs ...contentref.ContentRef) error
PublishTx enqueues Publish for each item in the host's transaction. Call it whenever what anonymous viewers may see of an item changes: publish, unpublish, soft delete, restore, a price or access change.
func (*Jobs) Purge ¶ added in v0.45.0
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 ¶
Queue is the shared media queue; registered workers may use it or add their own.
func (*Jobs) Register ¶
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 the item folder's blobs/, editor/ blobs, hash-named originals/ and staged uploads (staging/) that no manifest in the folder references, once every manifest is older than the grace period, and only objects past abandonedAt. Slot originals, slot and outputs (public/, editor/) and manifests are never swept.
Invariant: the sweep deletes only objects that no manifest references and that no in-flight commit 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 abandonedAt (see protected).
func (*Jobs) SweepAll ¶
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
// 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
// Resolver decides, with an anonymous actor, what of a video item is
// public (Publish); without it no poster is published.
Resolver access.ContentResolver
// Exposure maps that anonymous resolution to what is published; default
// DefaultExposure (drafts nothing, free items all, others the poster).
Exposure ExposurePolicy
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
Slots map[string]Slot // public slot name → outputs
// 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
// 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.
type Locker ¶
Locker serializes manifest edits on backends without conditional PUT.
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 version. List order is display order. Blob references are names within the item's folder.
func (*Manifest) Attached ¶ added in v0.43.0
Attached is the manifest without its unattached files and their video downloads ("{file}-{rung}p"), as readers see it; m itself when it has none.
func (*Manifest) EditorBlobs ¶ added in v0.33.0
EditorBlobs returns every editor/ name the manifest references (EditorOnly variants).
func (*Manifest) OriginalBytes ¶
OriginalBytes is the storage charged for a manifest: the sizes of its distinct originals. Deleting or erasing an item releases it.
type ManifestOptions ¶
type ManifestOptions struct {
// Locker is required when the store lacks ConditionalPut; edits then run
// under it and write unconditionally. 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 the current 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. An unchanged manifest is not written. A folder's first manifest is refused (ErrFolderNotEmpty) over a previous item's blobs.
func (*Manifests) Exposure ¶ added in v0.33.0
func (m *Manifests) Exposure(ctx context.Context, ref contentref.ContentRef) (Exposure, error)
Exposure is the item's published Exposure (zero before the first publish).
func (*Manifests) Get ¶
func (m *Manifests) Get(ctx context.Context, ref contentref.ContentRef) (*Manifest, string, error)
Get returns the manifest and its 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 (staging/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:
- copy staging → 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);
- rename every reference in the folder's manifests (original, master, hls source, download inputs), one conditional edit per manifest;
- 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) 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's manifest (one object), building output URLs with urls. A slot never committed has no outputs, nor has a gated slot the caller may not see.
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) and writes it like Edit: conditionally, re-running fn on conflict, and only when changed.
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 ¶
Multipart carries the opaque ticket for PresignParts, ListParts, Complete and Abort, and the part-size bounds the client adapts within.
type MultipartReply ¶
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 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
EditorToken string // the item's editor/ folder token; editors only
Exposure Exposure // what the item has published
}
OutputURLs builds slot output URLs for one caller. Outputs of gated slots (video posters) are served from editor/ with EditorToken to editors, from public/ to others when Exposure publishes them, and left out otherwise.
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 ¶
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 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 ¶
PartRequest is one part to presign: its exact length and SHA-256.
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 ¶
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 ¶
PresignedRequest is a request a browser sends as is, with exactly Header.
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
}
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 (EditorOnly ones only for editors). 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/{height}.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) ListedSlot ¶ added in v0.40.0
func (r *Reader) ListedSlot(ref contentref.ContentRef, slot string, aspect Aspect) (SlotManifest, error)
ListedSlot is a slot's manifest built without reads, for listings: every rung at its fixed URL (the image job renders each one, capped at the edited width, so none is missing once the slot is set). W is the rung, an upper bound; H follows aspect: the slot's, or for a native slot the one the host recorded from Hooks.SlotEncoded (unknown: 0). Hosts list it only for slots they know are set, and for gated slots only when the item publishes them.
func (*Reader) PublicURL ¶
func (r *Reader) PublicURL(ref contentref.ContentRef, name string) (string, error)
PublicURL is the plain URL of a public slot output or inline image; it reads nothing.
func (*Reader) Read ¶
func (r *Reader) Read(ctx context.Context, ref contentref.ContentRef, actor access.Actor, o ReadOptions) (*ReadResult, error)
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's manifest: ErrNotVisible for an item actor may not see. A video poster is listed from editor/ for editors, and from public/ for others once the item publishes it.
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 get it from editor/, others what the item has published (see Exposure).
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
// 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 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 ¶
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 manifest, and refused for an unversioned kind.
type Rendition ¶
type Rendition struct {
Rung int `json:"rung"`
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.
func FrameRendition ¶ added in v0.26.0
FrameRendition is the rendition poster frames are grabbed from: the widest.
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 Reservation ¶
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 Segment ¶
Segment is one EXT-X-BYTERANGE segment, encoded as [offset, length, seconds].
func (Segment) MarshalJSON ¶
func (*Segment) UnmarshalJSON ¶
type Settlement ¶
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 {slot}_{width}.webp (SlotOutput), rewritten in place on every change. Nothing is upscaled: a rung wider than the edited image is rendered at the edited width, so every rung always exists once the slot is set and listings can link them without reads. Its original is kept at originals/{slot} and its Edit in the slot record. An edit narrower than Min fails.
func (Slot) Hash ¶ added in v0.20.0
Hash is the slot spec's stable identity; outputs under another are stale.
func (Slot) Height ¶ added in v0.20.0
Height is the output height of a width at Aspect (0 for a native slot).
func (Slot) Min ¶ added in v0.20.0
Min is the narrowest edited width accepted: MinWidth, else the smallest width.
func (Slot) OutputWidth ¶ added in v0.40.0
OutputWidth is the width rendered for rung from an image edited pixels wide: the rung, or edited when narrower (never upscaled).
type SlotBody ¶
type SlotBody struct {
Ref RefBody `json:"ref"`
Slot string `json:"slot"`
SHA256 string `json:"sha256"`
Edit *Edit `json:"edit,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 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 {
Name string `json:"name"`
W int `json:"w"`
H int `json:"h"`
URL string `json:"url"`
}
SlotImage is one produced output.
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"`
}
SlotManifest describes a slot: its outputs by ascending width. Outputs are rewritten in place at fixed URLs, served no-cache with an ETag, so a new crop shows on the next revalidation.
type SlotRecord ¶ added in v0.20.0
type SlotRecord struct {
Original string `json:"original"` // ETag of originals/{slot} when committed
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 originals/{slot}.json, private like the original. Commits and edits set Original 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 SlotRendition ¶ added in v0.40.0
SlotRendition is one output: the rung it is stored under 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 (ETag) 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 served outputs were derived from.
type Spec ¶
type Spec struct {
Width int
Height int
Fit Fit
Quality int
Blur float64
// Unedited ignores the file's Edit: an editor's view of the whole source.
// It must be EditorOnly.
Unedited bool
// EditorOnly variants are signed by the read API only for actors whose
// Resolution is Editor; slots, inline images and zips cannot use them.
EditorOnly bool
}
Spec describes one derived image. Zero Width and Height keep full resolution.
type Staged ¶ added in v0.40.0
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)
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
}
Store is the bucket. Keys are built by Item; implementations do not interpret them.
type SweepResult ¶
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.
type UploadGrant ¶
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 is the sweep's (JobsConfig.Grace): the Manifests' Sweeps when it
// is *Jobs, else 24h.
Grace 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 ¶
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) 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, ref contentref.ContentRef, slot string, sum []byte, edit *Edit) error
CommitSlot validates an uploaded slot or inline original and enqueues the re-encode of its outputs. A registered slot records it with edit (nil: the centred crop at the slot's Aspect); its crop's height follows its width. Inline images take no edit. sum is the SHA-256 the upload was presigned with.
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) 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
SetSlotFromFile makes an image file the slot's original: its source is copied to originals/{slot} and recorded with the edit, and the slot's outputs are re-encoded 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.
func (*Uploads) SlotOriginal ¶ added in v0.20.0
func (u *Uploads) SlotOriginal(ctx context.Context, actor access.Actor, ref contentref.ContentRef, slot string) (io.ReadCloser, Object, error)
SlotOriginal opens a slot's committed original for its uploaders (the editor); originals are never public.
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. 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).
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
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 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.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package accessworker is the media access worker's HTTP handler, run by cmd/media-access: it checks the token for a blobs/ or editor/ path (URL `?t=` or cookie `mt`), serves public/ paths without one, refuses manifests and originals/, 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 blobs/ or editor/ path (URL `?t=` or cookie `mt`), serves public/ paths without one, refuses manifests and originals/, and streams the object from the private bucket with its own read-only key. |
|
Package image derives WebP variants, public slots and zip downloads with libvips (CGO).
|
Package image derives WebP variants, public slots 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 River schema workqueue.Schema in the host database.
|
Package worker is the media worker: the one process that does all media work, from River schema workqueue.Schema in the host 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. |