Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions internal/fetch/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ func fetch(ctx context.Context, r *Request) (int, error) {
req = req.WithContext(ctx)
}

return makeRequest(r, c, req)
return makeRequest(ctx, r, c, req)
}

func makeRequest(r *Request, c *client.Client, req *http.Request) (int, error) {
func makeRequest(ctx context.Context, r *Request, c *client.Client, req *http.Request) (int, error) {
resp, err := c.Do(req)
if err != nil {
return 0, err
Expand All @@ -177,7 +177,7 @@ func makeRequest(r *Request, c *client.Client, req *http.Request) (int, error) {
p.Flush()
}

body, err := formatResponse(r, resp, r.PrinterHandle.Stdout())
body, err := formatResponse(ctx, r, resp, r.PrinterHandle.Stdout())
if err != nil {
return 0, err
}
Expand All @@ -193,7 +193,7 @@ func makeRequest(r *Request, c *client.Client, req *http.Request) (int, error) {
return exitCode, nil
}

func formatResponse(r *Request, resp *http.Response, p *core.Printer) (io.Reader, error) {
func formatResponse(ctx context.Context, r *Request, resp *http.Response, p *core.Printer) (io.Reader, error) {
if r.Output != "" && r.Output != "-" {
f, err := os.Create(r.Output)
if err != nil {
Expand Down Expand Up @@ -251,7 +251,7 @@ func formatResponse(r *Request, resp *http.Response, p *core.Printer) (io.Reader

switch contentType {
case TypeImage:
return nil, image.Render(buf)
return nil, image.Render(ctx, buf)
case TypeJSON:
if format.FormatJSON(buf, p) == nil {
buf = p.Bytes()
Expand All @@ -278,12 +278,7 @@ func getContentType(headers http.Header) ContentType {
if typ, subtype, ok := strings.Cut(mediaType, "/"); ok {
switch typ {
case "image":
switch subtype {
case "jpeg", "png", "tiff", "webp":
return TypeImage
default:
return TypeUnknown
}
return TypeImage
case "application":
switch subtype {
case "json":
Expand Down
86 changes: 86 additions & 0 deletions internal/image/adaptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package image

import (
"bytes"
"context"
"errors"
"image"
"os"
"os/exec"
"path/filepath"
"slices"
)

const imagePathArg = "IMAGE_PATH"

type adaptor struct {
name string
args []string
}

var adaptors = []adaptor{
{
name: "vips",
args: []string{"copy", imagePathArg, ".jpeg"},
},
{
name: "magick",
args: []string{imagePathArg, "-flatten", "-auto-orient", "jpeg:-"},
},
{
name: "ffmpeg",
args: []string{"-i", imagePathArg, "-f", "image2pipe", "-vcodec", "mjpeg", "pipe:1"},
},
}

func decodeWithAdaptors(ctx context.Context, b []byte) (image.Image, error) {
// Write the image to a temporary file.
dir, err := os.MkdirTemp("", "")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir)

imgPath := filepath.Join(dir, "fetch-temp-image")
err = os.WriteFile(imgPath, b, 0666)
if err != nil {
return nil, err
}

// Attempt each adaptor, stopping at the first successful one.
for _, a := range adaptors {
img, err := decodeAdaptor(ctx, imgPath, a.name, a.args)
if err == nil {
return img, nil
}
}
return nil, errors.New("unable to decode image")
}

func decodeAdaptor(ctx context.Context, imgPath, name string, args []string) (image.Image, error) {
path, err := exec.LookPath(name)
if err != nil {
// Adaptor not found locally, exit.
return nil, err
}

// Replace "IMAGE_PATH" argument with the actual image path.
args = slices.Clone(args)
for i, arg := range args {
if arg == imagePathArg {
args[i] = imgPath
}
}

// Run the command, collecting the result on stdout.
var stdout bytes.Buffer
cmd := exec.CommandContext(ctx, path, args...)
cmd.Stdout = &stdout
if err = cmd.Run(); err != nil {
return nil, err
}

// Attempt to decode the adaptor output.
img, _, err := image.Decode(&stdout)
return img, err
}
21 changes: 19 additions & 2 deletions internal/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package image

import (
"bytes"
"context"
"encoding/base64"
"image"
_ "image/jpeg"
Expand All @@ -15,8 +16,8 @@ import (

// Render renders the provided raw image to stdout based on what protocol the
// current terminal emulator supports.
func Render(b []byte) error {
img, _, err := image.Decode(bytes.NewReader(b))
func Render(ctx context.Context, b []byte) error {
img, err := decodeImage(ctx, b)
if err != nil {
return err
}
Expand Down Expand Up @@ -48,6 +49,22 @@ func Render(b []byte) error {
}
}

func decodeImage(ctx context.Context, b []byte) (image.Image, error) {
img, _, err := image.Decode(bytes.NewReader(b))
if err == nil {
return img, nil
}

// Unable to decode the image ourselves, attempt with the adaptors.
var errAdaptor error
img, errAdaptor = decodeWithAdaptors(ctx, b)
if errAdaptor == nil {
return img, nil
}

return nil, err
}

// resizeForTerm returns a new image that has been resized to fit in less than
// 80% of the terminal height.
func resizeForTerm(img image.Image, termWidthPx, termHeightPx int) image.Image {
Expand Down