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