Zenn
🚴

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

2025/03/22に公開

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

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

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

package main

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

	"github.com/gen2brain/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()

	var img image.Image
	switch {
	case strings.HasSuffix(strings.ToLower(inputFile), ".png"):
		img, err = png.Decode(inFile)
	case strings.HasSuffix(strings.ToLower(inputFile), ".jpg"):
		img, err = jpeg.Decode(inFile)
	case strings.HasSuffix(strings.ToLower(inputFile), ".jpeg"):
		img, err = jpeg.Decode(inFile)
	case strings.HasSuffix(strings.ToLower(inputFile), ".gif"):
		img, err = gif.Decode(inFile)
	default:
		return fmt.Errorf("Unsupported file format: %s", inputFile)
	}
	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", "Input image file path (PNG, JPEG, or GIF)")
	outputFile := flag.String("o", "output.webp", "Output WEBP file path")
	quality := flag.Int("q", 75, "WEBP image quality (0-100, 100 implies lossless)")
	method := flag.Int("m", 4, "Encoding method (0=fast, 6=slower-better)")
	lossless := flag.Bool("l", false, "Enable lossless compression (ignores quality if true)")
	exact := flag.Bool("e", false, "Preserve exact RGB values in transparent areas")
	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"
	"strings"

	"github.com/gen2brain/avif"
)

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()

	var img image.Image
	switch {
	case strings.HasSuffix(strings.ToLower(inputFile), ".png"):
		img, err = png.Decode(inFile)
	case strings.HasSuffix(strings.ToLower(inputFile), ".jpg"):
		img, err = jpeg.Decode(inFile)
	case strings.HasSuffix(strings.ToLower(inputFile), ".jpeg"):
		img, err = jpeg.Decode(inFile)
	case strings.HasSuffix(strings.ToLower(inputFile), ".gif"):
		img, err = gif.Decode(inFile)
	default:
		return fmt.Errorf("Unsupported file format: %s", inputFile)
	}
	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", "Input image file path (PNG, JPEG, or GIF)")
	outputFile := flag.String("o", "output.avif", "Output AVIF file path")
	quality := flag.Int("q", 60, "AVIF image quality (0-100, 100 implies lossless)")
	speed := flag.Int("s", 10, "Encoding speed (0=fast, 10=slow, better quality)")
	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)
}
go.mod
module main

go 1.23.6

require (
	github.com/gen2brain/avif v0.4.3
	github.com/gen2brain/webp v0.5.4
)

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/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=

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

$ 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フリーなのでサクッっと作れて使えるので便利です。

Discussion

ログインするとコメントできます