aboutsummaryrefslogtreecommitdiff
path: root/image/webp/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'image/webp/client.go')
-rw-r--r--image/webp/client.go117
1 files changed, 117 insertions, 0 deletions
diff --git a/image/webp/client.go b/image/webp/client.go
new file mode 100644
index 0000000..6b08b72
--- /dev/null
+++ b/image/webp/client.go
@@ -0,0 +1,117 @@
1package webp
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "image"
8 "io"
9 "net"
10 "net/http"
11 "os"
12 "os/exec"
13 "path"
14 "strconv"
15 "syscall"
16
17 "golang.org/x/image/tiff"
18)
19
20// WebPConvertClient starts a webp conversion server and manages
21// its lifecycle. Call Convert to convert an image.Image to a webp
22// bytestream. This struct is only threadsafe after Start has been
23// called.
24type WebPConvertClient struct {
25 p *exec.Cmd
26 c *http.Client
27}
28
29// Start starts the server, cancelling the passed context will stop
30// the server process. It is important to pass a real context into the
31// process and not just context.Background. This function will return an
32// error if setup fails but does not block.
33func (c *WebPConvertClient) Start(ctx context.Context) error {
34 sockDir, err := os.MkdirTemp("", "webp-*")
35 if err != nil {
36 return err
37 }
38
39 sockPath := path.Join(sockDir, "webp.sock")
40
41 c.c = &http.Client{
42 Transport: &http.Transport{
43 DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
44 var d net.Dialer
45 return d.DialContext(ctx, "unix", sockPath)
46 },
47 },
48 }
49
50 c.p = exec.CommandContext(ctx, "webp-convert-server", sockPath)
51 if c.p.Err != nil {
52 return c.p.Err
53 }
54
55 c.p.Cancel = func() error {
56 if err := c.p.Process.Signal(syscall.SIGTERM); err != nil {
57 return err
58 }
59 return os.ErrProcessDone
60 }
61
62 ec := make(chan error, 1)
63 go func() {
64 ec <- c.p.Run()
65 os.RemoveAll(sockDir)
66 }()
67
68 select {
69 case err = <-ec:
70 return err
71 default:
72 return nil
73 }
74}
75
76// Convert encodes an image as TIFF and sends it to the conversion
77// server. It returns either an error or the webp encoded bytes from the
78// input image.
79func (c *WebPConvertClient) Convert(ctx context.Context, img image.Image, quality int, lossless, exact bool) ([]byte, error) {
80 if c.c == nil {
81 return nil, fmt.Errorf("WebPConvertClient has not been started")
82 }
83
84 bb := &bytes.Buffer{}
85 if err := tiff.Encode(bb, img, &tiff.Options{Compression: tiff.Uncompressed}); err != nil {
86 return nil, err
87 }
88
89 req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://unix/", bb)
90 if err != nil {
91 return nil, err
92 }
93 req.Header.Add("quality", strconv.Itoa(quality))
94 req.Header.Add("lossless", fmt.Sprintf("%t", lossless))
95 req.Header.Add("exact", fmt.Sprintf("%t", exact))
96
97 res, err := c.c.Do(req)
98 if err != nil {
99 return nil, err
100 }
101 defer res.Body.Close()
102
103 if res.StatusCode != 200 {
104 e, err := io.ReadAll(res.Body)
105 if err != nil {
106 return nil, err
107 }
108 return nil, fmt.Errorf("WebPConvertClient server error: %s", e)
109 }
110
111 bb.Reset()
112 if _, err := io.Copy(bb, res.Body); err != nil {
113 return nil, err
114 }
115
116 return bb.Bytes(), nil
117}