internal/format/magick_test.go
175 lines · 4539 bytes
1package format
2
3import (
4 "bytes"
5 "fmt"
6 "image/jpeg"
7 "os/exec"
8 "path/filepath"
9 "strings"
10 "testing"
11 "time"
12)
13
14func requireTools(t *testing.T) {
15 t.Helper()
16 if err := CheckTools(); err != nil {
17 t.Skip(err)
18 }
19}
20
21func makeImage(t *testing.T, name string, w, h int) string {
22 t.Helper()
23 p := filepath.Join(t.TempDir(), name)
24 run(t, "magick", "-size", fmt.Sprintf("%dx%d", w, h), "xc:gray", p)
25 return p
26}
27
28func run(t *testing.T, name string, args ...string) {
29 t.Helper()
30 if out, err := exec.Command(name, args...).CombinedOutput(); err != nil {
31 t.Fatalf("%s %v: %v\n%s", name, args, err, out)
32 }
33}
34
35func resizedSize(t *testing.T, f Format, src string, w int) (int, int) {
36 t.Helper()
37 var buf bytes.Buffer
38 if err := f.Resize(src, w, &buf); err != nil {
39 t.Fatal(err)
40 }
41 cfg, err := jpeg.DecodeConfig(&buf)
42 if err != nil {
43 t.Fatalf("output is not JPEG: %v", err)
44 }
45 return cfg.Width, cfg.Height
46}
47
48func TestMagickJPEG(t *testing.T) {
49 requireTools(t)
50 p := makeImage(t, "a.jpg", 300, 200)
51 run(t, "exiftool", "-overwrite_original", "-DateTimeOriginal=2025:03:14 23:10:05", "-Model=TestCam", p)
52
53 f := NewMagick("jpeg", ".jpg", ".jpeg")
54 m, err := f.Metadata(p)
55 if err != nil {
56 t.Fatal(err)
57 }
58 if m.Width != 300 || m.Height != 200 || m.Camera != "TestCam" {
59 t.Fatalf("got %+v", m)
60 }
61 if want := time.Date(2025, 3, 14, 23, 10, 5, 0, time.UTC); !m.Taken.Equal(want) {
62 t.Fatalf("Taken = %v", m.Taken)
63 }
64 if w, h := resizedSize(t, f, p, 150); w != 150 || h != 100 {
65 t.Fatalf("resized to %dx%d, want 150x100", w, h)
66 }
67}
68
69func TestMagickOrientation(t *testing.T) {
70 requireTools(t)
71 p := makeImage(t, "r.jpg", 300, 200)
72 run(t, "exiftool", "-overwrite_original", "-n", "-Orientation=6", p)
73
74 f := NewMagick("jpeg", ".jpg")
75 m, err := f.Metadata(p)
76 if err != nil {
77 t.Fatal(err)
78 }
79 if m.Width != 200 || m.Height != 300 {
80 t.Fatalf("metadata %dx%d, want 200x300", m.Width, m.Height)
81 }
82 if w, h := resizedSize(t, f, p, 100); w != 100 || h != 150 {
83 t.Fatalf("resized to %dx%d, want 100x150", w, h)
84 }
85}
86
87func TestMagickPNG(t *testing.T) {
88 requireTools(t)
89 p := makeImage(t, "a.png", 120, 80)
90 f := NewMagick("png", ".png")
91 m, err := f.Metadata(p)
92 if err != nil {
93 t.Fatal(err)
94 }
95 if m.Width != 120 || m.Height != 80 {
96 t.Fatalf("got %+v", m)
97 }
98 if w, h := resizedSize(t, f, p, 60); w != 60 || h != 40 {
99 t.Fatalf("resized to %dx%d, want 60x40", w, h)
100 }
101}
102
103func TestMagickResizeError(t *testing.T) {
104 requireTools(t)
105 var buf bytes.Buffer
106 if err := NewMagick("jpeg", ".jpg").Resize(filepath.Join(t.TempDir(), "missing.jpg"), 100, &buf); err == nil {
107 t.Fatal("want error")
108 }
109}
110
111func TestDefaultRegistry(t *testing.T) {
112 r := Default()
113 for path, want := range map[string]string{"a.JPG": "jpeg", "a.jpeg": "jpeg", "a.png": "png"} {
114 if f := r.Lookup(path); f == nil || f.Name() != want {
115 t.Errorf("%s: got %v, want %s", path, f, want)
116 }
117 }
118 if r.Lookup("a.heic") != nil {
119 t.Error("heic should not match in v1")
120 }
121}
122
123func TestMagickMetadataManyFiles(t *testing.T) {
124 requireTools(t)
125 f := NewMagick("jpeg", ".jpg")
126 dir := t.TempDir()
127 before := exiftoolStarts.Load()
128 for i := 1; i <= 20; i++ {
129 p := filepath.Join(dir, fmt.Sprintf("%02d.jpg", i))
130 run(t, "magick", "-size", fmt.Sprintf("%dx10", i*10), "xc:gray", p)
131 if i == 10 {
132 if _, err := f.Metadata(filepath.Join(dir, "missing.jpg")); err == nil {
133 t.Fatal("missing file: want error")
134 }
135 }
136 m, err := f.Metadata(p)
137 if err != nil {
138 t.Fatalf("%s: %v", p, err)
139 }
140 if m.Width != i*10 || m.Height != 10 {
141 t.Fatalf("%s: got %dx%d, want %dx10", p, m.Width, m.Height, i*10)
142 }
143 }
144 if n := exiftoolStarts.Load() - before; n > 1 {
145 t.Fatalf("started exiftool %d times for 21 calls, want at most 1", n)
146 }
147}
148
149func TestMagickMetadataRejectsNewlineInPath(t *testing.T) {
150 if _, err := NewMagick("jpeg", ".jpg").Metadata("a\nb.jpg"); err == nil {
151 t.Fatal("want error")
152 }
153}
154
155func TestResizeArgsLimitResources(t *testing.T) {
156 got := strings.Join(resizeArgs("/p/a.jpg", 960), " ")
157 want := "-limit memory 512MiB -limit map 1GiB /p/a.jpg -auto-orient -resize 960x -strip -quality 82 jpg:-"
158 if got != want {
159 t.Fatalf("got %s\nwant %s", got, want)
160 }
161}
162
163func TestMagickMetadataRelativePathArgfileSafe(t *testing.T) {
164 requireTools(t)
165 dir := t.TempDir()
166 run(t, "magick", "-size", "30x20", "xc:gray", filepath.Join(dir, "#a.jpg"))
167 t.Chdir(dir)
168 m, err := NewMagick("jpeg", ".jpg").Metadata("#a.jpg")
169 if err != nil {
170 t.Fatal(err)
171 }
172 if m.Width != 30 || m.Height != 20 {
173 t.Fatalf("got %dx%d", m.Width, m.Height)
174 }
175}