photos

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Nov 13, 2026 License: MIT Imports: 22 Imported by: 0

README

photos-go

The Go client library for the Photos.sh API: image tools for product photos. Remove backgrounds, resize and compress, upscale, erase objects and text, check photos against marketplace rules and fix them, and read barcodes, text and label details. Also: enhance, crop, watermark, expand, vectorize, denoise and colorize photos, select an object at a point, find, hide and restore faces, frame identity photos, list colours and write captions.

  • Standard library only: no dependencies.
  • Uploads are streamed, from files, bytes or any io.Reader.
  • Every call takes a context.Context.
  • Go 1.23 or later.

Install

go get github.com/photos-sh/photos-go
import photos "github.com/photos-sh/photos-go"

Quick start

package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	// An empty key means: use the PHOTOS_API_KEY environment variable.
	client, err := photos.New("")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	img, err := client.RemoveBackground(ctx, photos.File("shoe.jpg"), nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(img.ContentType, img.Width, img.Height)
	if err := img.Save("shoe.png"); err != nil {
		log.Fatal(err)
	}
}

Images in, images out

An image argument is a photos.Input:

photos.File("shoe.jpg")              // a path; opened when the request is sent
photos.Bytes("shoe.jpg", data)       // bytes in memory; the name may be ""
photos.Reader("shoe.jpg", r)         // any io.Reader; read to its end, not closed

Methods that change a photo return a *photos.Image:

img.Bytes        // the encoded image
img.ContentType  // "image/png", "image/jpeg", "image/webp", "image/avif"
img.Width        // pixels
img.Height
img.Quality      // 1–100; 0 for formats without one (PNG)
img.ServerTiming // the raw Server-Timing header
img.SubjectFound // *bool, from Crop and RemoveBackground; nil means not known
img.Header       // every response header
img.Save(path)   // write it to a file
img.Extension()  // ".png", ".jpg", ".webp", ".avif", ".svg"

Some methods report something beside the image (the faces hidden, a score). Their result holds an Image, so it has all of the above too.

Each method takes a pointer to its own options struct, which may be nil. A field left at its zero value is not sent, and the API uses its default. For most options the zero means nothing else: quality, sizes and scale start at 1, a setting of 0 changes nothing, and most booleans default to false.

A few options have a zero that is a value of its own: a watermark margin of 0, a trim tolerance of 0, an automatic correction turned off. Those fields are pointers. nil is not sent, and photos.Int, photos.Float and photos.Bool make one:

&photos.WatermarkOptions{Text: "Acme", Margin: photos.Float(0)}

Methods

In the examples, ctx is a context.Context and client a *photos.Client.

Remove the background
img, err := client.RemoveBackground(ctx, photos.File("shoe.jpg"), &photos.RemoveBackgroundOptions{
	Background: "white",          // or "#rrggbb"; empty keeps transparency
	Crop:       true,             // crop to the subject
	Padding:    40,               // margin when cropping, in pixels
	Format:     photos.FormatJPEG, // default PNG
	Quality:    90,
	// Mask: true returns the greyscale alpha mask instead.
})

The cutout can be staged in the same call: on a canvas, with a shadow or a reflection, in front of a colour, an image or the photo's own blurred background. Give at most one of Background, BackgroundImage and Blur.

img, err := client.RemoveBackground(ctx, photos.File("shoe.jpg"), &photos.RemoveBackgroundOptions{
	Width:           1600,                      // a canvas; give Height too
	Height:          1600,
	Fill:            0.8,                       // how much of it the subject may take (default 0.85)
	Align:           "bottom",                  // "center" (default), "top", "bottom"
	Shadow:          "contact",                 // or "drop"
	Reflection:      true,                      // needs room below: a Fill under 1
	BackgroundImage: photos.File("studio.jpg"), // or Background: "white", or Blur: 40
})
if img.SubjectFound != nil && !*img.SubjectFound {
	// the photo has no subject: the canvas is empty
}
Resize, convert, compress
img, err := client.Resize(ctx, photos.File("photo.jpg"), &photos.ResizeOptions{
	Width:    1600,
	Height:   1600,
	Fit:      photos.FitContain, // FitContain (default), FitCover, FitFill
	Enlarge:  false,             // allow scaling up with contain
	Format:   photos.FormatWebP,
	Quality:  82,
	MaxBytes: 300_000,           // lower the quality until it fits
	// Background: "white" flattens transparency.
})

It can also cut away an even border first, and write a print resolution:

img, err := client.Resize(ctx, photos.File("scan.png"), &photos.ResizeOptions{
	Trim:          true,          // whatever matches the top-left pixel
	TrimTolerance: photos.Int(0), // 0–255, default 10; 0 is the exact colour only
	DPI:           300,           // written into a JPEG or PNG; no pixel changes
})
Upscale
img, err := client.Upscale(ctx, photos.File("small.jpg"), &photos.UpscaleOptions{
	Scale: 2, // 1 to 4, default 4
})
Erase objects

Give the areas as rectangles, as a mask (an image the size of the photo, white where to remove), or both.

img, err := client.Erase(ctx, photos.File("room.jpg"), &photos.EraseOptions{
	Rects: []photos.Rect{{X: 40, Y: 60, Width: 200, Height: 120}},
	Mask:  photos.File("room-mask.png"),
})
Remove text, watermarks and captions

By default only text off the product is removed; labels printed on it stay.

res, err := client.RemoveText(ctx, photos.File("listing.jpg"), &photos.RemoveTextOptions{
	Region:   photos.RegionBackground, // or photos.RegionAll
	Matching: `sale|€|www\.`,          // only lines matching this regular expression
})
fmt.Println(res.Removed, res.Kept) // lines painted out, lines left
err = res.Save("listing-clean.jpg") // res has everything an Image has
Enhance light and colour

Without options it stretches the tones to the full range and takes out a colour cast. With any setting, only what you give is applied.

img, err := client.Enhance(ctx, photos.File("dull.jpg"), nil)

img, err = client.Enhance(ctx, photos.File("dull.jpg"), &photos.EnhanceOptions{
	Auto:       photos.Bool(true), // keep the automatic part beside your settings
	Brightness: 10,                // -100 to 100; so are Contrast, Saturation, Temperature
	Saturation: -15,
	Sharpen:    30,                // 0 to 100
	// Levels and WhiteBalance turn each automatic correction on or off: photos.Bool(false)
})
Crop to a shape

It finds the subject and crops the photo to a shape around it. The background stays.

img, err := client.Crop(ctx, photos.File("shoe.jpg"), &photos.CropOptions{
	Aspect: "4:5", // or "16:9", "1:1", "1.5"
	// Width: 800, Height: 1000 scale the crop to exactly that size.
})
if img.SubjectFound != nil && !*img.SubjectFound {
	// no subject was found: the crop is centred
}

// An avatar: tight around the face, with room for hair and chin.
avatar, err := client.Crop(ctx, photos.File("portrait.jpg"), &photos.CropOptions{
	Focus:   "face",            // "subject" (default) or "face"
	Zoom:    true,              // the smallest crop that holds it
	Padding: photos.Float(0.6), // room around it, 0–2 (default 0.1)
	Width:   400,
	Height:  400,
})
Add a watermark

Text or a logo, not both. Text is drawn with Latin, Greek and Cyrillic letters.

img, err := client.Watermark(ctx, photos.File("photo.jpg"), &photos.WatermarkOptions{
	Text:     "© Acme",
	Color:    "white",         // or "black", "#rrggbb"
	Position: "bottom-right",  // the default; "top-left", "top", "center", …
	Scale:    25,              // its width, as a percentage of the photo's
	Opacity:  50,              // 1–100
	Margin:   photos.Float(0), // percent of the shorter side, 0–50 (default 3)
})

img, err = client.Watermark(ctx, photos.File("photo.jpg"), &photos.WatermarkOptions{
	Watermark: photos.File("logo.png"), // a PNG with transparency works best
	Tile:      true,                    // repeat it across the whole photo
})
Expand the canvas
img, err := client.Expand(ctx, photos.File("shoe.jpg"), &photos.ExpandOptions{
	Aspect: "16:9", // or Left, Right, Top, Bottom, in pixels
	// Fill: "white" fills with a colour instead of continuing the photo.
})

The new area continues the photo, which suits plain and softly textured backgrounds. It does not invent content: a subject cut off at the edge stays cut off.

Trace into SVG

For logos, icons and flat drawings. A photograph traces into thousands of paths.

res, err := client.Vectorize(ctx, photos.File("logo.png"), &photos.VectorizeOptions{
	Mode:    "color",       // or "mono": one black shape
	Curves:  "spline",      // or "polygon", "pixel"
	Detail:  8,             // 1–8; lower merges similar shades
	Speckle: photos.Int(4), // drop areas smaller than this; 0 keeps everything
})
fmt.Println(res.Paths)     // how many paths the SVG has
err = res.Save("logo.svg") // res.Bytes is the SVG's text, res.ContentType "image/svg+xml"
Hide faces
res, err := client.BlurFaces(ctx, photos.File("street.jpg"), &photos.BlurFacesOptions{
	Mode: "pixelate", // "blur" (default), "pixelate" or "fill"
	// Strength: 50, Padding: photos.Float(0.15), Color: "black", MinScore: 0.6
})
fmt.Println(res.Faces) // how many faces were hidden

Check res.Faces before relying on the result: a small, turned or covered face may not be found, and then it still shows. Only "fill" leaves nothing of a face.

Frame an identity photo
res, err := client.IDPhoto(ctx, photos.File("portrait.jpg"), &photos.IDPhotoOptions{
	Document: "us-passport", // or WidthMm, HeightMm, HeadMinMm and HeadMaxMm
	// DPI: 300; Background: "white" or "document" replaces the background
})
fmt.Println(res.Passed, res.Findings, res.HeadMm) // false ["low_resolution"] 34

documents, err := client.IDPhotos(ctx) // []photos.IDPhotoDocument: Name, Title, WidthMm, HeightMm, …

IDPhoto keeps the photo's own background by default, because passport authorities do not accept edited photos. Findings lists what the tool could see is wrong: head_tilted, not_facing_camera, head_cut_off, not_enough_room, low_resolution, background_not_plain. It cannot judge expression, glasses, shadows or likeness, so an empty list is not an approval. A photo with no face, or more than one, is an ErrUnprocessable.

Remove noise
img, err := client.Denoise(ctx, photos.File("grainy.jpg"), &photos.DenoiseOptions{
	Strength: 80, // 1–100: how much of the cleaned result replaces the photo
})

Denoise is slow without a GPU and takes photos up to 12 megapixels.

Colour a black-and-white photo
img, err := client.Colorize(ctx, photos.File("grandparents.jpg"), &photos.ColorizeOptions{
	Strength: 80, // 1–100: lower is paler
})

Colorize guesses colours: skin, sky and grass come out believable, the colour of clothes or cars is invented.

Select an object

Where RemoveBackground decides what the subject is, here you say: points on the object, points to keep out of it, or a box around it.

res, err := client.Select(ctx, photos.File("desk.jpg"), &photos.SelectOptions{
	Points:  []photos.Point{{X: 420, Y: 310}}, // on the object; more points add to it
	Exclude: []photos.Point{{X: 600, Y: 300}}, // must stay outside
	// Box: photos.Rect{X: 380, Y: 250, Width: 120, Height: 140}
	// Pick: "smallest" or "largest"; Crop: true; Padding: 10; Background: "white"
})
fmt.Println(res.Score) // how well the model thinks the mask fits, 0 to 1

With Mask: true the result is the mask, which Erase takes as its Mask to remove the object. Nothing found at the points is an ErrUnprocessable.

Restore faces
res, err := client.RestoreFaces(ctx, photos.File("old-family.jpg"), &photos.RestoreFacesOptions{
	Strength: 80, // 1–100: how much of the restored face replaces the original
})
fmt.Println(res.Faces, res.FacesSkipped) // restored; left alone, as sharp enough

RestoreFaces invents detail: do not use it where the face has to be exactly the person's, such as an identity document or evidence.

Run a preset or a recipe
img, err := client.Run(ctx, photos.File("shoe.jpg"), &photos.RunOptions{Preset: "marketplace"})

// Or a recipe of your own, as TOML:
img, err = client.Run(ctx, photos.File("shoe.jpg"), &photos.RunOptions{
	Recipe: "[output]\nformat = \"webp\"\nmax_side = 1600\n",
})

presets, err := client.Presets(ctx) // []photos.Preset{{Name, Description}}
Fix a photo for a marketplace
res, err := client.Fix(ctx, photos.File("shoe.jpg"), "amazon")
fmt.Println(res.Passed, res.Findings) // true, [] or false, ["text_overlay"]
err = res.Save("shoe-amazon.jpg")

Marketplaces: amazon, ebay, etsy, shopify, google, allegro, zalando. client.Marketplaces(ctx) returns each one's rules.

Check quality and marketplace rules
res, err := client.Check(ctx, photos.File("shoe.jpg"), &photos.CheckOptions{
	Marketplace: "amazon", // also: Subject: true, Text: true
})
fmt.Println(res.Metrics.Width, res.Metrics.Height, res.Metrics.Sharpness)
for _, f := range res.Findings {
	fmt.Println(f.Severity, f.Code, f.Message)
}
if v := res.Marketplace; v != nil {
	fmt.Println(v.Passed, v.Findings, v.NotChecked)
}
Read barcodes
res, err := client.ReadBarcodes(ctx, photos.File("box.jpg"), &photos.ReadBarcodesOptions{
	AllFormats: true, // adds EAN-8, UPC-E, Code 128/39/93, QR, Data Matrix, PDF417
})
for _, b := range res.Barcodes {
	fmt.Println(b.Format, b.Value) // b.GS1 is a *string: nil unless a valid retail code
}
Read text
res, err := client.ReadText(ctx, photos.File("label.jpg"))
for _, line := range res.Lines {
	fmt.Println(line.Text, line.Score, line.Box)
}
Read a label

One to eight photos of one product; text and codes from all of them are pooled.

res, err := client.ReadLabel(ctx, []photos.Input{
	photos.File("front.jpg"),
	photos.File("back.jpg"),
})
label := res.Label
fmt.Println(label.Headline, label.GTIN, label.Ingredients, label.Allergens)
if q := label.NetQuantity; q != nil {
	fmt.Println(q.Value, q.Unit)
}

Fields that were not found are zero: an empty string, a nil slice or a nil pointer.

Match a product in your catalogue

One to eight photos of one product, matched against one of your organisation's catalogues. A catalogue is a spreadsheet of your products (product_id, title, and where you have them brand, barcode, sku), uploaded on the console's Catalogues page. Catalogue matching is turned on per organisation: until it is on for yours and a catalogue is uploaded, the API answers 404 not_found. The catalogue's name may be left out when you have exactly one.

Each catalogue is one kind of product, chosen when it is uploaded: any product, food and drink, cosmetics, pharmacy and supplements, household and pet, electronics, or wine and spirits. Each kind also compares its own values with the label, so the right size or strength of a product sold in several is found.

Each candidate's Evidence says what agreed beyond the name: barcode, barcode_text, sku, brand, and by the catalogue's kind quantity, pack, spf, strength, count, spec, model, cask_number, age, age_weak, vintage, abv, size. Label holds the values read off the photos.

res, err := client.Identify(ctx,
	[]photos.Input{photos.File("front.jpg"), photos.File("back.jpg")},
	&photos.IdentifyOptions{Catalogue: "shoes", Top: 3})
for _, c := range res.Candidates {
	fmt.Println(c.ProductID, c.Title, c.Score, c.Evidence)
}
List the main colours
res, err := client.Palette(ctx, photos.File("shoe.png"), &photos.PaletteOptions{Colors: 5}) // 1–16
for _, c := range res.Colors {
	fmt.Println(c.Hex, c.RGB, c.Share) // #c81018 [200 16 24] 0.62
}

Transparent pixels do not count, so a cutout's palette is the subject's.

Find faces
res, err := client.FindFaces(ctx, photos.File("group.jpg"), &photos.FindFacesOptions{
	MinScore: 0.8, // how sure the model must be, 0.1–1 (default 0.6)
})
for _, f := range res.Faces {
	fmt.Println(f.Box.X, f.Box.Y, f.Box.Width, f.Box.Height, f.Score, f.Roll)
	fmt.Println(f.Landmarks.RightEye, f.Landmarks.LeftEye, f.Landmarks.Nose) // photos.Point{X, Y}
}

It finds faces; it does not recognise who anyone is. "Right" and "left" are the person's own, so RightEye is the one on the left of the photo.

Describe a photo
res, err := client.Caption(ctx, photos.File("shoe.jpg"), &photos.CaptionOptions{
	Detail: "short", // one sentence (default); "detailed"; "paragraph"
})
fmt.Println(res.Caption)

Caption can be wrong and is English only: read it before publishing it.

Account, marketplaces, health
acc, err := client.Account(ctx)
if acc.Unlimited {
	// a server key: not metered
} else if acc.Credits != nil {
	fmt.Println(*acc.Credits, acc.Prices["remove-background"])
}

marketplaces, err := client.Marketplaces(ctx) // Name, Title, Checked, Sources, Notes, Rules, Fix

status, err := client.Health(ctx) // "ok", "loading" or "failed"; sends no key
JSON results and new fields

JSON results are typed structs. Each also keeps the response as it came in a Raw field (json.RawMessage), so a field the API adds later can be read before this library knows it. Loosely specified objects (Marketplace.Rules, Marketplace.Fix, LabelResult.Barcodes) are map[string]any. A photos.Point is an [x, y] pair in JSON.

Errors

img, err := client.Upscale(ctx, photos.File("small.jpg"), nil)

var apiErr *photos.Error
var connErr *photos.ConnectionError
switch {
case err == nil:
	// use img
case errors.Is(err, photos.ErrPaymentRequired):
	// out of credits
case errors.Is(err, photos.ErrBusy):
	// already retried; apiErr.RetryAfter says when to try again
case errors.As(err, &apiErr):
	fmt.Println(apiErr.Status, apiErr.Code, apiErr.Message)
case errors.As(err, &connErr):
	fmt.Println("could not reach the API; timed out:", connErr.Timeout)
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
	// your context ended
default:
	// something local, such as a file that could not be read
}

An error response from the API is a *photos.Error with Status, Code, Message, RetryAfter and Header. Each code has a constant and a sentinel for errors.Is:

Code Status Constant Sentinel
bad_request 400 CodeBadRequest ErrBadRequest
unauthorized 401 CodeUnauthorized ErrUnauthorized
payment_required 402 CodePaymentRequired ErrPaymentRequired
account_suspended 403 CodeAccountSuspended ErrAccountSuspended
not_found 404 CodeNotFound ErrNotFound
payload_too_large 413 CodePayloadTooLarge ErrPayloadTooLarge
unsupported_format 415 CodeUnsupportedFormat ErrUnsupportedFormat
unprocessable 422 CodeUnprocessable ErrUnprocessable
internal 500 CodeInternal ErrInternal
busy 503 CodeBusy ErrBusy
model_unavailable 503 CodeModelUnavailable ErrModelUnavailable

A response that is not the API's JSON (from a proxy, say) is still a *photos.Error, with an empty Code and the start of the body as Message.

A failure to get a response is a *photos.ConnectionError; its Timeout field tells a timeout from a broken connection, and errors.Unwrap gives the cause. If your own context is cancelled or its deadline passes, the call returns context.Canceled or context.DeadlineExceeded instead.

Batch jobs

A job applies one operation to many images in the background.

// Images the API fetches…
job, err := client.Jobs.Create(ctx, &photos.JobRequest{
	Operation:  photos.OperationRemoveBackground,
	Params:     map[string]any{"format": "png", "crop": true},
	URLs:       []string{"https://example.com/photos/1.jpg", "https://example.com/photos/2.jpg"},
	WebhookURL: "https://example.com/hooks/photos", // optional
})

// …or images you upload (streamed, like any other upload).
job, err = client.Jobs.Create(ctx, &photos.JobRequest{
	Operation: photos.OperationRun,
	Params:    map[string]any{"preset": "marketplace"},
	Images:    []photos.Input{photos.File("1.jpg"), photos.File("2.jpg")},
})

Params are the operation's parameters under their HTTP names. Operations: OperationRun, OperationRemoveBackground, OperationResize, OperationUpscale, OperationReadText, OperationReadBarcodes, OperationCheck, OperationFix, OperationRemoveText, OperationReadLabel, OperationEnhance, OperationCrop, OperationPalette, OperationWatermark, OperationExpand, OperationVectorize, OperationFindFaces, OperationBlurFaces, OperationIDPhoto, OperationDenoise, OperationColorize, OperationSelect, OperationRestoreFaces, OperationCaption. For OperationRun, Recipe takes recipe TOML.

A job item is one image, so a watermark job takes text only and a remove-background job no background image. A parameter that repeats takes a []string: "point": []string{"420,310", "440,300"} for a select job.

Wait for it, then collect the results:

job, err = client.Jobs.Wait(ctx, job.ID, &photos.WaitOptions{
	Interval: 5 * time.Second,  // default 2 s
	Timeout:  30 * time.Minute, // default 10 min
})
if errors.Is(err, photos.ErrWaitTimeout) {
	// job is the job as last seen
} else if err != nil {
	log.Fatal(err)
}

for n, item := range job.Items {
	if item.Status != photos.ItemDone {
		fmt.Println(item.Input, "failed:", item.Error) // item.Error.Code, .Message
		continue
	}
	res, err := client.Jobs.Item(ctx, job.ID, n)
	if err != nil {
		log.Fatal(err)
	}
	if img := res.Image(); img != nil { // nil unless the result is an image
		err = img.Save(fmt.Sprintf("result-%d%s", n, img.Extension()))
	} else {
		var text photos.TextResult // an "ocr" job; use the type of the operation
		err = res.JSON(&text)
	}
}

completed means every item was attempted: job.Counts (Total, Done, Failed, Pending) says how they went. An item result also has the raw Body, ContentType and Header.

The items of a fix or remove-text job carry what the synchronous methods report beside the image:

res, err := client.Jobs.Item(ctx, job.ID, 0)

fix, err := res.Fix()       // *photos.FixResult: Passed, Findings and the image
rt, err := res.RemoveText() // *photos.RemoveTextResult: Removed, Kept and the image
if errors.Is(err, photos.ErrMissingResultHeaders) {
	// not a result of that operation, or an engine that does not keep
	// those headers on job items; res.Image() still gives the image
}

So do the items of the newer operations. Each of these returns what the synchronous method does:

img, err := res.RemoveBackground() // *photos.Image; SubjectFound is nil unless a canvas stayed empty
img, err = res.Crop()              // *photos.Image with its SubjectFound
svg, err := res.Vectorize()        // *photos.VectorizeResult: Paths
blur, err := res.BlurFaces()       // *photos.BlurFacesResult: Faces
id, err := res.IDPhoto()           // *photos.IDPhotoResult: Passed, Findings, HeadMm
sel, err := res.Select()           // *photos.SelectResult: Score
faces, err := res.RestoreFaces()   // *photos.RestoreFacesResult: Faces, FacesSkipped

They return that error, never zero values, when the result lacks the headers they read (X-Marketplace-Passed, X-Text-Removed, X-Subject-Found for a crop, X-Paths, X-Faces, X-Id-Photo-Passed, X-Select-Score, X-Faces-Skipped). RemoveBackground is the exception: the API sends X-Subject-Found only when a canvas stayed empty, so without it the image comes back with a nil SubjectFound. The same headers are in the job itself, lower-case, as job.Items[n].Headers ("x-marketplace-passed", "x-marketplace-findings", "x-text-removed", "x-text-kept", "x-faces" and so on, beside "x-image-*" and "server-timing").

job, err := client.Jobs.Get(ctx, "job_5f0c…")
jobs, err := client.Jobs.List(ctx)        // newest first, up to 100, without items
err = client.Jobs.Delete(ctx, "job_5f0c…") // cancels it if unfinished

Webhooks

A job with a WebhookURL sends a job.completed event there when it is done, signed in the Photos-Signature header. Verify each delivery against the raw body before trusting it; this needs no client.

secret := os.Getenv("PHOTOS_WEBHOOK_SECRET") // once, from client.WebhookSecret(ctx)

http.HandleFunc("/hooks/photos", func(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
	if err != nil {
		http.Error(w, "cannot read the body", http.StatusBadRequest)
		return
	}
	event, err := photos.VerifyWebhook(body, r.Header.Get(photos.SignatureHeader), secret, 0)
	if err != nil {
		http.Error(w, "bad signature", http.StatusBadRequest)
		return
	}
	if event.Type == photos.EventJobCompleted {
		log.Printf("job %s: %d done, %d failed", event.Job.ID, event.Job.Counts.Done, event.Job.Counts.Failed)
	}
	w.WriteHeader(http.StatusNoContent)
})

The last argument is the tolerance for the delivery's timestamp; zero means the default, five minutes. photos.VerifyWebhookAt takes the current time as well, for tests. A rejected delivery is a *photos.WebhookError: one of ErrWebhookHeader (missing or malformed header), ErrWebhookSignature, ErrWebhookTimestamp or ErrWebhookPayload.

s, err := client.WebhookSecret(ctx) // s.Secret, s.Header, s.Scheme

Configuration

client, err := photos.New("your-api-key",
	photos.WithBaseURL("http://localhost:8080"), // default https://api.photos.sh
	photos.WithTimeout(60*time.Second),          // per attempt; default 120 s; 0 = none
	photos.WithRetries(4),                       // default 2; 0 = never retry
	photos.WithUserAgent("my-shop/1.2"),         // sent in front of photos-sh-go/<version>
	photos.WithHTTPClient(&http.Client{}),       // your transport, proxy or tracing
)
Setting Option Environment Default
API key first argument of New PHOTOS_API_KEY none: New returns ErrMissingAPIKey
Base URL WithBaseURL PHOTOS_BASE_URL https://api.photos.sh
Timeout WithTimeout 120 s per attempt
Retries WithRetries 2

An argument or option wins over the environment. A *photos.Client is safe for use by several goroutines.

Retries

A request is sent again, up to the number of retries, when

  • the API answers 503 with the code busy, or with a body that is not the API's own error (a proxy in front of it). The wait is the Retry-After the response gives, at most 30 s; without one, 1 s, then 2 s, doubling.
  • no connection could be made or it broke before a response. Jobs.Create is the exception: the job may have been made, so it is retried only after a 503.

Nothing else is retried: no other status, not a 503 model_unavailable (the tool is not on that server), and not a timeout.

Retrying needs the body a second time. File and Bytes inputs can always be sent again, and so can a Reader that is also an io.Seeker (an *os.File, a *bytes.Reader): it is rewound to where it was. A request with any other reader is sent once and never retried, and the first answer (a busy error, for one) is returned as it is. To have such a stream retried, read it into memory and pass photos.Bytes.

Timeouts

WithTimeout bounds one attempt, from sending the request to reading the last byte of the response. To bound a whole call, retries and waits included, give it a context with a deadline:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
img, err := client.Upscale(ctx, photos.File("small.jpg"), nil)

Development

go build ./...
go vet ./...
gofmt -l .
go test -race ./...

The unit tests need no network. The integration tests run against a real engine and are skipped unless both variables are set:

PHOTOS_TEST_BASE_URL=http://127.0.0.1:8098/api \
PHOTOS_TEST_API_KEY=your-test-key \
go test -run Integration -v ./...

They create jobs whose inputs and webhooks are on 127.0.0.1, which a development engine accepts and production does not.

Licence

MIT. See LICENSE.

Documentation

Overview

Package photos is the client library for the Photos.sh HTTP API: image tools for product photos. It removes backgrounds, resizes and compresses, upscales, erases objects and text, checks photos against marketplace rules and fixes them, and reads barcodes, text and label details. It also enhances, crops, watermarks, expands, vectorizes, denoises and colorizes photos, selects an object at a point, finds, hides and restores faces, frames identity photos, and lists colours and writes captions.

Make a client with your API key (or leave it empty to use the PHOTOS_API_KEY environment variable) and call a method with an image:

client, err := photos.New("")
if err != nil {
	log.Fatal(err)
}
img, err := client.RemoveBackground(ctx, photos.File("shoe.jpg"), nil)
if err != nil {
	log.Fatal(err)
}
err = img.Save("shoe.png")

Images

An image argument is an Input: File for a path, Bytes for data in memory, Reader for a stream. Uploads are streamed. Methods that change a photo return an *Image (the bytes, the content type, the size), or a result that holds one beside what the method reports, such as the faces hidden; methods that read a photo return a typed result.

Options

Each method takes a pointer to its own options struct, which may be nil. A field left at its zero value is not sent, and the API then uses its own default. The few options whose zero is a value of its own (a margin of 0, an automatic correction turned off) are pointers: nil is not sent, and Int, Float and Bool make one, as in Margin: photos.Float(0).

Errors

An error response from the API is an *Error with the HTTP status and the API's code and message. Test for a code with errors.Is and a sentinel (ErrUnauthorized, ErrPaymentRequired, ErrBusy…), or get the *Error with errors.As. A failure to reach the API is a *ConnectionError. A cancelled context or a passed context deadline comes back as context.Canceled or context.DeadlineExceeded.

Retries and timeouts

A request that gets a 503 "busy" (or a 503 that is not the API's own, as from a proxy) is sent again, twice by default, after the Retry-After the API gives (at most 30 s) or 1 s then 2 s; so is one that fails to connect, except Jobs.Create. Nothing else is retried: not a 503 "model_unavailable", which is permanent, and not a timeout. Each attempt may take 120 s by default. See WithRetries and WithTimeout, and Input for how retries treat readers.

Jobs and webhooks

Client.Jobs runs one operation on many images in the background: Create, then Wait, or give a webhook URL and check each delivery with VerifyWebhook.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	// An empty key means: use the PHOTOS_API_KEY environment variable.
	client, err := photos.New("")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	img, err := client.RemoveBackground(ctx, photos.File("shoe.jpg"), nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(img.ContentType, img.Width, img.Height)
	if err := img.Save("shoe.png"); err != nil {
		log.Fatal(err)
	}
}

Index

Examples

Constants

View Source
const (
	// DefaultBaseURL is where the API lives unless WithBaseURL or the
	// PHOTOS_BASE_URL environment variable says otherwise.
	DefaultBaseURL = "https://api.photos.sh"
	// DefaultTimeout is the time one HTTP attempt may take.
	DefaultTimeout = 120 * time.Second
	// DefaultRetries is how many times a request is sent again after a 503
	// or a connection failure.
	DefaultRetries = 2
)
View Source
const (
	CodeBadRequest        = "bad_request"        // 400: a parameter or the image is wrong
	CodeUnauthorized      = "unauthorized"       // 401: missing or invalid API key
	CodePaymentRequired   = "payment_required"   // 402: out of credits
	CodeAccountSuspended  = "account_suspended"  // 403
	CodeNotFound          = "not_found"          // 404: no such job or item result
	CodePayloadTooLarge   = "payload_too_large"  // 413
	CodeUnsupportedFormat = "unsupported_format" // 415
	CodeUnprocessable     = "unprocessable"      // 422: understood, but cannot be done
	CodeBusy              = "busy"               // 503: all workers busy; retried for you
	CodeModelUnavailable  = "model_unavailable"  // 503: not available on this server; never retried
	CodeInternal          = "internal"           // 500

	// CodeInputUnavailable appears only in a failed job item's error: the
	// input URL could not be fetched.
	CodeInputUnavailable = "input_unavailable"
)

Error codes the API answers with, in Error.Code.

View Source
const (
	OperationRun              = "run"
	OperationRemoveBackground = "remove-background"
	OperationResize           = "resize"
	OperationUpscale          = "upscale"
	OperationReadText         = "ocr"
	OperationReadBarcodes     = "barcodes"
	OperationCheck            = "check"
	OperationFix              = "fix"
	OperationRemoveText       = "remove-text"
	OperationReadLabel        = "label"
	OperationEnhance          = "enhance"
	OperationCrop             = "crop"
	OperationPalette          = "palette"
	OperationWatermark        = "watermark" // with text only: an item is one image
	OperationExpand           = "expand"
	OperationVectorize        = "vectorize"
	OperationFindFaces        = "faces"
	OperationBlurFaces        = "blur-faces"
	OperationIDPhoto          = "id-photo"
	OperationDenoise          = "denoise"
	OperationColorize         = "colorize"
	OperationSelect           = "select"
	OperationRestoreFaces     = "restore-faces"
	OperationCaption          = "caption"
)

Operations a job can run, for JobRequest.Operation.

View Source
const (
	JobQueued  = "queued"
	JobRunning = "running"
	// JobCompleted means every item was attempted; Job.Counts says how many
	// failed.
	JobCompleted = "completed"
)

Job statuses, in Job.Status.

View Source
const (
	ItemPending = "pending"
	ItemDone    = "done"
	ItemFailed  = "failed"
)

Item statuses, in JobItem.Status.

View Source
const (
	DefaultWaitInterval = 2 * time.Second
	DefaultWaitTimeout  = 10 * time.Minute
)

Defaults of Jobs.Wait.

View Source
const (
	FormatPNG  = "png"
	FormatJPEG = "jpg"
	FormatWebP = "webp"
	FormatAVIF = "avif"
)

Output formats, for the Format field of the options.

View Source
const (
	FitContain = "contain" // keep the proportions, fit inside (the default)
	FitCover   = "cover"   // keep the proportions, fill and crop
	FitFill    = "fill"    // stretch to exactly Width × Height
)

How Resize fits an image into Width × Height, for ResizeOptions.Fit.

View Source
const (
	RegionBackground = "background" // text off the product (the default)
	RegionAll        = "all"        // text on the product too
)

Which text RemoveText removes, for RemoveTextOptions.Region.

View Source
const (
	HealthOK      = "ok"      // ready
	HealthLoading = "loading" // still loading its models
	HealthFailed  = "failed"  // a model could not be loaded
)

Health statuses.

View Source
const (
	// SignatureHeader is the header a webhook's signature arrives in.
	SignatureHeader = "Photos-Signature"
	// DefaultWebhookTolerance is how far a webhook's timestamp may be from
	// now when VerifyWebhook is given no tolerance.
	DefaultWebhookTolerance = 5 * time.Minute
	// EventJobCompleted is the Type of the event sent when a job is done.
	EventJobCompleted = "job.completed"
)
View Source
const MaxImagesPerRequest = 8

MaxImagesPerRequest is how many photos ReadLabel and Identify take.

View Source
const Version = "1.0.0"

Version is the version of this library.

Variables

View Source
var (
	ErrBadRequest        error = &Error{Code: CodeBadRequest}
	ErrUnauthorized      error = &Error{Code: CodeUnauthorized}
	ErrPaymentRequired   error = &Error{Code: CodePaymentRequired}
	ErrAccountSuspended  error = &Error{Code: CodeAccountSuspended}
	ErrNotFound          error = &Error{Code: CodeNotFound}
	ErrPayloadTooLarge   error = &Error{Code: CodePayloadTooLarge}
	ErrUnsupportedFormat error = &Error{Code: CodeUnsupportedFormat}
	ErrUnprocessable     error = &Error{Code: CodeUnprocessable}
	ErrBusy              error = &Error{Code: CodeBusy}
	ErrModelUnavailable  error = &Error{Code: CodeModelUnavailable}
	ErrInternal          error = &Error{Code: CodeInternal}
)

Sentinels for errors.Is, one per error code.

View Source
var (
	// ErrMissingAPIKey is returned by New when no key was given and
	// PHOTOS_API_KEY is not set.
	ErrMissingAPIKey = errors.New("photos: no API key: pass one to New or set PHOTOS_API_KEY")
	// ErrWaitTimeout is returned (wrapped) by Jobs.Wait when the job did not
	// complete within WaitOptions.Timeout.
	ErrWaitTimeout = errors.New("photos: timed out waiting for the job")
	// ErrMissingResultHeaders is returned (wrapped) by JobItemResult.RemoveText,
	// JobItemResult.Fix and the other typed results of a job item when the
	// result does not carry the headers they read: it is the result of
	// another operation, or the engine is too old to keep those headers on
	// job items.
	ErrMissingResultHeaders = errors.New("photos: the job item result lacks the headers")
)

Errors that are not responses from the API.

View Source
var (
	// ErrWebhookHeader: the Photos-Signature header is missing or malformed.
	ErrWebhookHeader error = &WebhookError{Reason: "missing or malformed Photos-Signature header"}
	// ErrWebhookSignature: the signature does not match the body and secret.
	ErrWebhookSignature error = &WebhookError{Reason: "signature does not match"}
	// ErrWebhookTimestamp: the timestamp is outside the tolerance.
	ErrWebhookTimestamp error = &WebhookError{Reason: "timestamp outside the tolerance"}
	// ErrWebhookPayload: the signature is right but the body is not an event.
	ErrWebhookPayload error = &WebhookError{Reason: "body is not a JSON event"}
)

Why a webhook is rejected.

Functions

func Bool

func Bool(v bool) *bool

Bool returns a pointer to v, for an option that is a *bool.

func Float

func Float(v float64) *float64

Float returns a pointer to v, for an option that is a *float64.

func Int

func Int(v int) *int

Int returns a pointer to v, for an option that is a *int.

Types

type Account

type Account struct {
	Account string `json:"account,omitempty"`
	// Key is the key's id, "browser" or "job".
	Key string `json:"key,omitempty"`
	// Credits is the balance, nil for an unlimited account.
	Credits *int64 `json:"credits,omitempty"`
	// Prices are the credits one image costs, by operation.
	Prices map[string]int64 `json:"prices,omitempty"`
	// Unlimited is true for a server key, which is not metered.
	Unlimited bool `json:"unlimited,omitempty"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

Account is the caller's account.

func (*Account) UnmarshalJSON

func (a *Account) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the account and keeps the JSON in Raw.

type Barcode

type Barcode struct {
	// Format is the symbology: "EAN-13", "UPC-A", "ITF", "QR"…
	Format string `json:"format"`
	// Value is the code as decoded.
	Value string `json:"value"`
	// GS1 is the GS1-valid normalised digits of a retail code, else nil.
	GS1 *string `json:"gs1"`
}

Barcode is one code read from a photo.

type BarcodesResult

type BarcodesResult struct {
	Barcodes []Barcode `json:"barcodes"`
	// Variant is the preprocessing that found the codes, or "none".
	Variant string `json:"variant"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

BarcodesResult is what ReadBarcodes returns.

func (*BarcodesResult) UnmarshalJSON

func (r *BarcodesResult) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the result and keeps the JSON in Raw.

type BlurFacesOptions

type BlurFacesOptions struct {
	// Mode is "blur" (the default), "pixelate" or "fill". Only "fill" leaves
	// nothing of the face.
	Mode string
	// Strength is how strong the blur or how coarse the mosaic is, 1–100
	// (default 50).
	Strength float64
	// Padding is how far past the face's box the hidden area reaches, as a
	// fraction of the box, 0 to 1 (default 0.15). It is a pointer because 0
	// (the box itself) is a value: use Float.
	Padding *float64
	// Color is the colour for the "fill" mode: "black" (the default),
	// "white", "#rrggbb".
	Color string
	// MinScore is how sure the model must be that something is a face,
	// 0.1–1 (default 0.6). Lower finds more faces and more things that are
	// not.
	MinScore float64
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

BlurFacesOptions are the options of BlurFaces. Zero fields are not sent.

type BlurFacesResult

type BlurFacesResult struct {
	Image
	// Faces is how many faces were found and hidden (X-Faces). Check it
	// before relying on the result: a face that was not found still shows.
	Faces int
}

BlurFacesResult is the image BlurFaces returns, and how many faces it hid.

type Border

type Border struct {
	// Mean is the mean colour (red, green, blue).
	Mean []float64 `json:"mean"`
	// White is the share of the border that is pure white.
	White float64 `json:"white"`
	// Cast is (red−green, blue−green) of a light backdrop, or nil.
	Cast []float64 `json:"cast"`
}

Border describes the edge of a photo: its backdrop.

type Candidate

type Candidate struct {
	ProductID string  `json:"product_id"`
	Title     string  `json:"title"`
	Score     float64 `json:"score"`
	// Evidence says what agreed beyond the name: "barcode", "barcode_text",
	// "sku", "brand", and by the catalogue's kind "quantity", "pack", "spf"
	// (food, cosmetics), "strength", "count" (pharmacy, household), "spec",
	// "model" (electronics), "cask_number", "age", "age_weak", "vintage",
	// "abv", "size" (wine and spirits).
	Evidence []string `json:"evidence"`
}

Candidate is a catalogue product that may be the one in the photos.

type CaptionOptions

type CaptionOptions struct {
	// Detail is how much to say: "short" (one sentence, the default),
	// "detailed" (two or three) or "paragraph".
	Detail string
}

CaptionOptions are the options of Caption. Zero fields are not sent.

type CaptionResult

type CaptionResult struct {
	// Caption is the description, in English. It can be wrong: read it
	// before publishing it.
	Caption string `json:"caption"`
	// Detail is how much was asked for: "short", "detailed" or "paragraph".
	Detail string `json:"detail"`
	// Width and Height are the photo's size in pixels.
	Width  int `json:"width"`
	Height int `json:"height"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

CaptionResult is what Caption returns.

func (*CaptionResult) UnmarshalJSON

func (r *CaptionResult) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the result and keeps the JSON in Raw.

type CheckOptions

type CheckOptions struct {
	// Marketplace checks the photo against that marketplace's rules
	// ("amazon", "ebay", "etsy", …; see Marketplaces).
	Marketplace string
	// Subject finds the product, for framing and background checks.
	Subject bool
	// Text reads the text in the photo.
	Text bool
}

CheckOptions are the options of Check. Zero fields are not sent.

type CheckResult

type CheckResult struct {
	Metrics  Metrics   `json:"metrics"`
	Findings []Finding `json:"findings"`
	// Text is the text read in the photo, when it was read.
	Text []FoundText `json:"text,omitempty"`
	// Marketplace is the marketplace's verdict, when one was asked for.
	Marketplace *Verdict `json:"marketplace,omitempty"`
	// Ms is how long each step took, in milliseconds.
	Ms map[string]int64 `json:"ms,omitempty"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

CheckResult is what Check returns.

func (*CheckResult) UnmarshalJSON

func (r *CheckResult) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the result and keeps the JSON in Raw.

type Client

type Client struct {
	// Jobs runs one operation on many images in the background.
	Jobs *JobsService
	// contains filtered or unexported fields
}

Client talks to the Photos.sh API. Make one with New and share it: it is safe for use by several goroutines at once.

func New

func New(apiKey string, opts ...Option) (*Client, error)

New returns a Client that signs its requests with apiKey. An empty apiKey falls back to the PHOTOS_API_KEY environment variable; with neither, New returns ErrMissingAPIKey.

The base URL is DefaultBaseURL unless WithBaseURL or the PHOTOS_BASE_URL environment variable gives another.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, err := photos.New("your-api-key",
		photos.WithBaseURL("http://localhost:8080"), // a local engine
		photos.WithTimeout(60*time.Second),
		photos.WithRetries(4),
		photos.WithUserAgent("my-shop/1.2"),
		photos.WithHTTPClient(&http.Client{}),
	)
	if err != nil {
		log.Fatal(err)
	}
	status, err := client.Health(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(status)
}

func (*Client) Account

func (c *Client) Account(ctx context.Context) (*Account, error)

Account returns the caller's account: credits left and prices, or Unlimited for a server key.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	acc, err := client.Account(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	if acc.Unlimited {
		fmt.Println(acc.Account, "is not metered")
	} else if acc.Credits != nil {
		fmt.Println(*acc.Credits, "credits left; a background removal costs", acc.Prices["remove-background"])
	}
}

func (*Client) BlurFaces

func (c *Client) BlurFaces(ctx context.Context, image Input, opts *BlurFacesOptions) (*BlurFacesResult, error)

BlurFaces finds every face in a photo and hides it inside an ellipse: blurred, pixelated, or covered with a colour. Blur and pixelation make a face unrecognisable to a person looking at the photo; only the "fill" mode leaves nothing of it. opts may be nil.

A small face, a face seen from behind or one mostly covered may not be found: check the result's Faces before relying on it.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.BlurFaces(context.Background(), photos.File("street.jpg"),
		&photos.BlurFacesOptions{Mode: "pixelate"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Faces, "faces hidden") // check it: a face not found still shows
	if err := res.Save("street-anonymous.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Caption

func (c *Client) Caption(ctx context.Context, image Input, opts *CaptionOptions) (*CaptionResult, error)

Caption describes a photo in words: one sentence, a few, or a paragraph. The same photo always gets the same words. opts may be nil.

The description can be wrong (a colour, a count, what an object is), and it is in English only. It does not know names, brands or places unless they are written in the photo. Read it before publishing it.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.Caption(context.Background(), photos.File("shoe.jpg"), &photos.CaptionOptions{Detail: "detailed"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Caption) // a draft, in English: read it before publishing it
}

func (*Client) Check

func (c *Client) Check(ctx context.Context, image Input, opts *CheckOptions) (*CheckResult, error)

Check measures a photo's quality and, with opts.Marketplace, checks it against that marketplace's rules. opts may be nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.Check(context.Background(), photos.File("shoe.jpg"),
		&photos.CheckOptions{Marketplace: "amazon"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%d×%d, sharpness %.2f\n", res.Metrics.Width, res.Metrics.Height, res.Metrics.Sharpness)
	for _, f := range res.Findings {
		fmt.Println(f.Severity, f.Code, f.Message)
	}
	if v := res.Marketplace; v != nil && !v.Passed {
		for _, f := range v.Findings {
			fmt.Println("amazon:", f.Code, f.Message)
		}
	}
}

func (*Client) Colorize

func (c *Client) Colorize(ctx context.Context, image Input, opts *ColorizeOptions) (*Image, error)

Colorize adds colour to a black-and-white photo. The colours are guesses from what such things usually look like: skin, sky, grass and wood come out believable, while the colour of clothes, cars or walls cannot be known and is invented. A photo that already has colour is treated as black and white: its colours are replaced. Transparency is kept. opts may be nil.

Example
package main

import (
	"context"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	img, err := client.Colorize(context.Background(), photos.File("grandparents.jpg"), nil)
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("grandparents-colour.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Crop

func (c *Client) Crop(ctx context.Context, image Input, opts *CropOptions) (*Image, error)

Crop finds the subject (or, with opts.Focus "face", the faces) and crops the photo to a shape around it, keeping the background.

The result's SubjectFound is false when nothing was found; the crop is then centred.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	img, err := client.Crop(ctx, photos.File("shoe.jpg"), &photos.CropOptions{Aspect: "4:5"})
	if err != nil {
		log.Fatal(err)
	}
	if img.SubjectFound != nil && !*img.SubjectFound {
		fmt.Println("no subject found: the crop is centred")
	}

	// An avatar: tight around the face, with room for hair and chin.
	avatar, err := client.Crop(ctx, photos.File("portrait.jpg"), &photos.CropOptions{
		Focus:   "face",
		Zoom:    true,
		Padding: photos.Float(0.6),
		Width:   400,
		Height:  400,
	})
	if err != nil {
		log.Fatal(err)
	}
	if err := avatar.Save("avatar.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Denoise

func (c *Client) Denoise(ctx context.Context, image Input, opts *DenoiseOptions) (*Image, error)

Denoise removes sensor noise and the blocks and ringing of heavy JPEG compression. It does not sharpen a blurred photo or add detail, and on a photo that is already clean it removes a little fine texture: lower opts.Strength there, or do not use it. Transparency is kept. opts may be nil.

It is slow without a GPU (about half a minute a megapixel on a CPU) and takes photos up to 12 megapixels.

Example
package main

import (
	"context"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	img, err := client.Denoise(context.Background(), photos.File("grainy.jpg"), &photos.DenoiseOptions{Strength: 80})
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("clean.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Enhance

func (c *Client) Enhance(ctx context.Context, image Input, opts *EnhanceOptions) (*Image, error)

Enhance fixes light and colour. With no options it stretches the tones to the full range and takes out a colour cast; with any setting, only what is given is applied. Transparency is kept. It needs no model, and it does not denoise, deblur or recover blown highlights. opts may be nil.

Example
package main

import (
	"context"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	// No options: automatic levels and white balance.
	img, err := client.Enhance(ctx, photos.File("dull.jpg"), nil)
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("bright.jpg"); err != nil {
		log.Fatal(err)
	}

	// Settings of your own, beside the automatic part.
	_, err = client.Enhance(ctx, photos.File("dull.jpg"), &photos.EnhanceOptions{
		Auto:       photos.Bool(true),
		Brightness: 10,
		Saturation: -15,
		Sharpen:    30,
	})
	if err != nil {
		log.Fatal(err)
	}
}

func (*Client) Erase

func (c *Client) Erase(ctx context.Context, image Input, opts *EraseOptions) (*Image, error)

Erase removes objects by painting over the areas in opts.Rects and the white parts of opts.Mask.

Example
package main

import (
	"context"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	img, err := client.Erase(context.Background(), photos.File("room.jpg"), &photos.EraseOptions{
		Rects: []photos.Rect{{X: 40, Y: 60, Width: 200, Height: 120}},
		// And/or a mask the size of the photo, white where to remove:
		Mask: photos.File("room-mask.png"),
	})
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("room-clean.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Expand

func (c *Client) Expand(ctx context.Context, image Input, opts *ExpandOptions) (*Image, error)

Expand adds room around a photo: to a shape (opts.Aspect), or by pixels on each side. The new area continues the photo by inpainting, which suits plain and softly textured backgrounds. It does not invent content: a subject cut off at the edge stays cut off. Transparency is flattened onto white.

Example
package main

import (
	"context"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	// To a shape; the new area continues the backdrop.
	img, err := client.Expand(ctx, photos.File("shoe.jpg"), &photos.ExpandOptions{Aspect: "16:9"})
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("shoe-wide.jpg"); err != nil {
		log.Fatal(err)
	}

	// By pixels, with a flat colour.
	_, err = client.Expand(ctx, photos.File("shoe.jpg"), &photos.ExpandOptions{Left: 200, Right: 200, Fill: "white"})
	if err != nil {
		log.Fatal(err)
	}
}

func (*Client) FindFaces

func (c *Client) FindFaces(ctx context.Context, image Input, opts *FindFacesOptions) (*FacesResult, error)

FindFaces says where the faces in a photo are: a box and five landmarks each, the surest first. It finds faces; it does not recognise who anyone is. opts may be nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.FindFaces(context.Background(), photos.File("group.jpg"), nil)
	if err != nil {
		log.Fatal(err)
	}
	for _, f := range res.Faces {
		fmt.Printf("%.0f×%.0f at %.0f,%.0f, score %.2f, tilted %.1f°\n",
			f.Box.Width, f.Box.Height, f.Box.X, f.Box.Y, f.Score, f.Roll)
		fmt.Println("  nose at", f.Landmarks.Nose.X, f.Landmarks.Nose.Y)
	}
}

func (*Client) Fix

func (c *Client) Fix(ctx context.Context, image Input, marketplace string) (*FixResult, error)

Fix makes a photo meet a marketplace's rules ("amazon", "ebay", "etsy", "shopify", "google", "allegro", "zalando"; see Marketplaces), and reports whether the result passes.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.Fix(context.Background(), photos.File("shoe.jpg"), "amazon")
	if err != nil {
		log.Fatal(err)
	}
	if !res.Passed {
		fmt.Println("still failing:", res.Findings)
	}
	if err := res.Save("shoe-amazon.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Health

func (c *Client) Health(ctx context.Context) (string, error)

Health reports whether the service is ready: HealthOK, HealthLoading or HealthFailed. "loading" and "failed" arrive as a 503, which here is an answer, not an error, and is not retried. No API key is sent.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	status, err := client.Health(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	if status != photos.HealthOK {
		fmt.Println("not ready:", status)
	}
}

func (*Client) IDPhoto

func (c *Client) IDPhoto(ctx context.Context, image Input, opts *IDPhotoOptions) (*IDPhotoResult, error)

IDPhoto frames a portrait for an identity document: it levels the head, scales it to the document's head size and places it on a photo of the document's size. Give opts.Document (see IDPhotos) or the sizes.

By default the photo's own background is kept, because passport authorities do not accept edited photos: the photo is only turned, scaled and cropped. opts.Background replaces it, for uses where that is allowed.

The result's Findings list what the tool could see is wrong. It cannot judge expression, glasses, shadows, focus or likeness, so an empty list is not an approval. A photo with no face, or more than one, is refused with an ErrUnprocessable.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.IDPhoto(context.Background(), photos.File("portrait.jpg"),
		&photos.IDPhotoOptions{Document: "us-passport"})
	if errors.Is(err, photos.ErrUnprocessable) {
		log.Fatal("the photo must show one face") // none, or more than one
	} else if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("head %.1f mm; findings: %v\n", res.HeadMm, res.Findings)
	if !res.Passed {
		fmt.Println("the tool saw something wrong; an empty list is still not an approval")
	}
	if err := res.Save("passport.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) IDPhotos

func (c *Client) IDPhotos(ctx context.Context) ([]IDPhotoDocument, error)

IDPhotos lists the documents whose photo rules IDPhoto knows: each one's photo size, head size, background and notes, with the official pages the rules were read from.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	documents, err := client.IDPhotos(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	for _, d := range documents {
		fmt.Printf("%s: %s, %g × %g mm, head %g–%g mm\n", d.Name, d.Title, d.WidthMm, d.HeightMm, d.HeadMinMm, d.HeadMaxMm)
		if d.Alteration != "" {
			fmt.Println(" ", d.Alteration)
		}
	}
}

func (*Client) Identify

func (c *Client) Identify(ctx context.Context, images []Input, opts *IdentifyOptions) (*IdentifyResult, error)

Identify matches one to eight photos of ONE product against one of your organisation's catalogues (uploaded on the console's Catalogues page). Catalogue matching is turned on per organisation: until it is on for yours and a catalogue is uploaded, the API answers 404 (errors.Is(err, ErrNotFound)). opts may be nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.Identify(context.Background(),
		[]photos.Input{photos.File("front.jpg"), photos.File("back.jpg")},
		&photos.IdentifyOptions{Catalogue: "shoes", Top: 3})
	if err != nil {
		log.Fatal(err)
	}
	for _, c := range res.Candidates {
		fmt.Println(c.ProductID, c.Title, c.Score, c.Evidence)
	}
}

func (*Client) Marketplaces

func (c *Client) Marketplaces(ctx context.Context) ([]Marketplace, error)

Marketplaces lists the marketplaces Check and Fix know, with their rules.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	marketplaces, err := client.Marketplaces(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	for _, m := range marketplaces {
		fmt.Println(m.Name, m.Title, "checked", m.Checked)
		for _, rule := range m.Rules {
			fmt.Println(" ", rule["check"], rule["severity"])
		}
	}
}

func (*Client) Palette

func (c *Client) Palette(ctx context.Context, image Input, opts *PaletteOptions) (*PaletteResult, error)

Palette lists the colours an image is made of, the most common first. Transparent pixels do not count, so a cutout's palette is the subject's. It needs no model. opts may be nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.Palette(context.Background(), photos.File("shoe.png"), &photos.PaletteOptions{Colors: 3})
	if err != nil {
		log.Fatal(err)
	}
	for _, c := range res.Colors {
		fmt.Printf("%s %.0f%%\n", c.Hex, c.Share*100)
	}
}

func (*Client) Presets

func (c *Client) Presets(ctx context.Context) ([]Preset, error)

Presets lists the built-in presets Run takes.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	presets, err := client.Presets(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	for _, p := range presets {
		fmt.Println(p.Name, "-", p.Description)
	}
}

func (*Client) ReadBarcodes

func (c *Client) ReadBarcodes(ctx context.Context, image Input, opts *ReadBarcodesOptions) (*BarcodesResult, error)

ReadBarcodes reads the barcodes in a photo. opts may be nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.ReadBarcodes(context.Background(), photos.File("box.jpg"),
		&photos.ReadBarcodesOptions{AllFormats: true})
	if err != nil {
		log.Fatal(err)
	}
	for _, b := range res.Barcodes {
		fmt.Println(b.Format, b.Value)
	}
}

func (*Client) ReadLabel

func (c *Client) ReadLabel(ctx context.Context, images []Input) (*LabelResult, error)

ReadLabel reads product details from one to eight photos of ONE product's label: text and codes from all of them are pooled.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.ReadLabel(context.Background(), []photos.Input{
		photos.File("front.jpg"),
		photos.File("back.jpg"),
	})
	if err != nil {
		log.Fatal(err)
	}
	label := res.Label
	fmt.Println(label.Headline, label.GTIN)
	if q := label.NetQuantity; q != nil {
		fmt.Println(q.Value, q.Unit)
	}
	if label.Alcohol != nil {
		fmt.Printf("%.1f%% vol\n", label.Alcohol.ABV)
	}
}

func (*Client) ReadText

func (c *Client) ReadText(ctx context.Context, image Input) (*TextResult, error)

ReadText reads the text in a photo (OCR), line by line.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.ReadText(context.Background(), photos.File("label.jpg"))
	if err != nil {
		log.Fatal(err)
	}
	for _, line := range res.Lines {
		fmt.Printf("%.2f %s\n", line.Score, line.Text)
	}
}

func (*Client) RemoveBackground

func (c *Client) RemoveBackground(ctx context.Context, image Input, opts *RemoveBackgroundOptions) (*Image, error)

RemoveBackground cuts the subject out of its background, and can stage the cutout in the same call: on a canvas, with a shadow or a reflection, in front of a colour, an image or the photo's own blurred background. opts may be nil.

With a canvas and no subject in the photo, the result is an empty canvas and its SubjectFound is false.

Example
package main

import (
	"context"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	img, err := client.RemoveBackground(context.Background(), photos.File("shoe.jpg"),
		&photos.RemoveBackgroundOptions{
			Background: "white",
			Crop:       true,
			Padding:    40,
			Format:     photos.FormatJPEG,
			Quality:    90,
		})
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("shoe-white" + img.Extension()); err != nil {
		log.Fatal(err)
	}
}
Example (Staged)

A cutout staged in the same call: on a canvas, standing on a soft shadow, in front of a backdrop of your own.

package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	img, err := client.RemoveBackground(context.Background(), photos.File("shoe.jpg"),
		&photos.RemoveBackgroundOptions{
			Width:           1600,
			Height:          1600,
			Fill:            0.8,
			Align:           "bottom",
			Shadow:          "contact",
			BackgroundImage: photos.File("studio.jpg"),
			Format:          photos.FormatJPEG,
		})
	if err != nil {
		log.Fatal(err)
	}
	if img.SubjectFound != nil && !*img.SubjectFound {
		fmt.Println("no subject in the photo: the canvas is empty")
	}
	if err := img.Save("shoe-staged.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) RemoveText

func (c *Client) RemoveText(ctx context.Context, image Input, opts *RemoveTextOptions) (*RemoveTextResult, error)

RemoveText paints out text, watermarks and captions: by default only text off the product. opts may be nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.RemoveText(context.Background(), photos.File("listing.jpg"),
		&photos.RemoveTextOptions{Matching: `sale|€|www\.`})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("removed %d lines, kept %d\n", res.Removed, res.Kept)
	if err := res.Save("listing-clean.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Resize

func (c *Client) Resize(ctx context.Context, image Input, opts *ResizeOptions) (*Image, error)

Resize resizes, converts and compresses an image. It can also trim an even border first and write a print resolution. opts may be nil.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")

	// Any io.Reader will do; here, standard input.
	img, err := client.Resize(context.Background(), photos.Reader("photo.jpg", os.Stdin),
		&photos.ResizeOptions{Width: 1600, Format: photos.FormatWebP, MaxBytes: 300_000})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%d×%d at quality %d, %d bytes\n", img.Width, img.Height, img.Quality, len(img.Bytes))
}

func (*Client) RestoreFaces

func (c *Client) RestoreFaces(ctx context.Context, image Input, opts *RestoreFacesOptions) (*RestoreFacesResult, error)

RestoreFaces finds the faces in an old, blurred, small or noisy photo and redraws each one sharp; the rest of the photo is not touched. opts may be nil.

It invents the detail: the result resembles the person, but the colour of the eyes, the teeth and the skin's texture are the model's. Do not use it where the face must be exactly the person's own, such as an identity document or evidence.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.RestoreFaces(context.Background(), photos.File("old-family.jpg"), nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Faces, "faces restored,", res.FacesSkipped, "left alone")
	if err := res.Save("old-family-restored.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Run

func (c *Client) Run(ctx context.Context, image Input, opts *RunOptions) (*Image, error)

Run applies a preset (opts.Preset) or a recipe (opts.Recipe) to an image.

Example
package main

import (
	"context"
	"log"
	"os"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	// A built-in preset (see Presets)…
	img, err := client.Run(ctx, photos.File("shoe.jpg"), &photos.RunOptions{Preset: "marketplace"})
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("shoe-marketplace.jpg"); err != nil {
		log.Fatal(err)
	}

	// …or a recipe of your own, as TOML.
	recipe, err := os.ReadFile("recipe.toml")
	if err != nil {
		log.Fatal(err)
	}
	if _, err := client.Run(ctx, photos.File("shoe.jpg"), &photos.RunOptions{Recipe: string(recipe)}); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Select

func (c *Client) Select(ctx context.Context, image Input, opts *SelectOptions) (*SelectResult, error)

Select cuts out the one object at opts.Points or in opts.Box, or returns its mask. Where RemoveBackground decides for itself what the subject is, here you say.

When nothing is found at the points, the error is an ErrUnprocessable; a point outside the photo is an ErrBadRequest.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	// The object at a click, cut out.
	res, err := client.Select(ctx, photos.File("desk.jpg"), &photos.SelectOptions{
		Points:  []photos.Point{{X: 420, Y: 310}},
		Exclude: []photos.Point{{X: 600, Y: 300}},
		Crop:    true,
	})
	if errors.Is(err, photos.ErrUnprocessable) {
		log.Fatal("nothing was found at that point")
	} else if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("score %.2f\n", res.Score)
	if err := res.Save("mug.png"); err != nil {
		log.Fatal(err)
	}

	// Its mask is what Erase takes to remove it.
	mask, err := client.Select(ctx, photos.File("desk.jpg"), &photos.SelectOptions{
		Points: []photos.Point{{X: 420, Y: 310}},
		Mask:   true,
	})
	if err != nil {
		log.Fatal(err)
	}
	_, err = client.Erase(ctx, photos.File("desk.jpg"), &photos.EraseOptions{
		Mask: photos.Bytes("mask.png", mask.Bytes),
	})
	if err != nil {
		log.Fatal(err)
	}
}

func (*Client) Upscale

func (c *Client) Upscale(ctx context.Context, image Input, opts *UpscaleOptions) (*Image, error)

Upscale enlarges an image with an AI model. opts may be nil.

Example
package main

import (
	"context"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	img, err := client.Upscale(context.Background(), photos.File("small.jpg"), &photos.UpscaleOptions{Scale: 2})
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("large.jpg"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) Vectorize

func (c *Client) Vectorize(ctx context.Context, image Input, opts *VectorizeOptions) (*VectorizeResult, error)

Vectorize traces an image into SVG: each area of one colour becomes a filled path. It is for logos, icons and flat drawings; a photograph traces into thousands of paths and a large file. Images over 2000 pixels on a side are traced from a smaller copy. It needs no model. opts may be nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	res, err := client.Vectorize(context.Background(), photos.File("logo.png"), nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Paths, "paths, traced at", res.Width, "×", res.Height)
	if err := res.Save("logo.svg"); err != nil { // res.Bytes is the SVG's text
		log.Fatal(err)
	}
}

func (*Client) Watermark

func (c *Client) Watermark(ctx context.Context, image Input, opts *WatermarkOptions) (*Image, error)

Watermark puts opts.Text or the image opts.Watermark (a logo) on a photo: at one of nine positions, or tiled across it. It needs no model.

Example
package main

import (
	"context"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	// Text…
	img, err := client.Watermark(ctx, photos.File("photo.jpg"), &photos.WatermarkOptions{
		Text:     "© Acme",
		Position: "bottom-right",
		Opacity:  60,
	})
	if err != nil {
		log.Fatal(err)
	}
	if err := img.Save("photo-marked.jpg"); err != nil {
		log.Fatal(err)
	}

	// …or a logo, here tiled across the photo.
	_, err = client.Watermark(ctx, photos.File("photo.jpg"), &photos.WatermarkOptions{
		Watermark: photos.File("logo.png"),
		Scale:     15,
		Tile:      true,
	})
	if err != nil {
		log.Fatal(err)
	}
}

func (*Client) WebhookSecret

func (c *Client) WebhookSecret(ctx context.Context) (*WebhookSecret, error)

WebhookSecret returns the key your job webhooks are signed with; pass its Secret to VerifyWebhook.

type ColorizeOptions

type ColorizeOptions struct {
	// Strength is how colourful the result is, 1–100: 100 (the default) is
	// the model's colour as it is, lower is paler.
	Strength float64
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

ColorizeOptions are the options of Colorize. Zero fields are not sent.

type ConnectionError

type ConnectionError struct {
	// Err is the cause, from net/http.
	Err error
	// Timeout is true when the Client's timeout (WithTimeout) passed, or the
	// network reported a timeout. Timeouts are not retried.
	Timeout bool
	// contains filtered or unexported fields
}

ConnectionError means no (complete) response arrived: the connection could not be made or broke, or the Client's timeout passed. Unwrap gives the cause.

A cancelled context, or one whose own deadline passed, is not reported as a ConnectionError: the call returns context.Canceled or context.DeadlineExceeded.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

type CropOptions

type CropOptions struct {
	// Aspect is the shape as width:height ("4:5", "16:9", "1:1") or a number
	// ("1.5").
	Aspect string
	// Width and Height scale the crop to exactly this size in pixels; give
	// both. Without Aspect they are the shape as well.
	Width  int
	Height int
	// Zoom crops tightly around the subject: the smallest window of the
	// shape that holds it and its Padding. Without Zoom the crop is the
	// largest window of the shape, moved to hold the subject.
	Zoom bool
	// Padding is the room around the subject with Zoom, as a fraction of
	// its longest side, 0 to 2 (default 0.1). It is a pointer because 0 (no
	// room) is a value: use Float.
	Padding *float64
	// Focus is what the crop is built around: "subject" (the default) or
	// "face" (all the faces in the photo as one area). For a portrait or an
	// avatar use "face" with Zoom and a Padding around 0.6.
	Focus string
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

CropOptions are the options of Crop. Zero fields are not sent.

type DenoiseOptions

type DenoiseOptions struct {
	// Strength is how much of the cleaned result replaces the photo, 1–100:
	// 100 (the default) is all of it, 50 half-way back to the original.
	Strength float64
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

DenoiseOptions are the options of Denoise. Zero fields are not sent.

type EnhanceOptions

type EnhanceOptions struct {
	// Auto is automatic levels and white balance together. It is on when no
	// other setting is given; Bool(true) keeps it beside other settings.
	Auto *bool
	// Levels stretches the tones so the darkest pixels are black and the
	// brightest white. Nil follows Auto.
	Levels *bool
	// WhiteBalance takes out a colour cast. Nil follows Auto.
	WhiteBalance *bool
	// Brightness, Contrast and Saturation go from -100 to 100; zero changes
	// nothing. A Saturation of -100 is black and white.
	Brightness float64
	Contrast   float64
	Saturation float64
	// Temperature goes from -100 (cooler, bluer) to 100 (warmer).
	Temperature float64
	// Sharpen goes from 0 to 100.
	Sharpen float64
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

EnhanceOptions are the options of Enhance. Without any, Enhance stretches the tones to the full range and takes out a colour cast. With any setting, only what is given is applied.

type EraseOptions

type EraseOptions struct {
	// Rects are the areas to remove.
	Rects []Rect
	// Mask is an image the size of the photo, white where to remove. The
	// zero Input means no mask.
	Mask Input
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

EraseOptions say what Erase removes: the areas in Rects, the white parts of Mask, or both. At least one is needed.

type Error

type Error struct {
	// Status is the HTTP status.
	Status int
	// Code is the API's error code (one of the Code constants; the API may
	// add more). It is empty when the response was not the API's JSON, as
	// from a proxy in front of it.
	Code string
	// Message says what went wrong. For a response that was not the API's
	// JSON it is the start of the body.
	Message string
	// RetryAfter is the Retry-After header, zero when the response had none.
	RetryAfter time.Duration
	// Header is the response's headers.
	Header http.Header
}

Error is an error response from the API. Get at it with errors.As, or test for one code with errors.Is and a sentinel:

if errors.Is(err, photos.ErrPaymentRequired) { … }

var apiErr *photos.Error
if errors.As(err, &apiErr) { log.Print(apiErr.Status, apiErr.Code) }
Example

Errors from the API are *photos.Error values: branch on the code with errors.Is, or read the details with errors.As.

package main

import (
	"context"
	"errors"
	"fmt"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	_, err := client.Upscale(context.Background(), photos.File("small.jpg"), nil)

	var apiErr *photos.Error
	var connErr *photos.ConnectionError
	switch {
	case err == nil:
		// Use the image.
	case errors.Is(err, photos.ErrPaymentRequired):
		fmt.Println("out of credits")
	case errors.Is(err, photos.ErrBusy):
		// Already retried; apiErr.RetryAfter says when to try again.
		fmt.Println("busy")
	case errors.As(err, &apiErr):
		fmt.Println(apiErr.Status, apiErr.Code, apiErr.Message)
	case errors.As(err, &connErr):
		fmt.Println("could not reach the API; timed out:", connErr.Timeout)
	case errors.Is(err, context.DeadlineExceeded), errors.Is(err, context.Canceled):
		fmt.Println("the context ended")
	default:
		fmt.Println(err) // a file that could not be read, for one
	}
}

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

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

Is makes errors.Is(err, photos.ErrUnauthorized) and the other sentinels work: an Error matches a sentinel with the same code.

type Event

type Event struct {
	// Type is the kind of event: EventJobCompleted.
	Type string `json:"type"`
	// Job is the job the event is about.
	Job *Job `json:"job,omitempty"`
	// Raw is the event as it came.
	Raw json.RawMessage `json:"-"`
}

Event is a verified webhook delivery.

func VerifyWebhook

func VerifyWebhook(body []byte, header, secret string, tolerance time.Duration) (*Event, error)

VerifyWebhook checks that a webhook delivery was signed by Photos.sh and returns the event it carries.

body is the request body exactly as received (verify before decoding it), header the value of the Photos-Signature header ("t=<unix seconds>,v1=<hex>"), and secret your signing key (Client.WebhookSecret). Deliveries whose timestamp is more than tolerance from now are rejected; a tolerance of zero or less means DefaultWebhookTolerance.

It needs no Client. An error is one of ErrWebhookHeader, ErrWebhookSignature, ErrWebhookTimestamp or ErrWebhookPayload.

Example

A webhook endpoint: verify the delivery against the raw body before trusting it.

package main

import (
	"io"
	"log"
	"net/http"
	"os"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	secret := os.Getenv("PHOTOS_WEBHOOK_SECRET") // from Client.WebhookSecret

	http.HandleFunc("/hooks/photos", func(w http.ResponseWriter, r *http.Request) {
		body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
		if err != nil {
			http.Error(w, "cannot read the body", http.StatusBadRequest)
			return
		}
		event, err := photos.VerifyWebhook(body, r.Header.Get(photos.SignatureHeader), secret, 0)
		if err != nil {
			http.Error(w, "bad signature", http.StatusBadRequest)
			return
		}
		if event.Type == photos.EventJobCompleted {
			log.Printf("job %s: %d done, %d failed", event.Job.ID, event.Job.Counts.Done, event.Job.Counts.Failed)
		}
		w.WriteHeader(http.StatusNoContent)
	})
}

func VerifyWebhookAt

func VerifyWebhookAt(body []byte, header, secret string, tolerance time.Duration, now time.Time) (*Event, error)

VerifyWebhookAt is VerifyWebhook with the current time given as now, for tests and for verifying stored deliveries.

Example
package main

import (
	"errors"
	"fmt"
	"time"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	body := []byte(`{"type":"job.completed","job":{"id":"job_1"}}`)
	header := "t=1800000000,v1=90c1672761ea299dc1569349a086c9d9c3053d8fa0636d77d40a0286ce3fb226"

	event, err := photos.VerifyWebhookAt(body, header, "whsec_test", 5*time.Minute, time.Unix(1800000060, 0))
	fmt.Println(event.Type, event.Job.ID, err)

	_, err = photos.VerifyWebhookAt(body, header, "whsec_test", 5*time.Minute, time.Unix(1800009999, 0))
	fmt.Println(errors.Is(err, photos.ErrWebhookTimestamp))

	_, err = photos.VerifyWebhookAt(body, header, "another secret", 5*time.Minute, time.Unix(1800000060, 0))
	fmt.Println(err)

}
Output:
job.completed job_1 <nil>
true
photos: webhook: signature does not match

type ExpandOptions

type ExpandOptions struct {
	// Aspect is the shape to reach, as width:height ("1:1") or a number. The
	// photo stays in the centre.
	Aspect string
	// Left, Right, Top and Bottom are the pixels to add on each side. A side
	// can grow by at most the photo's own width or height.
	Left   int
	Right  int
	Top    int
	Bottom int
	// Fill is "inpaint" (the default: the new area continues the photo) or a
	// colour ("white", "black", "#rrggbb"), which needs no model.
	Fill string
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

ExpandOptions say how Expand grows the canvas: to a shape (Aspect), or by pixels on each side, not both. Zero fields are not sent.

type Face

type Face struct {
	// Box runs from forehead to chin.
	Box FaceBox `json:"box"`
	// Landmarks are five points of the face.
	Landmarks FaceLandmarks `json:"landmarks"`
	// Score is how sure the model is that this is a face, 0–1.
	Score float64 `json:"score"`
	// Roll is the tilt of the line through the eyes, in degrees: 0 for a
	// level head.
	Roll float64 `json:"roll"`
}

Face is one face in a photo. Its coordinates are in the photo's pixels, after its EXIF rotation.

type FaceBox

type FaceBox struct {
	X      float64 `json:"x"`
	Y      float64 `json:"y"`
	Width  float64 `json:"width"`
	Height float64 `json:"height"`
}

FaceBox is where a face is. It can reach past the photo's edges (a negative X or Y) for a face partly out of the frame.

type FaceLandmarks

type FaceLandmarks struct {
	RightEye   Point `json:"right_eye"`
	LeftEye    Point `json:"left_eye"`
	Nose       Point `json:"nose"`
	MouthRight Point `json:"mouth_right"`
	MouthLeft  Point `json:"mouth_left"`
}

FaceLandmarks are five points of a face. "Right" and "left" are the person's own, so RightEye is the one on the left of the photo.

type FacesResult

type FacesResult struct {
	// Width and Height are the photo's size in pixels, after its EXIF
	// rotation.
	Width  int `json:"width"`
	Height int `json:"height"`
	// Faces are the faces found, the surest first; empty when there are
	// none.
	Faces []Face `json:"faces"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

FacesResult is what FindFaces returns.

func (*FacesResult) UnmarshalJSON

func (r *FacesResult) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the result and keeps the JSON in Raw.

type FindFacesOptions

type FindFacesOptions struct {
	// MinScore is how sure the model must be that something is a face,
	// 0.1–1 (default 0.6).
	MinScore float64
}

FindFacesOptions are the options of FindFaces. Zero fields are not sent.

type Finding

type Finding struct {
	// Code is a stable identifier: "blurry", "too_small",
	// "background_not_white", "text_overlay"…
	Code string `json:"code"`
	// Severity is "info", "warning" or "error"; an error breaks a
	// marketplace rule.
	Severity string `json:"severity"`
	Message  string `json:"message"`
	// Detail is what was measured or found, when there is something to say.
	Detail string `json:"detail,omitempty"`
}

Finding is one thing a check found.

type FixResult

type FixResult struct {
	Image
	// Passed reports whether the fixed image passes the marketplace's check
	// (X-Marketplace-Passed).
	Passed bool
	// Findings are the codes of the findings still present
	// (X-Marketplace-Findings); empty, never nil, when there are none.
	Findings []string
}

FixResult is the image Fix returns, and the marketplace's verdict on it.

type FoundText

type FoundText struct {
	Text string `json:"text"`
	// OnProduct says whether it is printed on the product; nil when the
	// product was not looked for.
	OnProduct *bool `json:"on_product"`
}

FoundText is a piece of text Check read in a photo.

type IDPhotoDocument

type IDPhotoDocument struct {
	// Name is what IDPhotoOptions.Document takes, such as "us-passport".
	Name  string `json:"name"`
	Title string `json:"title"`
	// Checked is the date (YYYY-MM-DD) the rules were last compared with
	// the sources.
	Checked string `json:"checked"`
	// Sources are the official pages the rules were read from.
	Sources []string `json:"sources"`
	// Notes are rules the tool cannot check or make true.
	Notes []string `json:"notes"`
	// WidthMm and HeightMm are the photo's size in millimetres.
	WidthMm  float64 `json:"width_mm"`
	HeightMm float64 `json:"height_mm"`
	// HeadMinMm and HeadMaxMm are the smallest and largest allowed head,
	// chin to crown, in millimetres.
	HeadMinMm float64 `json:"head_min_mm"`
	HeadMaxMm float64 `json:"head_max_mm"`
	// EyesFromBottomMm is the smallest and the largest distance of the eyes
	// from the bottom edge, for a document with such a rule; nil otherwise.
	EyesFromBottomMm []float64 `json:"eyes_from_bottom_mm,omitempty"`
	// Background is the background the rules ask for, as a colour.
	Background string `json:"background"`
	// Alteration is what the authority says about photos changed by
	// software, in its own words; empty when it says nothing.
	Alteration string `json:"alteration,omitempty"`
	// Raw is the document as it came.
	Raw json.RawMessage `json:"-"`
}

IDPhotoDocument is an identity document whose photo rules IDPhoto knows.

func (*IDPhotoDocument) UnmarshalJSON

func (d *IDPhotoDocument) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the document and keeps the JSON in Raw.

type IDPhotoOptions

type IDPhotoOptions struct {
	// Document names a document with built-in rules, such as "us-passport";
	// IDPhotos lists them.
	Document string
	// WidthMm and HeightMm are the photo's size in millimetres, 10–200.
	WidthMm  float64
	HeightMm float64
	// HeadMinMm and HeadMaxMm are the smallest and largest allowed head,
	// chin to crown, in millimetres.
	HeadMinMm float64
	HeadMaxMm float64
	// EyesMinMm and EyesMaxMm are the eyes' allowed distance from the bottom
	// edge, for documents with such a rule. Give both or neither.
	EyesMinMm float64
	EyesMaxMm float64
	// DPI is the pixels per inch of the result, 100–1200 (default 300). It
	// is also written into the file.
	DPI int
	// Background is "keep" (the default: the photo's own background),
	// "document" (the colour the document asks for) or a colour ("white",
	// "#rrggbb"). Anything but "keep" cuts the person out and replaces the
	// background, and many authorities do not accept a photo changed that
	// way.
	Background string
	// Format is the output format; the default is JPEG.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

IDPhotoOptions say what IDPhoto frames the portrait for: a Document, or the photo and head sizes (WidthMm, HeightMm, HeadMinMm and HeadMaxMm), not both. Zero fields are not sent.

type IDPhotoResult

type IDPhotoResult struct {
	Image
	// Passed reports whether Findings is empty (X-Id-Photo-Passed). It is
	// not an approval of the photo: expression, glasses, shadows, focus and
	// likeness are not judged.
	Passed bool
	// Findings are the codes of what was found (X-Id-Photo-Findings):
	// "head_tilted", "not_facing_camera", "head_cut_off", "not_enough_room",
	// "low_resolution", "background_not_plain". Empty, never nil, when
	// there are none.
	Findings []string
	// HeadMm is the head's height in the result, chin to crown, in
	// millimetres (X-Id-Photo-Head-Mm).
	HeadMm float64
}

IDPhotoResult is the photo IDPhoto returns, and what the tool could see is wrong with it.

type IdentifyLabel

type IdentifyLabel struct {
	Ages     []int     `json:"ages,omitempty"`
	WeakAges []int     `json:"weak_ages,omitempty"`
	Years    []int     `json:"years,omitempty"`
	ABVs     []float64 `json:"abvs,omitempty"`
	// Sizes are in centilitres.
	Sizes []float64 `json:"sizes,omitempty"`
	// Quantities are net quantities, in "g" or "ml".
	Quantities []Measure `json:"quantities,omitempty"`
	// Packs are the units in a multipack: 6 for "6 x 330 ml".
	Packs []int `json:"packs,omitempty"`
	// SPFs are sun protection factors.
	SPFs []int `json:"spfs,omitempty"`
	// Strengths are those of a medicine or supplement, in "mg", "iu" or
	// "percent".
	Strengths []Measure `json:"strengths,omitempty"`
	// Counts are the tablets, capsules, washes, rolls, wipes, batteries and
	// the like in the pack.
	Counts []int `json:"counts,omitempty"`
	// Specs are technical values, in "gb", "mah", "wh", "w", "v", "a", "hz"
	// or "in".
	Specs []Measure `json:"specs,omitempty"`
}

IdentifyLabel is what Identify read off the label; which values there are depends on the catalogue's kind.

type IdentifyOptions

type IdentifyOptions struct {
	// Catalogue names the catalogue to search; it may be empty when your
	// organisation has exactly one.
	Catalogue string
	// Top is how many candidates to return, 1–50 (default 5).
	Top int
}

IdentifyOptions are the options of Identify. Zero fields are not sent.

type IdentifyResult

type IdentifyResult struct {
	Catalogue string `json:"catalogue"`
	// Candidates are the products that match, best first.
	Candidates []Candidate `json:"candidates"`
	// Label is what was read off the label.
	Label IdentifyLabel `json:"label"`
	// Barcodes are the codes read.
	Barcodes []string `json:"barcodes"`
	// Text is all the text recognised.
	Text string `json:"text,omitempty"`
	// Ms is how long each step took, in milliseconds.
	Ms map[string]int64 `json:"ms,omitempty"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

IdentifyResult is what Identify returns.

func (*IdentifyResult) UnmarshalJSON

func (r *IdentifyResult) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the result and keeps the JSON in Raw.

type Image

type Image struct {
	// Bytes is the encoded image.
	Bytes []byte
	// ContentType is its media type, such as "image/png".
	ContentType string
	// Width and Height are its size in pixels (the X-Image-Width and
	// X-Image-Height headers), zero if the response did not say.
	Width  int
	Height int
	// Quality is the quality it was encoded at, 1–100 (X-Image-Quality).
	// It is zero for formats without one, such as PNG.
	Quality int
	// ServerTiming is the raw Server-Timing header: how long each step took.
	ServerTiming string
	// SubjectFound says whether a subject was found in the photo (the
	// X-Subject-Found header). It is nil when the response did not say: nil
	// means "unknown", not "no". Crop always says. RemoveBackground says
	// false when a canvas was asked for and the photo has no subject; the
	// canvas is then empty.
	SubjectFound *bool
	// Header is every header of the response.
	Header http.Header
}

Image is an image the API returned.

func (*Image) Extension

func (img *Image) Extension() string

Extension is a file extension for the image's content type, with the dot (".png", ".jpg", ".webp", ".avif", ".svg"), or "" for a type it does not know.

func (*Image) Save

func (img *Image) Save(path string) error

Save writes the image to the file at path, replacing it if it exists.

type Input

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

Input is an image to send: make one with File, Bytes or Reader. The zero Input is "no image".

The request body is streamed, not built in memory, so a large file costs no more memory than a small one.

Retries need the body a second time. File and Bytes inputs can always be sent again. A Reader input can be sent again only if it is also an io.Seeker (an *os.File, a *bytes.Reader); a request with any other reader is sent once and never retried, whatever WithRetries says: the first answer, such as a 503 "busy" *Error, is returned as it is. Read the data into memory and use Bytes when you want such a stream retried.

func Bytes

func Bytes(name string, b []byte) Input

Bytes is an image held in memory. name is the file name the API sees (job items are listed under it); it may be empty.

func File

func File(path string) Input

File is the image in the file at path. The file is opened when the request is sent, and again for each retry.

func Reader

func Reader(name string, r io.Reader) Input

Reader is an image read from r, which is read to its end but not closed. name is the file name the API sees; it may be empty.

A request reading from r is retried only if r is also an io.Seeker; see Input.

func (Input) IsZero

func (in Input) IsZero() bool

IsZero reports whether in is the zero Input: no image.

type ItemError

type ItemError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

ItemError is why a job item failed: the error the synchronous endpoint would have answered with.

func (*ItemError) Error

func (e *ItemError) Error() string

func (*ItemError) UnmarshalJSON

func (e *ItemError) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts the usual {"code", "message"} object, and a bare string as the message.

type Job

type Job struct {
	ID        string `json:"id"`
	Operation string `json:"operation"`
	// Params is the operation's parameters as a query string.
	Params string `json:"params"`
	// Status is JobQueued, JobRunning or JobCompleted.
	Status string `json:"status"`
	// CreatedAt and FinishedAt are Unix seconds; FinishedAt is zero until
	// the job has completed.
	CreatedAt  int64     `json:"created_at"`
	FinishedAt int64     `json:"finished_at,omitempty"`
	Counts     JobCounts `json:"counts"`
	Items      []JobItem `json:"items"`
	// Webhook is the state of the webhook delivery, when the job has one.
	Webhook *JobWebhook `json:"webhook,omitempty"`
	// Raw is the job as it came.
	Raw json.RawMessage `json:"-"`
}

Job is a batch job: its status, progress and per-item results.

func (*Job) Completed

func (j *Job) Completed() bool

Completed reports whether every item was attempted.

func (*Job) UnmarshalJSON

func (j *Job) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the job and keeps the JSON in Raw.

type JobCounts

type JobCounts struct {
	Total   int `json:"total"`
	Done    int `json:"done"`
	Failed  int `json:"failed"`
	Pending int `json:"pending"`
}

JobCounts counts a job's items by status.

type JobItem

type JobItem struct {
	// Input is the upload's file name, or the input URL.
	Input string `json:"input"`
	// URL is the input URL, for an input that was one.
	URL string `json:"url,omitempty"`
	// Status is ItemPending, ItemDone or ItemFailed.
	Status string `json:"status"`
	// Result is the path of the result when done; fetch it with Jobs.Item.
	Result      string `json:"result,omitempty"`
	ContentType string `json:"content_type,omitempty"`
	// Bytes is the size of the result.
	Bytes int64 `json:"bytes,omitempty"`
	// Headers are the result's headers, in lower case: x-image-width,
	// x-image-height, x-image-quality and server-timing, plus
	// x-text-removed and x-text-kept for a "remove-text" job,
	// x-marketplace-passed and x-marketplace-findings for a "fix" job, and
	// likewise x-subject-found, x-paths, x-faces, x-faces-skipped,
	// x-select-score and x-id-photo-* for the operations that report them.
	// (Older engines keep only the first four.)
	// Jobs.Item returns the same headers with the result, and its
	// RemoveText, Fix, RemoveBackground, Crop, Vectorize, BlurFaces,
	// IDPhoto, Select and RestoreFaces read them for you.
	Headers map[string]string `json:"headers,omitempty"`
	// Error says why a failed item failed: an error code of the API, or
	// CodeInputUnavailable when the input URL could not be fetched.
	Error *ItemError `json:"error,omitempty"`
}

JobItem is one input of a job and what became of it.

type JobItemResult

type JobItemResult struct {
	// Body is the raw result: an encoded image, or JSON.
	Body []byte
	// ContentType is its media type.
	ContentType string
	// Header is every header of the response.
	Header http.Header
	// contains filtered or unexported fields
}

JobItemResult is the result of one finished item: what the synchronous endpoint would have returned.

func (*JobItemResult) BlurFaces

func (r *JobItemResult) BlurFaces() (*BlurFacesResult, error)

BlurFaces returns the result of a "blur-faces" job's item as the synchronous Client.BlurFaces would: the image and how many faces were hidden. Without the X-Faces header it returns an ErrMissingResultHeaders, as RemoveText does.

func (*JobItemResult) Crop

func (r *JobItemResult) Crop() (*Image, error)

Crop returns the result of a "crop" job's item as the synchronous Client.Crop would: the image, whose SubjectFound says whether a subject was found. Without the X-Subject-Found header, which a crop always carries, it returns an ErrMissingResultHeaders, as RemoveText does.

func (*JobItemResult) Fix

func (r *JobItemResult) Fix() (*FixResult, error)

Fix returns the result of a "fix" job's item as the synchronous Client.Fix would: the image, whether it passes the marketplace's check, and the findings still present.

If the result does not carry the X-Marketplace-Passed header (it belongs to another operation, or the engine is too old to keep the header on job items), Fix returns no result and an error for which errors.Is(err, ErrMissingResultHeaders) is true, rather than a false verdict. Image still gives the image.

Example

The items of a "fix" job carry the marketplace's verdict, as Client.Fix returns it; those of a "remove-text" job have RemoveText likewise.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	job, err := client.Jobs.Create(ctx, &photos.JobRequest{
		Operation: photos.OperationFix,
		Params:    map[string]any{"marketplace": "etsy"},
		Images:    []photos.Input{photos.File("1.jpg"), photos.File("2.jpg")},
	})
	if err != nil {
		log.Fatal(err)
	}
	if job, err = client.Jobs.Wait(ctx, job.ID, nil); err != nil {
		log.Fatal(err)
	}
	for n, item := range job.Items {
		if item.Status != photos.ItemDone {
			continue
		}
		res, err := client.Jobs.Item(ctx, job.ID, n)
		if err != nil {
			log.Fatal(err)
		}
		fix, err := res.Fix()
		if errors.Is(err, photos.ErrMissingResultHeaders) {
			log.Fatal("this engine does not keep the verdict on job items")
		} else if err != nil {
			log.Fatal(err)
		}
		fmt.Println(item.Input, fix.Passed, fix.Findings)
		if err := fix.Save(fmt.Sprintf("fixed-%d%s", n, fix.Extension())); err != nil {
			log.Fatal(err)
		}
	}
}

func (*JobItemResult) IDPhoto

func (r *JobItemResult) IDPhoto() (*IDPhotoResult, error)

IDPhoto returns the result of an "id-photo" job's item as the synchronous Client.IDPhoto would: the photo, the findings and the head's size. Without the X-Id-Photo-Passed header it returns an ErrMissingResultHeaders, as RemoveText does.

func (*JobItemResult) Image

func (r *JobItemResult) Image() *Image

Image returns the result as an Image, with its size, quality, timing and SubjectFound (a "crop" or "remove-background" job) read from the headers, or nil if the result is not an image. The SVG of a "vectorize" job is one.

func (*JobItemResult) IsImage

func (r *JobItemResult) IsImage() bool

IsImage reports whether the result is an image.

func (*JobItemResult) IsJSON

func (r *JobItemResult) IsJSON() bool

IsJSON reports whether the result is JSON.

func (*JobItemResult) JSON

func (r *JobItemResult) JSON(v any) error

JSON decodes a JSON result into v: a *CheckResult for a "check" job, a *TextResult for "ocr", a *BarcodesResult for "barcodes", a *LabelResult for "label", a *PaletteResult for "palette", a *FacesResult for "faces", a *CaptionResult for "caption", or any type of your own.

func (*JobItemResult) RemoveBackground

func (r *JobItemResult) RemoveBackground() (*Image, error)

RemoveBackground returns the result of a "remove-background" job's item as the synchronous Client.RemoveBackground would: the image, whose SubjectFound is false when a canvas was asked for and the photo has no subject.

The API sends the X-Subject-Found header only then. A result without it is not an error: its SubjectFound is nil, which means "not known". A result that is not an image is an ErrMissingResultHeaders.

func (*JobItemResult) RemoveText

func (r *JobItemResult) RemoveText() (*RemoveTextResult, error)

RemoveText returns the result of a "remove-text" job's item as the synchronous Client.RemoveText would: the image, and how many lines of text were removed and kept.

If the result does not carry the X-Text-Removed header (it belongs to another operation, or the engine is too old to keep the header on job items), RemoveText returns no result and an error for which errors.Is(err, ErrMissingResultHeaders) is true, rather than zero counts. Image still gives the image.

func (*JobItemResult) RestoreFaces

func (r *JobItemResult) RestoreFaces() (*RestoreFacesResult, error)

RestoreFaces returns the result of a "restore-faces" job's item as the synchronous Client.RestoreFaces would: the image, and how many faces were restored and skipped. Without the X-Faces-Skipped header it returns an ErrMissingResultHeaders, as RemoveText does.

func (*JobItemResult) Select

func (r *JobItemResult) Select() (*SelectResult, error)

Select returns the result of a "select" job's item as the synchronous Client.Select would: the cutout or the mask, and the model's score. Without the X-Select-Score header it returns an ErrMissingResultHeaders, as RemoveText does.

func (*JobItemResult) Vectorize

func (r *JobItemResult) Vectorize() (*VectorizeResult, error)

Vectorize returns the result of a "vectorize" job's item as the synchronous Client.Vectorize would: the SVG and how many paths it has. Without the X-Paths header it returns an ErrMissingResultHeaders, as RemoveText does.

type JobRequest

type JobRequest struct {
	// Operation is the operation to run: one of the Operation constants.
	Operation string
	// Params are the operation's parameters, named as in the HTTP API:
	// {"format": "png", "crop": true}. Values may be strings, booleans and
	// numbers, and a []string for a parameter that repeats, such as the
	// "point" of a "select" job.
	//
	// An item is one image, so a "watermark" job takes "text" only and a
	// "remove-background" job no background image.
	Params map[string]any
	// Recipe is a recipe as TOML text, for OperationRun.
	Recipe string
	// URLs are images for the API to fetch: http(s) on public addresses.
	URLs []string
	// Images are images to upload. With none, the job is sent as JSON; with
	// any, as multipart/form-data, streamed like any other upload.
	Images []Input
	// WebhookURL, if set, receives a signed "job.completed" event when the
	// job is done; see VerifyWebhook.
	WebhookURL string
}

JobRequest describes a new job: one operation applied to every input.

type JobSummary

type JobSummary struct {
	ID        string `json:"id"`
	Operation string `json:"operation"`
	Status    string `json:"status"`
	// CreatedAt and FinishedAt are Unix seconds; FinishedAt is zero until
	// the job has completed.
	CreatedAt  int64     `json:"created_at"`
	FinishedAt int64     `json:"finished_at"`
	Counts     JobCounts `json:"counts"`
}

JobSummary is a job as Jobs.List shows it: without its items.

type JobWebhook

type JobWebhook struct {
	URL       string `json:"url"`
	Attempts  int    `json:"attempts"`
	Delivered bool   `json:"delivered"`
	LastError string `json:"last_error,omitempty"`
}

JobWebhook is the state of a job's webhook delivery.

type JobsService

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

JobsService runs one operation on many images in the background. Use it through Client.Jobs.

func (*JobsService) Create

func (s *JobsService) Create(ctx context.Context, req *JobRequest) (*Job, error)

Create starts a job and returns at once; follow it with Get or Wait, or give a WebhookURL.

Unlike other calls, Create is sent again only after a 503: after a connection failure the job may already exist.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	// Images the API fetches…
	job, err := client.Jobs.Create(ctx, &photos.JobRequest{
		Operation:  photos.OperationRemoveBackground,
		Params:     map[string]any{"format": "png", "crop": true},
		URLs:       []string{"https://example.com/photos/1.jpg", "https://example.com/photos/2.jpg"},
		WebhookURL: "https://example.com/hooks/photos",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(job.ID, job.Status)

	// …or images you upload.
	job, err = client.Jobs.Create(ctx, &photos.JobRequest{
		Operation: photos.OperationRun,
		Params:    map[string]any{"preset": "marketplace"},
		Images:    []photos.Input{photos.File("1.jpg"), photos.File("2.jpg")},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(job.ID, job.Counts.Total)
}

func (*JobsService) Delete

func (s *JobsService) Delete(ctx context.Context, id string) error

Delete cancels a job if it is unfinished, and deletes it and its results.

func (*JobsService) Get

func (s *JobsService) Get(ctx context.Context, id string) (*Job, error)

Get returns a job's status, progress and per-item results.

func (*JobsService) Item

func (s *JobsService) Item(ctx context.Context, id string, n int) (*JobItemResult, error)

Item returns the result of item n (from 0) of a job, once that item is done.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")

	// The result of an "ocr" job's first item is JSON.
	res, err := client.Jobs.Item(context.Background(), "job_5f0c", 0)
	if err != nil {
		log.Fatal(err)
	}
	var text photos.TextResult
	if err := res.JSON(&text); err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(text.Lines), "lines")
}

func (*JobsService) List

func (s *JobsService) List(ctx context.Context) ([]JobSummary, error)

List returns your recent jobs, newest first (up to 100), without their items.

Example
package main

import (
	"context"
	"fmt"
	"log"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	jobs, err := client.Jobs.List(ctx)
	if err != nil {
		log.Fatal(err)
	}
	for _, j := range jobs {
		fmt.Println(j.ID, j.Operation, j.Status, j.Counts.Done, "/", j.Counts.Total)
		if j.Status == photos.JobCompleted {
			if err := client.Jobs.Delete(ctx, j.ID); err != nil {
				log.Fatal(err)
			}
		}
	}
}

func (*JobsService) Wait

func (s *JobsService) Wait(ctx context.Context, id string, opts *WaitOptions) (*Job, error)

Wait polls a job until it has completed and returns it. opts may be nil.

If the job has not completed within opts.Timeout, Wait returns the job as last seen and an error for which errors.Is(err, ErrWaitTimeout) is true. If ctx ends first, it returns ctx's error.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	photos "github.com/photos-sh/photos-go"
)

func main() {
	client, _ := photos.New("")
	ctx := context.Background()

	job, err := client.Jobs.Wait(ctx, "job_5f0c", &photos.WaitOptions{
		Interval: 5 * time.Second,
		Timeout:  30 * time.Minute,
	})
	if errors.Is(err, photos.ErrWaitTimeout) {
		log.Fatalf("still %s: %d of %d done", job.Status, job.Counts.Done, job.Counts.Total)
	} else if err != nil {
		log.Fatal(err)
	}

	for n, item := range job.Items {
		if item.Status != photos.ItemDone {
			fmt.Println(item.Input, "failed:", item.Error)
			continue
		}
		res, err := client.Jobs.Item(ctx, job.ID, n)
		if err != nil {
			log.Fatal(err)
		}
		if img := res.Image(); img != nil {
			if err := img.Save(fmt.Sprintf("result-%d%s", n, img.Extension())); err != nil {
				log.Fatal(err)
			}
		}
	}
}

type LabelAlcohol

type LabelAlcohol struct {
	// ABV is alcohol by volume, in percent.
	ABV  float64 `json:"abv"`
	Text string  `json:"text,omitempty"`
}

LabelAlcohol is an alcohol statement.

type LabelDate

type LabelDate struct {
	// Kind is "best_before", "use_by", "expiry", "production" or "packed".
	Kind string `json:"kind"`
	// Date is an ISO date or month, when it could be read.
	Date string `json:"date,omitempty"`
	Text string `json:"text,omitempty"`
}

LabelDate is a date printed on a label.

type LabelDetails

type LabelDetails struct {
	// Headline is the largest text: brand and product name candidates.
	Headline    []string        `json:"headline,omitempty"`
	GTIN        string          `json:"gtin,omitempty"`
	GS1         *LabelGS1       `json:"gs1,omitempty"`
	NetQuantity *LabelQuantity  `json:"net_quantity,omitempty"`
	Alcohol     *LabelAlcohol   `json:"alcohol,omitempty"`
	AgeYears    *int            `json:"age_years,omitempty"`
	Vintage     *int            `json:"vintage,omitempty"`
	Bottled     *int            `json:"bottled,omitempty"`
	Dates       []LabelDate     `json:"dates,omitempty"`
	Lot         string          `json:"lot,omitempty"`
	Serial      string          `json:"serial,omitempty"`
	Model       string          `json:"model,omitempty"`
	Ingredients string          `json:"ingredients,omitempty"`
	Allergens   []string        `json:"allergens,omitempty"`
	MayContain  []string        `json:"may_contain,omitempty"`
	Nutrition   *LabelNutrition `json:"nutrition,omitempty"`
	Materials   []LabelMaterial `json:"materials,omitempty"`
	Origin      string          `json:"origin,omitempty"`
	Electrical  *LabelElectric  `json:"electrical,omitempty"`
	// Claims are among "organic", "vegan", "vegetarian", "gluten_free",
	// "lactose_free", "no_added_sugar", "sugar_free", "non_gmo",
	// "fairtrade", "kosher", "halal".
	Claims []string `json:"claims,omitempty"`
}

LabelDetails are the details read off a product's label. A field that was not found is its zero value: an empty string, a nil slice or a nil pointer.

type LabelElectric

type LabelElectric struct {
	Voltage    string   `json:"voltage,omitempty"`
	Frequency  string   `json:"frequency,omitempty"`
	PowerW     *float64 `json:"power_w,omitempty"`
	BatteryMAh *float64 `json:"battery_mah,omitempty"`
}

LabelElectric is the electrical rating of a device.

type LabelGS1

type LabelGS1 struct {
	GTIN        string   `json:"gtin,omitempty"`
	Batch       string   `json:"batch,omitempty"`
	Serial      string   `json:"serial,omitempty"`
	Expiry      string   `json:"expiry,omitempty"`
	BestBefore  string   `json:"best_before,omitempty"`
	Production  string   `json:"production,omitempty"`
	Packed      string   `json:"packed,omitempty"`
	NetWeightKg *float64 `json:"net_weight_kg,omitempty"`
	Count       *int     `json:"count,omitempty"`
}

LabelGS1 is the content of a GS1 code (GS1-128, GS1 DataMatrix).

type LabelMaterial

type LabelMaterial struct {
	Name    string   `json:"name"`
	Percent *float64 `json:"percent,omitempty"`
}

LabelMaterial is one material of a textile composition.

type LabelNutrition

type LabelNutrition struct {
	// Per is the reference amount, such as "100g" or "100ml".
	Per          string   `json:"per,omitempty"`
	EnergyKJ     *float64 `json:"energy_kj,omitempty"`
	EnergyKcal   *float64 `json:"energy_kcal,omitempty"`
	Fat          *float64 `json:"fat,omitempty"`
	Saturates    *float64 `json:"saturates,omitempty"`
	Carbohydrate *float64 `json:"carbohydrate,omitempty"`
	Sugars       *float64 `json:"sugars,omitempty"`
	Fibre        *float64 `json:"fibre,omitempty"`
	Protein      *float64 `json:"protein,omitempty"`
	Salt         *float64 `json:"salt,omitempty"`
}

LabelNutrition is a nutrition table; values not printed are nil.

type LabelQuantity

type LabelQuantity struct {
	Value float64 `json:"value"`
	// Unit is "ml", "cl", "dl", "l", "fl_oz", "g", "kg", "oz" or "lb".
	Unit  string   `json:"unit"`
	Ml    *float64 `json:"ml,omitempty"`
	Grams *float64 `json:"grams,omitempty"`
	// Count is the number of items in a multipack.
	Count *int   `json:"count,omitempty"`
	Text  string `json:"text,omitempty"`
}

LabelQuantity is a net quantity, such as "70 cl" or "6 × 330 ml".

type LabelResult

type LabelResult struct {
	// Label is what was understood; fields not found are left zero.
	Label LabelDetails `json:"label"`
	// Barcodes are the codes found, as the API gives them.
	Barcodes []map[string]any `json:"barcodes"`
	// Text is every line read.
	Text []string `json:"text"`
	// Ms is how long each step took, in milliseconds.
	Ms map[string]int64 `json:"ms,omitempty"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

LabelResult is what ReadLabel returns.

func (*LabelResult) UnmarshalJSON

func (r *LabelResult) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the result and keeps the JSON in Raw.

type Marketplace

type Marketplace struct {
	Name  string `json:"name"`
	Title string `json:"title"`
	// Checked is the date (YYYY-MM-DD) the rules were last checked against
	// the sources.
	Checked string   `json:"checked"`
	Sources []string `json:"sources"`
	// Notes are rules that cannot be checked automatically.
	Notes []string `json:"notes,omitempty"`
	// Rules are the rules as the API gives them; each has at least "check"
	// and "severity".
	Rules []map[string]any `json:"rules"`
	// Fix is what Fix produces for this marketplace.
	Fix map[string]any `json:"fix"`
	// Raw is the marketplace as it came.
	Raw json.RawMessage `json:"-"`
}

Marketplace is a marketplace's image rules.

func (*Marketplace) UnmarshalJSON

func (m *Marketplace) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the marketplace and keeps the JSON in Raw.

type Measure

type Measure struct {
	Value float64 `json:"value"`
	Unit  string  `json:"unit"`
}

Measure is a value read off a label, with its unit.

type Metrics

type Metrics struct {
	Width  int `json:"width"`
	Height int `json:"height"`
	// Sharpness is edge crispness; under 0.3 is blurry, under 0.5 soft.
	Sharpness float64 `json:"sharpness"`
	// Noise is in grey levels (above 4 is visible grain); nil when it could
	// not be measured.
	Noise             *float64 `json:"noise"`
	Brightness        float64  `json:"brightness"`
	HighlightsClipped float64  `json:"highlights_clipped"`
	ShadowsClipped    float64  `json:"shadows_clipped"`
	Brightest         float64  `json:"brightest"`
	Border            Border   `json:"border"`
	// Subject is the product, when it was looked for and found.
	Subject *Subject `json:"subject"`
}

Metrics are the measurements of a photo.

type Option

type Option func(*Client) error

Option changes how New builds a Client.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets the address of the API, for example "http://localhost:8080". A trailing slash is ignored. It takes precedence over the PHOTOS_BASE_URL environment variable.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient makes the Client send its requests with hc (for a proxy, a custom transport, or tracing). The Client's own timeout still applies to every attempt; see WithTimeout.

func WithRetries

func WithRetries(n int) Option

WithRetries sets how many times a request is sent again after a 503 response or a connection failure (default DefaultRetries). Zero turns retries off.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets how long one HTTP attempt may take, from sending the request to reading the last byte of the response. The default is DefaultTimeout (120 s; model work takes seconds). Zero means no timeout beyond the context's own deadline.

func WithUserAgent

func WithUserAgent(userAgent string) Option

WithUserAgent puts a product token for your application in front of the library's own, giving "<userAgent> photos-sh-go/<version>".

type PaletteColor

type PaletteColor struct {
	// Hex is the colour as "#rrggbb".
	Hex string `json:"hex"`
	// RGB is its red, green and blue, each 0–255.
	RGB []int `json:"rgb"`
	// Share is the fraction of the visible image this colour stands for,
	// 0–1.
	Share float64 `json:"share"`
}

PaletteColor is one colour of a palette.

type PaletteOptions

type PaletteOptions struct {
	// Colors is how many colours to return at most, 1–16 (default 5).
	Colors int
}

PaletteOptions are the options of Palette. Zero fields are not sent.

type PaletteResult

type PaletteResult struct {
	// Width and Height are the image's size in pixels.
	Width  int `json:"width"`
	Height int `json:"height"`
	// Colors are the image's main colours, the most common first.
	Colors []PaletteColor `json:"colors"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

PaletteResult is what Palette returns.

func (*PaletteResult) UnmarshalJSON

func (r *PaletteResult) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the result and keeps the JSON in Raw.

type Point

type Point struct {
	X, Y float64
}

Point is a position in an image in pixels, from its top-left corner. In JSON it is an [x, y] pair.

func (Point) MarshalJSON

func (p Point) MarshalJSON() ([]byte, error)

MarshalJSON encodes the point as [x, y].

func (*Point) UnmarshalJSON

func (p *Point) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the point from [x, y].

type Preset

type Preset struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

Preset is a built-in preset for Run.

type ReadBarcodesOptions

type ReadBarcodesOptions struct {
	// AllFormats adds EAN-8, UPC-E, Code 128/39/93, QR, Data Matrix and
	// PDF417 to the retail codes read by default.
	AllFormats bool
	// Engine is "auto" (default), "zbar" or "rxing".
	Engine string
}

ReadBarcodesOptions are the options of ReadBarcodes. Zero fields are not sent.

type Rect

type Rect struct {
	X, Y, Width, Height int
}

Rect is an area of an image in pixels, from its top-left corner.

type RemoveBackgroundOptions

type RemoveBackgroundOptions struct {
	// Format is the output format: "png" (the default), "jpg", "webp" or "avif".
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
	// Background composites the cutout onto a colour ("white", "#rrggbb").
	// Empty keeps it transparent (JPEG output defaults to white).
	Background string
	// Crop crops to the subject.
	Crop bool
	// Padding is the margin around the subject when cropping, in pixels.
	Padding int
	// Mask returns the greyscale alpha mask instead of the cutout.
	Mask bool
	// Width and Height place the subject on a canvas of this size, in
	// pixels. Give both. Without them the photo keeps its size.
	Width  int
	Height int
	// Fill is how much of the canvas the subject may take in its tighter
	// dimension, 0.05 to 1 (default 0.85). It needs Width and Height.
	Fill float64
	// Align is where the subject sits on the canvas: "center" (the
	// default), "top" (hanging from the top margin) or "bottom" (standing on
	// the bottom margin).
	Align string
	// Shadow is "contact" (a soft shadow at the subject's base, as if it
	// stands on a surface), "drop" (the silhouette, offset and blurred) or
	// "none" (the default).
	Shadow string
	// Reflection adds a fading mirror image under the subject. Leave room
	// below it: a canvas with Fill under 1.
	Reflection bool
	// Blur keeps the photo's own background behind the subject, blurred:
	// 1 (slight) to 100.
	Blur float64
	// BackgroundImage is an image to put behind the subject; it is scaled to
	// cover the result. The zero Input means none.
	BackgroundImage Input
}

RemoveBackgroundOptions are the options of RemoveBackground. Zero fields are not sent.

The fields after Mask stage the cutout in the same call: a canvas with the subject at a set size and position, a shadow or a reflection under it, and something behind it. Give at most one of Background, BackgroundImage and Blur. Mask takes none of them, and Crop goes with neither a canvas nor Blur.

type RemoveTextOptions

type RemoveTextOptions struct {
	// Region is RegionBackground (default: only text off the product) or
	// RegionAll.
	Region string
	// Matching limits removal to lines matching this case-insensitive
	// regular expression, such as `sale|€|www\.`.
	Matching string
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

RemoveTextOptions are the options of RemoveText. Zero fields are not sent.

type RemoveTextResult

type RemoveTextResult struct {
	Image
	// Removed is how many lines of text were painted out (X-Text-Removed).
	Removed int
	// Kept is how many were left in place (X-Text-Kept).
	Kept int
}

RemoveTextResult is the image RemoveText returns, and what it did.

type ResizeOptions

type ResizeOptions struct {
	// Width and Height are the target size in pixels. Give one to keep the
	// proportions, or both.
	Width  int
	Height int
	// Fit says how to fit when both sides are given: FitContain (default),
	// FitCover or FitFill. Cover and fill need both sides.
	Fit string
	// Enlarge allows scaling up with FitContain.
	Enlarge bool
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100 (WebP 100 is lossless).
	Quality int
	// MaxBytes lowers the quality until the file fits in this many bytes.
	MaxBytes int
	// Background flattens transparency onto this colour.
	Background string
	// Trim cuts away an even border first: whatever matches the top-left
	// pixel, or is transparent like it.
	Trim bool
	// TrimTolerance is how far a pixel may differ from the border's colour
	// and still be border, per channel, 0–255 (default 10). It needs Trim.
	// It is a pointer because 0 (only the exact colour) is a value: use Int.
	TrimTolerance *int
	// DPI is the print resolution to write into a JPEG or PNG, 1–2400. No
	// pixel changes; WebP and AVIF have no such field.
	DPI int
}

ResizeOptions are the options of Resize. Zero fields are not sent.

type RestoreFacesOptions

type RestoreFacesOptions struct {
	// Strength is how much of the restored face replaces the original,
	// 1–100 (default 100: all of it).
	Strength float64
	// MinScore is how sure the detector must be that something is a face,
	// 0.1–1 (default 0.6).
	MinScore float64
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

RestoreFacesOptions are the options of RestoreFaces. Zero fields are not sent.

type RestoreFacesResult

type RestoreFacesResult struct {
	Image
	// Faces is how many faces were restored (X-Faces).
	Faces int
	// FacesSkipped is how many were left alone because they are large and
	// detailed enough already (X-Faces-Skipped).
	FacesSkipped int
}

RestoreFacesResult is the image RestoreFaces returns, and what it did.

type RunOptions

type RunOptions struct {
	// Preset is the name of a built-in preset, such as "marketplace".
	Preset string
	// Recipe is a recipe as TOML text.
	Recipe string
}

RunOptions name what Run applies: a built-in preset (see Presets) or a recipe.

type SelectOptions

type SelectOptions struct {
	// Points are points on the object. More than one adds to the selection.
	Points []Point
	// Exclude are points that must stay outside the selection.
	Exclude []Point
	// Box is a box around the object. The zero Rect means none.
	Box Rect
	// Pick says which of the model's three answers to return: "auto" (the
	// default: the surest one that agrees with every point), "smallest" (the
	// part clicked) or "largest" (the whole thing it belongs to).
	Pick string
	// Mask returns the greyscale mask instead of the cutout. Erase takes it
	// as its Mask, to remove the object.
	Mask bool
	// Crop crops to the object.
	Crop bool
	// Padding is the margin around the object when cropping, in pixels.
	Padding int
	// Background composites the cutout onto a colour ("white", "#rrggbb").
	// Empty keeps it transparent (JPEG output defaults to white).
	Background string
	// Format is the output format: "png" (the default), "jpg", "webp" or "avif".
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

SelectOptions say which object Select cuts out: the one at Points, or in Box. At least one point or a box is needed. Coordinates are in the photo's pixels, after its EXIF rotation.

type SelectResult

type SelectResult struct {
	Image
	// Score is how well the model thinks the mask fits the object, 0 to 1
	// (X-Select-Score).
	Score float64
}

SelectResult is the cutout (or the mask) Select returns, and how sure the model is of it.

type Subject

type Subject struct {
	X      int `json:"x"`
	Y      int `json:"y"`
	Width  int `json:"width"`
	Height int `json:"height"`
	// Fill is the share of the frame the product spans.
	Fill float64 `json:"fill"`
	Area float64 `json:"area"`
	// Touches lists the edges the product touches: "top", "bottom", "left",
	// "right".
	Touches         []string `json:"touches"`
	BackgroundWhite float64  `json:"background_white"`
}

Subject is where the product is in a photo.

type TextLine

type TextLine struct {
	Text string `json:"text"`
	// Score is the mean character confidence, 0–1.
	Score float64 `json:"score"`
	// Box is the line's corners in pixels (x, y), clockwise from the
	// top-left.
	Box [][]float64 `json:"box"`
}

TextLine is one line of text in a photo.

type TextResult

type TextResult struct {
	// Lines are the lines of text, top to bottom.
	Lines []TextLine `json:"lines"`
	// Ms is how long each step took, in milliseconds.
	Ms map[string]int64 `json:"ms,omitempty"`
	// Raw is the response as it came.
	Raw json.RawMessage `json:"-"`
}

TextResult is what ReadText returns.

func (*TextResult) UnmarshalJSON

func (r *TextResult) UnmarshalJSON(data []byte) (err error)

UnmarshalJSON decodes the result and keeps the JSON in Raw.

type UpscaleOptions

type UpscaleOptions struct {
	// Scale is the factor, 1 to 4 (default 4).
	Scale float64
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

UpscaleOptions are the options of Upscale. Zero fields are not sent.

type VectorizeOptions

type VectorizeOptions struct {
	// Mode is "color" (the default) or "mono", which draws whatever is
	// darker than mid grey as one black shape.
	Mode string
	// Curves is "spline" (smooth curves, the default), "polygon" (straight
	// segments) or "pixel" (every pixel step, for pixel art).
	Curves string
	// Detail is the bits kept per colour channel when grouping pixels, 1–8
	// (default 8). Lower merges similar shades into fewer shapes.
	Detail int
	// Speckle drops areas smaller than this many pixels across, 0–100
	// (default 4). It is a pointer because 0 (keep everything) is a value:
	// use Int.
	Speckle *int
}

VectorizeOptions are the options of Vectorize. Zero fields are not sent.

type VectorizeResult

type VectorizeResult struct {
	Image
	// Paths is how many paths the SVG has (X-Paths).
	Paths int
}

VectorizeResult is the SVG Vectorize returns. Bytes is the SVG's text and ContentType "image/svg+xml"; Width and Height are the size the image was traced at.

type Verdict

type Verdict struct {
	Marketplace string `json:"marketplace"`
	Title       string `json:"title,omitempty"`
	// Passed is true when no "error" rule is broken and every one was
	// checked.
	Passed   bool      `json:"passed"`
	Findings []Finding `json:"findings"`
	// NotChecked are the rules that could not be checked.
	NotChecked []string `json:"not_checked"`
	// Notes are rules for the seller to check by eye.
	Notes   []string `json:"notes,omitempty"`
	Checked string   `json:"checked,omitempty"`
	Sources []string `json:"sources,omitempty"`
}

Verdict is a marketplace's judgement of a photo.

type WaitOptions

type WaitOptions struct {
	// Interval is the time between polls (default DefaultWaitInterval, 2 s).
	Interval time.Duration
	// Timeout is how long to wait in all (default DefaultWaitTimeout,
	// 10 min).
	Timeout time.Duration
}

WaitOptions say how Jobs.Wait polls. Zero fields mean the defaults.

type WatermarkOptions

type WatermarkOptions struct {
	// Text is the text to draw, up to 200 characters, in a bold sans-serif
	// with Latin, Greek and Cyrillic letters.
	Text string
	// Watermark is an image to put on the photo, such as a logo; a PNG with
	// transparency works best. The zero Input means none.
	Watermark Input
	// Color is the text's colour: "white" (the default), "black", "#rrggbb".
	Color string
	// Position is "top-left", "top", "top-right", "left", "center", "right",
	// "bottom-left", "bottom" or "bottom-right" (the default).
	Position string
	// Scale is the mark's width as a percentage of the photo's width, 1–100
	// (default 25).
	Scale float64
	// Opacity goes from 1 to 100 (default 50).
	Opacity float64
	// Margin is the distance from the edges as a percentage of the photo's
	// shorter side, 0–50 (default 3). It is a pointer because 0 (against the
	// edge) is a value: use Float.
	Margin *float64
	// Tile repeats the mark across the whole photo. Position is then unused.
	Tile bool
	// Format is the output format; the default is the photo's own.
	Format string
	// Quality is the JPEG/WebP/AVIF quality, 1–100.
	Quality int
}

WatermarkOptions say what Watermark puts on the photo, Text or a Watermark image (not both), and where. Zero fields are not sent.

type WebhookError

type WebhookError struct {
	Reason string
}

WebhookError is why VerifyWebhook rejected a delivery. Compare with errors.Is against ErrWebhookHeader, ErrWebhookSignature, ErrWebhookTimestamp and ErrWebhookPayload, or catch them all with errors.As and a *WebhookError.

func (*WebhookError) Error

func (e *WebhookError) Error() string

type WebhookSecret

type WebhookSecret struct {
	// Secret is the signing key, for VerifyWebhook.
	Secret string `json:"secret"`
	// Header is the name of the signature header, "Photos-Signature".
	Header string `json:"header"`
	// Scheme describes how the signature is made.
	Scheme string `json:"scheme"`
}

WebhookSecret is the key job webhooks are signed with.

Jump to

Keyboard shortcuts

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