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 | |
| 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>
| -rw-r--r-- | .go.mod.sri | 2 | ||||
| -rw-r--r-- | cmd/uploud/main.go | 316 | ||||
| -rw-r--r-- | cmd/xedn/imgoptimize.go | 10 | ||||
| -rw-r--r-- | cmd/xedn/uplodr/main.go | 56 | ||||
| -rw-r--r-- | go.mod | 6 | ||||
| -rw-r--r-- | go.sum | 12 | ||||
| -rw-r--r-- | internal/avif/COPYING | 121 | ||||
| -rw-r--r-- | internal/avif/README.md | 117 | ||||
| -rw-r--r-- | internal/avif/av1.c | 215 | ||||
| -rw-r--r-- | internal/avif/av1.h | 44 | ||||
| -rw-r--r-- | internal/avif/avif.go | 201 | ||||
| -rw-r--r-- | internal/avif/example_test.go | 42 | ||||
| -rw-r--r-- | internal/avif/hacker-nest.avif | bin | 22522 -> 0 bytes | |||
| -rw-r--r-- | internal/avif/mp4.go | 799 |
14 files changed, 250 insertions, 1691 deletions
diff --git a/.go.mod.sri b/.go.mod.sri index 7cc1de8..91bf166 100644 --- a/.go.mod.sri +++ b/.go.mod.sri @@ -1 +1 @@ -sha256-dumTVs9kNBwk6glAbRDz/phNyIub/oH3e77rq4GVfTU= +sha256-aMyVdkAqM0jygChfI6+nTJc7VC45cN43oA1rn6SZzc4= 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", diff --git a/cmd/xedn/imgoptimize.go b/cmd/xedn/imgoptimize.go index 5b509e7..c82c362 100644 --- a/cmd/xedn/imgoptimize.go +++ b/cmd/xedn/imgoptimize.go @@ -15,12 +15,14 @@ import ( "strings" "time" - "github.com/chai2010/webp" "github.com/disintegration/imaging" + "github.com/gen2brain/avif" + _ "github.com/gen2brain/heic" + _ "github.com/gen2brain/jpegxl" + "github.com/gen2brain/webp" "go.etcd.io/bbolt" "golang.org/x/sync/singleflight" "tailscale.com/metrics" - "within.website/x/internal/avif" ) type OptimizedImageServer struct { @@ -138,11 +140,11 @@ func (ois *OptimizedImageServer) ResizeTo(widthPixels int, character, mood, form return nil, err } case "webp": - if err := webp.Encode(&result, dstImg, &webp.Options{Quality: 95}); err != nil { + if err := webp.Encode(&result, dstImg, webp.Options{Quality: 95}); err != nil { return nil, err } case "avif": - if err := avif.Encode(&result, dstImg, &avif.Options{Quality: 48, Speed: avif.MaxSpeed}); err != nil { + if err := avif.Encode(&result, dstImg, avif.Options{Quality: 95, Speed: 7}); err != nil { return nil, err } default: diff --git a/cmd/xedn/uplodr/main.go b/cmd/xedn/uplodr/main.go index e1a9c10..148a0e6 100644 --- a/cmd/xedn/uplodr/main.go +++ b/cmd/xedn/uplodr/main.go @@ -14,18 +14,19 @@ import ( "net" "os" "path/filepath" - "runtime" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/credentials" "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" "google.golang.org/grpc" "within.website/x/cmd/xedn/uplodr/pb" "within.website/x/internal" - "within.website/x/internal/avif" "within.website/x/tigris" ) @@ -40,12 +41,10 @@ var ( tigrisBucket = flag.String("bucket-name", "xedn", "Tigris bucket to dump things to") - avifQuality = flag.Int("avif-quality", 8, "AVIF quality (higher is worse quality)") avifEncoderSpeed = flag.Int("avif-encoder-speed", 0, "AVIF encoder speed (higher is faster)") - - jpegQuality = flag.Int("jpeg-quality", 90, "JPEG quality (lower means lower file size)") - - webpQuality = flag.Int("webp-quality", 9, "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)") + webpMethod = flag.Int("webp-method", 4, "WebP encoding method (0-6, 0 is fastest-worst, 6 is slowest-best)") ) func main() { @@ -195,7 +194,7 @@ func (s *Server) Upload(ctx context.Context, ur *pb.UploadReq) (*pb.UploadResp, errs = append(errs, fmt.Errorf("while uploading %s to b2: %w", path, err)) continue } - slog.Debug("uploaded", "to", "tigris", "key", key) + slog.Debug("uploaded", "to", "b2", "key", key) result = append(result, &pb.Variant{ Url: fmt.Sprintf("https://cdn.xeiaso.net/file/christine-static/%s/%s", ur.GetFolder(), fname), @@ -221,16 +220,36 @@ 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, + if err := avif.Encode(dst, src, avif.Options{ + Quality: *imageQuality, + QualityAlpha: *imageQuality, + Speed: *avifEncoderSpeed, + }); err != nil { + return err + } + + log.Printf("Encoded AVIF at %s", dstPath) + + 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 AVIF at %s", dstPath) + log.Printf("Encoded JPEG XL at %s", dstPath) return nil } @@ -242,7 +261,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 } @@ -263,7 +285,9 @@ 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 } @@ -15,7 +15,6 @@ require ( github.com/bwmarrin/discordgo v0.28.1 github.com/c-bata/go-prompt v0.2.6 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/chai2010/webp v1.1.1 github.com/danrusei/gobot-bsky v0.1.0 github.com/disintegration/imaging v1.6.2 github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7 @@ -23,6 +22,10 @@ require ( github.com/expr-lang/expr v1.16.3 github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 github.com/fogleman/primitive v0.0.0-20190214200932-673f57e7b1b5 + github.com/gen2brain/avif v0.3.1 + github.com/gen2brain/heic v0.3.0 + github.com/gen2brain/jpegxl v0.3.0 + github.com/gen2brain/webp v0.4.4 github.com/go-interpreter/wagon v0.6.0 github.com/go-shiori/go-readability v0.0.0-20230421032831-c66949dfc0ad github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 @@ -104,6 +107,7 @@ require ( github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ebitengine/purego v0.7.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanw/esbuild v0.19.11 // indirect github.com/fasthttp/router v1.5.0 // indirect @@ -213,8 +213,6 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= -github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -288,6 +286,8 @@ github.com/eaburns/peggy v1.0.2/go.mod h1:X2pbl0EV5erfnK8kSGwo0lBCgMGokvR1E6KerA github.com/eaburns/pretty v0.0.0-20170305202417-362524b72369/go.mod h1:iW/TU1T4mA4w2KzqNbBCjacPFdJ9PfGvNSxr8ajT/iM= github.com/eaburns/pretty v1.0.0 h1:00W1wrrtMXUSqLPN0txS8j7g9qFXy6nA5vZVqVQOo6w= github.com/eaburns/pretty v1.0.0/go.mod h1:retcK8A0KEgdmb0nuxhvyxixwCmEPO7SKlK0IJhjg8A= +github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA= +github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= @@ -331,6 +331,14 @@ github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADi github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gaissmai/bart v0.4.1 h1:G1t58voWkNmT47lBDawH5QhtTDsdqRIO+ftq5x4P9Ls= github.com/gaissmai/bart v0.4.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= +github.com/gen2brain/avif v0.3.1 h1:womS2LKvhS/dSR3zIKUxtJW+riGlY48akGWqc+YgHtE= +github.com/gen2brain/avif v0.3.1/go.mod h1:s9sI2zo2cF6EdyRVCtnIfwL/Qb3k0TkOIEsz6ovK1ms= +github.com/gen2brain/heic v0.3.0 h1:YDw7cerzjnxmb+/o5RAEpRy9j4jsFYCh9DuP1NDOc7Q= +github.com/gen2brain/heic v0.3.0/go.mod h1:+x0Y/m2EP1kd6mWvC131B3IK4eoKtLBBqJJ1uJB8CT8= +github.com/gen2brain/jpegxl v0.3.0 h1:hfnZykNPnyVksr6gtHVoiEuOHfJwXnrJLtP4RN1B7Lk= +github.com/gen2brain/jpegxl v0.3.0/go.mod h1:zi+BuuV3cTYRG3dt6dS0nT3DxVLZXkQUCIOirPEzKXk= +github.com/gen2brain/webp v0.4.4 h1:1cpydwbHid5ReumxE3kHWua9eimKONjeEo5CxeUcAtE= +github.com/gen2brain/webp v0.4.4/go.mod h1:4B2bZAeutMBvabnV96t3S06eW3wWfr6uU5nw4X5j+zc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= diff --git a/internal/avif/COPYING b/internal/avif/COPYING deleted file mode 100644 index 0e259d4..0000000 --- a/internal/avif/COPYING +++ /dev/null @@ -1,121 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. diff --git a/internal/avif/README.md b/internal/avif/README.md deleted file mode 100644 index 9c9fef6..0000000 --- a/internal/avif/README.md +++ /dev/null @@ -1,117 +0,0 @@ -# go-avif [](https://travis-ci.org/Kagami/go-avif) [](https://godoc.org/github.com/Kagami/go-avif) - -go-avif implements -AVIF ([AV1 Still Image File Format](https://aomediacodec.github.io/av1-avif/)) -encoder for Go using libaom, the [high quality](https://github.com/Kagami/av1-bench) -AV1 codec. - -## Requirements - -Make sure libaom is installed. On typical Linux distro just run: - -#### Debian (and derivatives): -```bash -sudo apt-get install libaom-dev -``` - -#### RHEL (and derivatives): -```bash -sudo dnf install libaom-devel -``` - -## Usage - -To use go-avif in your Go code: - -```go -import "github.com/Kagami/go-avif" -``` - -To install go-avif in your $GOPATH: - -```bash -go get github.com/Kagami/go-avif -``` - -For further details see [GoDoc documentation](https://godoc.org/github.com/Kagami/go-avif). - -## Example - -```go -package main - -import ( - "image" - _ "image/jpeg" - "log" - "os" - - "github.com/Kagami/go-avif" -) - -func main() { - if len(os.Args) != 3 { - log.Fatalf("Usage: %s src.jpg dst.avif", os.Args[0]) - } - - srcPath := os.Args[1] - src, err := os.Open(srcPath) - if err != nil { - log.Fatalf("Can't open sorce file: %v", err) - } - - dstPath := os.Args[2] - dst, err := os.Create(dstPath) - if err != nil { - log.Fatalf("Can't create destination file: %v", err) - } - - img, _, err := image.Decode(src) - if err != nil { - log.Fatalf("Can't decode source file: %v", err) - } - - err = avif.Encode(dst, img, nil) - if err != nil { - log.Fatalf("Can't encode source image: %v", err) - } - - log.Printf("Encoded AVIF at %s", dstPath) -} -``` - -## CLI - -go-avif comes with handy CLI utility `avif`. It supports encoding of JPEG and -PNG files to AVIF: - -```bash -# Compile and put avif binary to $GOPATH/bin -go get github.com/Kagami/go-avif/... - -# Encode JPEG to AVIF with default settings -avif -e cat.jpg -o kitty.avif - -# Encode PNG with slowest speed -avif -e dog.png -o doggy.avif --best -q 15 - -# Lossless encoding -avif -e pig.png -o piggy.avif --lossless - -# Show help -avif -h -``` - -Static 64-bit builds for Windows, macOS and Linux are available at -[releases page](https://github.com/Kagami/go-avif/releases). They include -latest libaom from git at the moment of build. - -## Display - -To display resulting AVIF files take a look at software listed -[here](https://github.com/AOMediaCodec/av1-avif/wiki#demuxers--players). E.g. -use [avif.js](https://kagami.github.io/avif.js/) web viewer. - -## License - -go-avif is licensed under [CC0](COPYING). diff --git a/internal/avif/av1.c b/internal/avif/av1.c deleted file mode 100644 index 24cbeb8..0000000 --- a/internal/avif/av1.c +++ /dev/null @@ -1,215 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <assert.h> -#include <aom/aom_encoder.h> -#include <aom/aomcx.h> -#include "av1.h" - -#define SET_CODEC_CONTROL(ctrl, val) \ - {if (aom_codec_control(ctx, ctrl, val)) return AVIF_ERROR_CODEC_INIT;} - -typedef struct { - aom_img_fmt_t fmt; - int dst_c_dec_h; - int dst_c_dec_v; - int bps; - int bytes_per_sample; -} avif_format; - -static avif_format convert_subsampling(const avif_subsampling subsampling) { - avif_format fmt = { 0 }; - switch (subsampling) { - case AVIF_SUBSAMPLING_I420: - fmt.fmt = AOM_IMG_FMT_I420; - fmt.dst_c_dec_h = 2; - fmt.dst_c_dec_v = 2; - fmt.bps = 12; - fmt.bytes_per_sample = 1; - break; - default: - assert(0); - } - return fmt; -} - -// We don't use aom_img_wrap() because it forces padding for odd picture -// sizes (c) libaom/common/y4minput.c -static void convert_frame(const avif_frame *frame, aom_image_t *aom_frame) { - memset(aom_frame, 0, sizeof(*aom_frame)); - avif_format fmt = convert_subsampling(frame->subsampling); - aom_frame->fmt = fmt.fmt; - aom_frame->w = aom_frame->d_w = frame->width; - aom_frame->h = aom_frame->d_h = frame->height; - aom_frame->x_chroma_shift = fmt.dst_c_dec_h >> 1; - aom_frame->y_chroma_shift = fmt.dst_c_dec_v >> 1; - aom_frame->bps = fmt.bps; - int pic_sz = frame->width * frame->height * fmt.bytes_per_sample; - int c_w = (frame->width + fmt.dst_c_dec_h - 1) / fmt.dst_c_dec_h; - c_w *= fmt.bytes_per_sample; - int c_h = (frame->height + fmt.dst_c_dec_v - 1) / fmt.dst_c_dec_v; - int c_sz = c_w * c_h; - aom_frame->stride[AOM_PLANE_Y] = frame->width * fmt.bytes_per_sample; - aom_frame->stride[AOM_PLANE_U] = aom_frame->stride[AOM_PLANE_V] = c_w; - aom_frame->planes[AOM_PLANE_Y] = frame->data; - aom_frame->planes[AOM_PLANE_U] = frame->data + pic_sz; - aom_frame->planes[AOM_PLANE_V] = frame->data + pic_sz + c_sz; -} - -static int get_frame_stats(aom_codec_ctx_t *ctx, - const aom_image_t *frame, - aom_fixed_buf_t *stats) { - if (aom_codec_encode(ctx, frame, 1/*pts*/, 1/*duration*/, 0/*flags*/)) - return AVIF_ERROR_FRAME_ENCODE; - - const aom_codec_cx_pkt_t *pkt = NULL; - aom_codec_iter_t iter = NULL; - int got_pkts = 0; - while ((pkt = aom_codec_get_cx_data(ctx, &iter)) != NULL) { - got_pkts = 1; - if (pkt->kind == AOM_CODEC_STATS_PKT) { - const uint8_t *const pkt_buf = pkt->data.twopass_stats.buf; - const size_t pkt_si |
