diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-05-28 10:08:04 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-05-28 10:09:26 -0400 |
| commit | 479407203e6d6654974a4bd340f576cb0b167f55 (patch) | |
| tree | d3c78f9ad4fee1ab1e51b735218764be27ce799f /cmd/uploud/main.go | |
| parent | 79d43f5be33f97b36402ec8de7db51e36b1e7f45 (diff) | |
| download | x-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.go | 316 |
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", |
