🚴

GoでWEBPとAVIFのコンバーター作ったので紹介だけします

に公開

wazeroのLibrariesでwebpとavifのエンコーダー・デコーダーを見つけたので作りました。

https://wazero.io/community/users/

置き場所に困ったので紹介だけします。

package main

import (
	"flag"
	"fmt"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"os"

	"github.com/gen2brain/webp"
	_ "golang.org/x/image/webp"
)

func ConvertToWEBP(inputFile, outputFile string, options webp.Options) error {
	inFile, err := os.Open(inputFile)
	if err != nil {
		return fmt.Errorf("Error opening input file: %v", err)
	}
	defer inFile.Close()

	img, _, err := image.Decode(inFile)
	if err != nil {
		return fmt.Errorf("Error decoding image: %v", err)
	}

	outFile, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("Error creating output file: %v", err)
	}
	defer outFile.Close()

	if err := webp.Encode(outFile, img, options); err != nil {
		return fmt.Errorf("Error encoding to WEBP: %v", err)
	}
	return nil
}

func main() {
	inputFile := flag.String("i", "input.png", "Path to the input image file (e.g., PNG, JPEG, GIF, WEBP).")
	outputFile := flag.String("o", "output.webp", "Path for the output image file.")
	quality := flag.Int("q", 75, "Quality for lossy compression (1-100). Ignored if -l is true.")
	lossless := flag.Bool("l", false, "Use lossless compression. Overrides -q.")
	method := flag.Int("m", 4, "Encoding method (0=fast, 6=slower/better quality).")
	exact := flag.Bool("e", false, "Preserve exact RGB values in transparent areas (lossless only).")
	flag.Parse()

	if *quality < 0 || *quality > 100 {
		fmt.Println("Error: Quality must be between 0 and 100.")
		return
	}
	if *method < 0 || *method > 6 {
		fmt.Println("Error: Method must be between 0 and 6.")
		return
	}

	options := webp.Options{
		Quality:  *quality,
		Method:   *method,
		Lossless: *lossless,
		Exact:    *exact,
	}
	if err := ConvertToWEBP(*inputFile, *outputFile, options); err != nil {
		fmt.Println("Conversion failed:", err)
		return
	}
	fmt.Println("Conversion successful! Saved as", *outputFile)
}
package main

import (
	"flag"
	"fmt"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"os"

	"github.com/gen2brain/avif"
	_ "golang.org/x/image/webp"
)

func ConvertToAVIF(inputFile, outputFile string, options avif.Options) error {
	inFile, err := os.Open(inputFile)
	if err != nil {
		return fmt.Errorf("Error opening input file: %v", err)
	}
	defer inFile.Close()

	img, _, err := image.Decode(inFile)
	if err != nil {
		return fmt.Errorf("Error decoding image: %v", err)
	}

	outFile, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("Error creating output file: %v", err)
	}
	defer outFile.Close()

	if err := avif.Encode(outFile, img, options); err != nil {
		return fmt.Errorf("Error encoding to AVIF: %v", err)
	}
	return nil
}

func main() {
	inputFile := flag.String("i", "input.png", "Path to the input image file (e.g., PNG, JPEG, GIF, WEBP).")
	outputFile := flag.String("o", "output.avif", "Path for the output image file.")
	quality := flag.Int("q", 60, "Image quality for RGB channels (0-100; 100 implies lossless).")
	speed := flag.Int("s", 10, "Encoding speed (0=fastest, 10=slowest/best compression).")
	flag.Parse()

	if *quality < 0 || *quality > 100 {
		fmt.Println("Error: Quality must be between 0 and 100.")
		return
	}
	if *speed < 0 || *speed > 10 {
		fmt.Println("Error: Speed must be between 0 and 10.")
		return
	}

	options := avif.Options{
		Quality: *quality,
		Speed:   *speed,
	}
	if err := ConvertToAVIF(*inputFile, *outputFile, options); err != nil {
		fmt.Println("Conversion failed:", err)
		return
	}
	fmt.Println("Conversion successful! Saved as", *outputFile)
}
package main

import (
	"flag"
	"fmt"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"os"

	"github.com/gen2brain/jpegli"
	_ "golang.org/x/image/webp"
)

func ConvertToJPEG(inputFile, outputFile string, options *jpegli.EncodingOptions) error {
	inFile, err := os.Open(inputFile)
	if err != nil {
		return fmt.Errorf("Error opening input file: %v", err)
	}
	defer inFile.Close()

	img, _, err := image.Decode(inFile)
	if err != nil {
		return fmt.Errorf("Error decoding image: %v", err)
	}

	outFile, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("Error creating output file: %v", err)
	}
	defer outFile.Close()

	if err := jpegli.Encode(outFile, img, options); err != nil {
		return fmt.Errorf("Error encoding to JPEG: %v", err)
	}
	return nil
}

func main() {
	inputFile := flag.String("i", "input.png", "Path to the input image file (e.g., PNG, JPEG, GIF, WEBP).")
	outputFile := flag.String("o", "output.jpg", "Path for the output image file.")
	quality := flag.Int("q", 75, "Image quality (1-100).")
	progressiveLevel := flag.Int("p", 0, "Progressive encoding level (0-2; 0=sequential, higher is more steps).")
	optimizeCoding := flag.Bool("z", true, "Enable Huffman code optimization.")
	adaptiveQuantization := flag.Bool("d", true, "Use adaptive quantization.")
	standardQuantTables := flag.Bool("t", false, "Use standard JPEG quantization tables (Annex K).")
	fancyDownsampling := flag.Bool("f", false, "Apply fancy downsampling.")
	flag.Parse()

	if *quality < 0 || *quality > 100 {
		fmt.Println("Error: Quality must be between 0 and 100.")
		return
	}
	if *progressiveLevel < 0 || *progressiveLevel > 2 {
		fmt.Println("Error: Progressive level must be between 0 and 2.")
		return
	}

	options := jpegli.EncodingOptions{
		Quality:              *quality,
		ProgressiveLevel:     *progressiveLevel,
		OptimizeCoding:       *optimizeCoding,
		AdaptiveQuantization: *adaptiveQuantization,
		StandardQuantTables:  *standardQuantTables,
		FancyDownsampling:    *fancyDownsampling,
	}
	if err := ConvertToJPEG(*inputFile, *outputFile, &options); err != nil {
		fmt.Println("Conversion failed:", err)
		return
	}
	fmt.Println("Conversion successful! Saved as", *outputFile)
}
go.mod
module main.go

go 1.23.6

require (
	github.com/gen2brain/avif v0.4.3
	github.com/gen2brain/jpegli v0.3.4
	github.com/gen2brain/webp v0.5.4
	golang.org/x/image v0.27.0
)

require (
	github.com/ebitengine/purego v0.8.2 // indirect
	github.com/tetratelabs/wazero v1.9.0 // indirect
)
go.sum
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gen2brain/avif v0.4.3 h1:pxpXOCIA4YfB6wxDagbNCpXdTh5/1q3huedPx0arqwA=
github.com/gen2brain/avif v0.4.3/go.mod h1:L0hvma2Pwz8HWgE3w7KkRIUYxnVEZ94ZfVQGKwaIQ40=
github.com/gen2brain/jpegli v0.3.4 h1:wFoUHIjfPJGGeuW3r9dqy0MTT1TtvJuWf6EqfHPPGFM=
github.com/gen2brain/jpegli v0.3.4/go.mod h1:tVnF7NPyufTo8noFlW5lurUUwZW8trwBENOItzuk2BM=
github.com/gen2brain/webp v0.5.4 h1:edPfjyt9vU9A+VMQCg7uKz/A2Jjle+yP20ZPqZH3l6s=
github.com/gen2brain/webp v0.5.4/go.mod h1:kEFkGdK1wG9ePKj9ylcLUrMQfqevXGTpVq1S8lZL0j4=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=

あとはコンパイルするだけ。

$ CGO_ENABLED=0 go build -ldflags '-s -w -extldflags "-static"' -trimpath

クロスコンパイルもできる。

$ GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-s -w -extldflags "-static"' -trimpath

関数を分けたりライブラリの使い方を調べるのは自分でやりましたが、それ以外はChatGPTを使いました。
CGOフリーなのでサクッっと作れて使えるので便利です。

2025/05/16更新:リファクタリングした最新のコードを反映して、jpegliも追加しました。解説内容は変わっていません。

Discussion