aboutsummaryrefslogtreecommitdiff
path: root/cmd/uploud/main.go
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2024-05-28 10:08:04 -0400
committerXe Iaso <me@xeiaso.net>2024-05-28 10:09:26 -0400
commit479407203e6d6654974a4bd340f576cb0b167f55 (patch)
treed3c78f9ad4fee1ab1e51b735218764be27ce799f /cmd/uploud/main.go
parent79d43f5be33f97b36402ec8de7db51e36b1e7f45 (diff)
downloadx-479407203e6d6654974a4bd340f576cb0b167f55.tar.xz
x-479407203e6d6654974a4bd340f576cb0b167f55.zip
internal/avif: remove this package
Replace this with a bunch of smaller packages that embed WebAssembly libraries for the following functionality: * AVIF encoding * JXL encoding * WEBP encoding Also include HEIC decoding and add JXL encoding support to uploud/xedn Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd/uploud/main.go')
-rw-r--r--cmd/uploud/main.go316
1 files changed, 188 insertions, 128 deletions
diff --git a/cmd/uploud/main.go b/cmd/uploud/main.go
index cef798d..b9691e0 100644
--- a/cmd/uploud/main.go
+++ b/cmd/uploud/main.go
@@ -15,30 +15,130 @@ import (
"log"
"os"
"path/filepath"
- "runtime"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
- "github.com/chai2010/webp"
"github.com/disintegration/imaging"
+ "github.com/gen2brain/avif"
+ _ "github.com/gen2brain/heic"
+ "github.com/gen2brain/jpegxl"
+ "github.com/gen2brain/webp"
+ "golang.org/x/sync/errgroup"
"within.website/x/internal"
- "within.website/x/internal/avif"
"within.website/x/tigris"
)
var (
- b2Bucket = flag.String("tigris-bucket", "xedn", "Tigris bucket to dump things to")
-
- avifQuality = flag.Int("avif-quality", 24, "AVIF quality (higher is worse quality)")
avifEncoderSpeed = flag.Int("avif-encoder-speed", 0, "AVIF encoder speed (higher is faster)")
-
- jpegQuality = flag.Int("jpeg-quality", 85, "JPEG quality (lower means lower file size)")
-
- webpQuality = flag.Int("webp-quality", 50, "WEBP quality (higher is worse quality)")
+ jxlEffort = flag.Int("jxl-effort", 7, "JPEG XL encoding effort in the range [1,10]. Sets encoder effort/speed level without affecting decoding speed. Default is 7.")
+ imageQuality = flag.Int("image-quality", 85, "image quality (lower means lower file size)")
+ tigrisBucket = flag.String("tigris-bucket", "xedn", "Tigris bucket to dump things to")
+ webpMethod = flag.Int("webp-method", 4, "WebP encoding method (0-6, 0 is fastest-worst, 6 is slowest-best)")
noEncode = flag.Bool("no-encode", false, "if set, just upload the file directly without encoding")
)
+func main() {
+ internal.HandleStartup()
+
+ if flag.NArg() != 2 {
+ log.Fatalf("usage: %s <filename/folder> <b2 path>", os.Args[0])
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ td, err := os.MkdirTemp("", "uploud")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer os.RemoveAll(td)
+
+ st, err := os.Stat(flag.Arg(0))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if st.IsDir() {
+ files, err := os.ReadDir(flag.Arg(0))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, finfo := range files {
+ if !*noEncode {
+ if err := processImage(filepath.Join(flag.Arg(0), finfo.Name()), td); err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ if _, err := copyFile(filepath.Join(flag.Arg(0), finfo.Name()), filepath.Join(td, finfo.Name())); err != nil {
+ log.Fatal(err)
+ }
+ }
+ }
+ } else {
+ if !*noEncode {
+ if err := processImage(flag.Arg(0), td); err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ if _, err := copyFile(flag.Arg(0), filepath.Join(td, filepath.Base(flag.Arg(0)))); err != nil {
+ log.Fatal(err)
+ }
+ }
+ }
+
+ files, err := os.ReadDir(td)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ s3c, err := tigris.Client(context.Background())
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, finfo := range files {
+ log.Printf("uploading %s", finfo.Name())
+ fin, err := os.Open(filepath.Join(td, finfo.Name()))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fin.Close()
+
+ st, err := fin.Stat()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ shaSum, err := hashFileSha256(fin)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ md5Sum, err := hashFileMD5(fin)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ _, err = s3c.PutObject(ctx,
+ &s3.PutObjectInput{
+ Body: fin,
+ Bucket: tigrisBucket,
+ Key: aws.String(flag.Arg(1) + "/" + finfo.Name()),
+ ContentType: aws.String(mimeTypes[filepath.Ext(finfo.Name())]),
+ ContentLength: aws.Int64(st.Size()),
+ ChecksumSHA256: aws.String(shaSum),
+ ContentMD5: aws.String(md5Sum),
+ },
+ tigris.WithCreateObjectIfNotExists(),
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+}
+
func doAVIF(src image.Image, dstPath string) error {
dst, err := os.Create(dstPath)
if err != nil {
@@ -46,11 +146,12 @@ func doAVIF(src image.Image, dstPath string) error {
}
defer dst.Close()
- err = avif.Encode(dst, src, &avif.Options{
- Threads: runtime.GOMAXPROCS(0),
- Speed: *avifEncoderSpeed,
- Quality: *avifQuality,
+ err = avif.Encode(dst, src, avif.Options{
+ Quality: *imageQuality,
+ QualityAlpha: *imageQuality,
+ Speed: *avifEncoderSpeed,
})
+
if err != nil {
return err
}
@@ -60,6 +161,27 @@ func doAVIF(src image.Image, dstPath string) error {
return nil
}
+func doJXL(src image.Image, dstPath string) error {
+ dst, err := os.Create(dstPath)
+ if err != nil {
+ log.Fatalf("Can't create destination file: %v", err)
+ }
+ defer dst.Close()
+
+ err = jpegxl.Encode(dst, src, jpegxl.Options{
+ Quality: *imageQuality,
+ Effort: *jxlEffort,
+ })
+
+ if err != nil {
+ return err
+ }
+
+ log.Printf("Encoded JPEG XL at %s", dstPath)
+
+ return nil
+}
+
func doWEBP(src image.Image, dstPath string) error {
fout, err := os.Create(dstPath)
if err != nil {
@@ -67,7 +189,10 @@ func doWEBP(src image.Image, dstPath string) error {
}
defer fout.Close()
- err = webp.Encode(fout, src, &webp.Options{Quality: float32(*webpQuality)})
+ err = webp.Encode(fout, src, webp.Options{
+ Quality: *imageQuality,
+ Method: *webpMethod,
+ })
if err != nil {
return err
}
@@ -88,7 +213,7 @@ func doJPEG(src image.Image, dstPath string) error {
}
defer fout.Close()
- if err := jpeg.Encode(fout, src, &jpeg.Options{Quality: *jpegQuality}); err != nil {
+ if err := jpeg.Encode(fout, src, &jpeg.Options{Quality: *imageQuality}); err != nil {
return err
}
@@ -104,7 +229,10 @@ func resizeSmol(src image.Image, dstPath string) error {
}
defer fout.Close()
- dstImg := imaging.Resize(src, 800, 0, imaging.Lanczos)
+ dstImg := src
+ if src.Bounds().Dx() > 800 {
+ dstImg = imaging.Resize(src, 800, 0, imaging.Lanczos)
+ }
enc := png.Encoder{
CompressionLevel: png.BestCompression,
@@ -131,19 +259,49 @@ func processImage(fname, tempDir string) error {
return err
}
- if err := doAVIF(src, filepath.Join(tempDir, fnameBase+".avif")); err != nil {
- return err
- }
+ eg, _ := errgroup.WithContext(context.Background())
- if err := doWEBP(src, filepath.Join(tempDir, fnameBase+".webp")); err != nil {
- return err
- }
+ eg.Go(func() error {
+ if err := doAVIF(src, filepath.Join(tempDir, fnameBase+".avif")); err != nil {
+ return fmt.Errorf("avif: %w", err)
+ }
- if err := doJPEG(src, filepath.Join(tempDir, fnameBase+".jpg")); err != nil {
- return err
- }
+ return nil
+ })
+
+ eg.Go(func() error {
+ if err := doJXL(src, filepath.Join(tempDir, fnameBase+".jxl")); err != nil {
+ return fmt.Errorf("jxl: %w", err)
+ }
+
+ return nil
+ })
+
+ eg.Go(func() error {
+ if err := doWEBP(src, filepath.Join(tempDir, fnameBase+".webp")); err != nil {
+ return fmt.Errorf("webp: %w", err)
+ }
+
+ return nil
+ })
- if err := resizeSmol(src, filepath.Join(tempDir, fnameBase+"-smol.png")); err != nil {
+ eg.Go(func() error {
+ if err := doJPEG(src, filepath.Join(tempDir, fnameBase+".jpg")); err != nil {
+ return fmt.Errorf("webp: %w", err)
+ }
+
+ return nil
+ })
+
+ eg.Go(func() error {
+ if err := resizeSmol(src, filepath.Join(tempDir, fnameBase+"-smol.png")); err != nil {
+ return fmt.Errorf("smol: %w", err)
+ }
+
+ return nil
+ })
+
+ if err := eg.Wait(); err != nil {
return err
}
@@ -175,111 +333,13 @@ func copyFile(src, dst string) (int64, error) {
return nBytes, err
}
-func main() {
- internal.HandleStartup()
-
- if flag.NArg() != 2 {
- log.Fatalf("usage: %s <filename/folder> <b2 path>", os.Args[0])
- }
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- td, err := os.MkdirTemp("", "uploud")
- if err != nil {
- log.Fatal(err)
- }
- defer os.RemoveAll(td)
-
- st, err := os.Stat(flag.Arg(0))
- if err != nil {
- log.Fatal(err)
- }
-
- if st.IsDir() {
- files, err := os.ReadDir(flag.Arg(0))
- if err != nil {
- log.Fatal(err)
- }
-
- for _, finfo := range files {
- if !*noEncode {
- if err := processImage(filepath.Join(flag.Arg(0), finfo.Name()), td); err != nil {
- log.Fatal(err)
- }
- } else {
- if _, err := copyFile(filepath.Join(flag.Arg(0), finfo.Name()), filepath.Join(td, finfo.Name())); err != nil {
- log.Fatal(err)
- }
- }
- }
- } else {
- if !*noEncode {
- if err := processImage(flag.Arg(0), td); err != nil {
- log.Fatal(err)
- }
- } else {
- if _, err := copyFile(flag.Arg(0), filepath.Join(td, filepath.Base(flag.Arg(0)))); err != nil {
- log.Fatal(err)
- }
- }
- }
-
- files, err := os.ReadDir(td)
- if err != nil {
- log.Fatal(err)
- }
-
- s3c, err := tigris.Client(context.Background())
- if err != nil {
- log.Fatal(err)
- }
-
- for _, finfo := range files {
- log.Printf("uploading %s", finfo.Name())
- fin, err := os.Open(filepath.Join(td, finfo.Name()))
- if err != nil {
- log.Fatal(err)
- }
- defer fin.Close()
-
- st, err := fin.Stat()
- if err != nil {
- log.Fatal(err)
- }
-
- shaSum, err := hashFileSha256(fin)
- if err != nil {
- log.Fatal(err)
- }
-
- md5Sum, err := hashFileMD5(fin)
- if err != nil {
- log.Fatal(err)
- }
-
- _, err = s3c.PutObject(ctx,
- &s3.PutObjectInput{
- Body: fin,
- Bucket: b2Bucket,
- Key: aws.String(flag.Arg(1) + "/" + finfo.Name()),
- ContentType: aws.String(mimeTypes[filepath.Ext(finfo.Name())]),
- ContentLength: aws.Int64(st.Size()),
- ChecksumSHA256: aws.String(shaSum),
- ContentMD5: aws.String(md5Sum),
- },
- tigris.WithCreateObjectIfNotExists(),
- )
- if err != nil {
- log.Fatal(err)
- }
- }
-}
-
var mimeTypes = map[string]string{
".avif": "image/avif",
".webp": "image/webp",
".jpg": "image/jpeg",
+ ".jpeg": "image/jpeg",
+ ".heic": "image/heic",
+ ".jxl": "image/jxl",
".png": "image/png",
".svg": "image/svg+xml",
".wasm": "application/wasm",