lens @ c2d666b43477ea7042b574ad940c508216cb0e83

fix: Fix content type

Content type was always being set to `text/html`.

Also swap lib for processing thumbnail for something that accepts HEIC.
  1diff --git a/cmd/server/main.go b/cmd/server/main.go
  2index 1bd445c277b1dafd0150aedc42880d55d3e008ca..224b37a3b9bc401adccc92f285e19642cb7be633 100644
  3--- a/cmd/server/main.go
  4+++ b/cmd/server/main.go
  5@@ -120,7 +120,7 @@ 	)
  6 
  7 	// worker
  8 	var (
  9-		serverWorker = worker.NewServerWorker(&fasthttp.Server{Handler: r.Handler})
 10+		serverWorker = worker.NewServerWorker(&fasthttp.Server{Handler: r.Handler, NoDefaultContentType: true})
 11 		fileWorker   = worker.NewWorkerFromChanProcessor[string](
 12 			fileScanner,
 13 			scheduler,
 14diff --git a/go.mod b/go.mod
 15index a473a48a10f27c01829261445a200578185a04dc..d94e64b19d879ebacbf1e91f5677a0f0bd2f185b 100644
 16--- a/go.mod
 17+++ b/go.mod
 18@@ -4,9 +4,9 @@ go 1.19
 19 
 20 require (
 21 	github.com/barasher/go-exiftool v1.10.0
 22-	github.com/disintegration/imaging v1.6.2
 23 	github.com/fasthttp/router v1.4.19
 24 	github.com/google/go-cmp v0.5.9
 25+	github.com/h2non/bimg v1.1.9
 26 	github.com/sirupsen/logrus v1.9.2
 27 	github.com/spf13/pflag v1.0.5
 28 	github.com/valyala/fasthttp v1.47.0
 29@@ -30,7 +30,6 @@ 	github.com/klauspost/compress v1.16.5 // indirect
 30 	github.com/mattn/go-sqlite3 v1.14.16 // indirect
 31 	github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
 32 	github.com/valyala/bytebufferpool v1.0.0 // indirect
 33-	golang.org/x/image v0.8.0 // indirect
 34 	golang.org/x/mod v0.10.0 // indirect
 35 	golang.org/x/sys v0.8.0 // indirect
 36 	golang.org/x/text v0.10.0 // indirect
 37diff --git a/go.sum b/go.sum
 38index 8694f645250aa9eae9b6d9d74ca7452bc73887f2..f50562cb18cd980d52bc4c300492e424a38e527b 100644
 39--- a/go.sum
 40+++ b/go.sum
 41@@ -5,8 +5,6 @@ github.com/barasher/go-exiftool v1.10.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
 42 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 43 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 44 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 45-github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
 46-github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
 47 github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
 48 github.com/fasthttp/router v1.4.19/go.mod h1:+Fh3YOd8x1+he6ZS+d2iUDBH9MGGZ1xQFUor0DE9rKE=
 49 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 50@@ -17,6 +15,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 51 github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
 52 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 53 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 54+github.com/h2non/bimg v1.1.9 h1:WH20Nxko9l/HFm4kZCA3Phbgu2cbHvYzxwxn9YROEGg=
 55+github.com/h2non/bimg v1.1.9/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
 56 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 57 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 58 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
 59@@ -50,51 +50,18 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 60 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 61 github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
 62 github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
 63-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 64-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 65-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 66 golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
 67 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
 68-golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 69-golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
 70-golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
 71-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 72-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 73 golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
 74 golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 75-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 76-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 77-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 78-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 79-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 80-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 81-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 82 golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
 83-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 84-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 85-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 86-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 87 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 88-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 89-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 90 golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
 91 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 92-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 93-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 94-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 95-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 96-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 97-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 98-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 99 golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
100 golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
101-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
102-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
103-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
104-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
105 golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
106 golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
107-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
108 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
109 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
110 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
111diff --git a/pkg/ext/middleware.go b/pkg/ext/middleware.go
112index d255c6d70f398251ed0e7a7340bb9169fad7bf9a..c83b9989cd8e0719062d77df3c6cdae69e5c63c1 100644
113--- a/pkg/ext/middleware.go
114+++ b/pkg/ext/middleware.go
115@@ -12,7 +12,9 @@ )
116 
117 func HTML(next fasthttp.RequestHandler) fasthttp.RequestHandler {
118 	return func(ctx *fasthttp.RequestCtx) {
119-		ctx.Response.Header.SetContentType("text/html")
120+		if len(ctx.Request.Header.ContentType()) > 0 {
121+			ctx.Response.Header.SetContentType("text/html")
122+		}
123 		next(ctx)
124 	}
125 }
126diff --git a/pkg/fileop/file.go b/pkg/fileop/file.go
127index 07c08e5d57eff695d8658ae7626adfb132e7f4d3..10e220242ecdd4124c057cca253bacb095bcc7a9 100644
128--- a/pkg/fileop/file.go
129+++ b/pkg/fileop/file.go
130@@ -12,6 +12,9 @@ 	return hex.EncodeToString(hash[:])
131 }
132 
133 func IsMimeTypeSupported(mimetype string) bool {
134-	return strings.HasPrefix(mimetype, "video") &&
135+	if mimetype == "image/svg+xml" {
136+		return false
137+	}
138+	return strings.HasPrefix(mimetype, "video") ||
139 		strings.HasPrefix(mimetype, "image")
140 }
141diff --git a/pkg/fileop/thumbnail.go b/pkg/fileop/thumbnail.go
142index 32f6064dc93654e14514875551c2c452fbe1d773..fcdfa12cbc6ae2668eeab49363456c0ecf7be84f 100644
143--- a/pkg/fileop/thumbnail.go
144+++ b/pkg/fileop/thumbnail.go
145@@ -1,60 +1,55 @@
146 package fileop
147 
148 import (
149-	"image"
150-	"image/jpeg"
151-	"os"
152+	"bytes"
153+	"fmt"
154 	"os/exec"
155+	"strconv"
156 
157-	"github.com/disintegration/imaging"
158+	"github.com/h2non/bimg"
159 )
160 
161 func EncodeImageThumbnail(inputPath string, outputPath string, width, height int) error {
162-	inputImage, err := imaging.Open(inputPath, imaging.AutoOrientation(true))
163+	buffer, err := bimg.Read(inputPath)
164 	if err != nil {
165 		return err
166 	}
167 
168-	thumbImage := imaging.Fit(inputImage, width, height, imaging.Lanczos)
169-	if err = encodeImageJPEG(thumbImage, outputPath, 60); err != nil {
170-		return err
171+	options := bimg.Options{
172+		Width:         width,
173+		Height:        height,
174+		Embed:         true,
175+		Type:          bimg.JPEG,
176+		StripMetadata: true,
177 	}
178 
179-	return nil
180-}
181-
182-func encodeImageJPEG(image image.Image, outputPath string, jpegQuality int) error {
183-	photo_file, err := os.Create(outputPath)
184-	if err != nil {
185-		return err
186-	}
187-	defer photo_file.Close()
188-
189-	err = jpeg.Encode(photo_file, image, &jpeg.Options{Quality: jpegQuality})
190+	newImage, err := bimg.NewImage(buffer).Process(options)
191 	if err != nil {
192 		return err
193 	}
194 
195-	return nil
196+	return bimg.Write(outputPath, newImage)
197 }
198 
199-func EncodeVideoThumbnail(inputPath string, outputPath string, width, height int) error {
200+func EncodeVideoThumbnail(inputPath string, outputPath string, width, _ int) error {
201 	args := []string{
202 		"-i",
203 		inputPath,
204-		"-vframes", "1", // output one frame
205-		"-an", // disable audio
206-		"-vf", "scale='min(1024,iw)':'min(1024,ih)':force_original_aspect_ratio=decrease:force_divisible_by=2",
207-		"-vf", "select=gte(n\\,100)",
208+		"-y",
209+		"-vframes", "1",
210+		"-q:v", "1",
211+		"-vf", "thumbnail,scale=" + strconv.Itoa(width) + ":-1",
212 		outputPath,
213 	}
214 
215 	cmd := exec.Command("ffmpeg", args...)
216 
217+	var b bytes.Buffer
218+	cmd.Stderr = &b
219+
220 	if err := cmd.Run(); err != nil {
221-		return err
222+		return fmt.Errorf("%s; %w", b.String(), err)
223 	}
224 
225 	return nil
226-
227 }
228diff --git a/pkg/worker/list_processor.go b/pkg/worker/list_processor.go
229index 0a07085d58ddb936e87789eafd6929f3f433f893..c060583bc9ebb22d2a0183deffe1ba42f7111acd 100644
230--- a/pkg/worker/list_processor.go
231+++ b/pkg/worker/list_processor.go
232@@ -16,6 +16,10 @@ 		Query(context.Context) (<-chan T, error)
233 		Process(context.Context, T) error
234 	}
235 
236+	OnFail[T any] interface {
237+		OnFail(context.Context, T, error)
238+	}
239+
240 	BatchProcessor[T any] interface {
241 		Query(context.Context) ([]T, error)
242 		Process(context.Context, T) error
243@@ -77,6 +81,12 @@ 		}
244 		var wg sync.WaitGroup
245 
246 		for _, v := range values {
247+			select {
248+			case <-ctx.Done():
249+				return ctx.Err()
250+			default:
251+			}
252+
253 			wg.Add(1)
254 			l.scheduler.Take()
255 			go func(v T) {
256@@ -84,6 +94,9 @@ 				defer l.scheduler.Return()
257 				defer wg.Done()
258 				if err := l.batchProcessor.Process(ctx, v); err != nil && !errors.Is(err, context.Canceled) {
259 					l.logrus.WithError(err).Error("Error processing batch")
260+					if failure, ok := l.batchProcessor.(OnFail[T]); ok {
261+						failure.OnFail(ctx, v, err)
262+					}
263 				}
264 			}(v)
265 		}
266diff --git a/pkg/worker/thumbnail_scanner.go b/pkg/worker/thumbnail_scanner.go
267index cc201b830ff9d562380baa44cf80a2926cad9619..168abef60b86033d7a8bad7118e361c6b013277d 100644
268--- a/pkg/worker/thumbnail_scanner.go
269+++ b/pkg/worker/thumbnail_scanner.go
270@@ -2,6 +2,7 @@ package worker
271 
272 import (
273 	"context"
274+	"fmt"
275 	"math"
276 	"os"
277 	"path"
278@@ -47,12 +48,12 @@
279 	if media.IsVideo() {
280 		err := fileop.EncodeVideoThumbnail(media.Path, output, 1080, 1080)
281 		if err != nil {
282-			return err
283+			return fmt.Errorf("Error thumbnail video %d; %w", media.ID, err)
284 		}
285 	} else {
286-		err := fileop.EncodeImageThumbnail(media.Path, output, 1080, math.MaxInt)
287+		err := fileop.EncodeImageThumbnail(media.Path, output, 1080, math.MinInt32)
288 		if err != nil {
289-			return err
290+			return fmt.Errorf("Error thumbnail image %d; %w", media.ID, err)
291 		}
292 	}
293