internal/format/magick_test.go

main
gallery/internal/format/magick_test.go history · blame · raw

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}