Bulk WebP Converter

WebP is a modern image format that offers superior compression and quality. Chances are you know that already, which is why you clicked on this article in the first place. If you need to convert multiple images to WebP format quickly and easily, my Bulk WebP Converter is the tool for you. I wrote it as a command line tool so you could use it in your own scripts, as a standalone tool, or in your DevOps pipeline. The source code is available on my GitHub, so you can tweak it if you’d like.

I also have another article where I do bulk WebP conversion with Python which you can find over at Convert JPG Images To WebP Using Python. The python tool supports the same image formats as the Go tool in this article. The Bulk WebP Converter I wrote in Go is faster than the Python version because it uses a compiled executable and supports concurrency.

Table of Contents

Bulk WebP Converter Features

  • Batch Conversion: Convert entire directories of images to WebP format with one command.
  • Customizable Settings: Adjust compression quality and image dimensions as needed.
  • High Performance: Written in Go for fast and reliable processing.
  • Multi-threaded: The code has concurrency, so you can rip through a bunch of images in a directory quickly.
  • Open Source: Free to use and modify. Available on GitHub.

Supported Source Image Formats

The Bulk WebP Converter tool I wrote supports the following image formats as inputs:

  • JPG/JPEG
  • PNG
  • TIFF
  • GIF

Bulk WebP Converter Code

If you want to build it yourself or modify it, here is the code to do that. You can also jump straight to Bulk WebP Converter and fork it yourself.

main.go

package main

import (
	"fmt"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	_ "golang.org/x/image/tiff"

	"runtime"

	"github.com/chai2010/webp"
	"github.com/schollz/progressbar/v3"
)

func main() {
	if len(os.Args) < 3 {
		fmt.Println("Usage: $ webpconvert <input_directory_path> <output_directory_path>")
		os.Exit(1)
	}

	inputDirectoryPath := os.Args[1]
	outputDirectoryPath := os.Args[2]

	fmt.Println("Scanning directory for images...")

	totalImages, err := countImages(inputDirectoryPath)
	if err != nil {
		fmt.Printf("Error scanning directory: %v\n", err)
		os.Exit(1)
	}

	if totalImages == 0 {
		fmt.Println("No images found for conversion.")
		os.Exit(0)
	}

	fmt.Printf("Found %d images for conversion. Starting...\n", totalImages)
	bar := progressbar.Default(int64(totalImages))

	runtime.GOMAXPROCS(runtime.NumCPU())

	filePaths := make(chan string, totalImages)

	var wg sync.WaitGroup

	const numWorkers = 4
	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go convertWorker(filePaths, &wg, bar, outputDirectoryPath)
	}

	start := time.Now()

	filepath.Walk(inputDirectoryPath, func(path string, info os.FileInfo, err error) error {
		if err == nil && !info.IsDir() && isSupportedFormat(path) {
			filePaths <- path
		}
		return nil
	})
	close(filePaths)

	wg.Wait()

	elapsed := time.Since(start)
	fmt.Printf("\nAll conversions successful! Time taken: %s\n", elapsed)
}

func countImages(dirPath string) (int, error) {
	var count int
	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
		if err == nil && !info.IsDir() && isSupportedFormat(path) {
			count++
		}
		return err
	})
	return count, err
}

func convertWorker(filePaths chan string, wg *sync.WaitGroup, bar *progressbar.ProgressBar, outputDir string) {
	defer wg.Done()
	for path := range filePaths {
		err := convertToWebP(path, outputDir)
		if err != nil {
			fmt.Printf("Failed to convert %s: %v\n", path, err)
		} else {
			bar.Add(1)
		}
	}
}

func isSupportedFormat(filePath string) bool {
	ext := strings.ToLower(filepath.Ext(filePath))
	return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" || ext == ".tiff" || ext == ".tif"
}

func convertToWebP(inputPath, outputDir string) error {
	file, err := os.Open(inputPath)
	if err != nil {
		return err
	}
	defer file.Close()

	img, _, err := image.Decode(file)
	if err != nil {
		return err
	}

	// Ensure the output directory exists
	if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
		return err
	}

	outputFileName := filepath.Join(outputDir, filepath.Base(strings.TrimSuffix(inputPath, filepath.Ext(inputPath)))+".webp")
	outputFile, err := os.Create(outputFileName)
	if err != nil {
		return err
	}
	defer outputFile.Close()

	return webp.Encode(outputFile, img, &webp.Options{Lossless: true})
}

go.mod

module webp-converter

go 1.21.5

require (
	github.com/chai2010/webp v1.1.1 // indirect
	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
	github.com/rivo/uniseg v0.4.4 // indirect
	github.com/schollz/progressbar/v3 v3.14.1 // indirect
	golang.org/x/image v0.14.0 // indirect
	golang.org/x/sys v0.15.0 // indirect
	golang.org/x/term v0.15.0 // indirect
)

Bulk WebP Converter Instructions

The origional code is written in Go, and compiled on 1.21.5

These instructions are split into two sections. The first section is the standalone executable – you can download that and run it directly, and not worry about having to compile anything. It is currently compiled to run on Linux and MacOS out of the box.

Standalone Executable

Download and Unzip the binary and place it in your home directory, programs directory or wherever you want to launch the program from

Ubuntu Linux

Extract the executable files wherever and run:

$ ./webpconverter source_folder/ destination_folder/

MacOSX (Intel and M1, M2, M3)

Extract the Extract the executable files wherever and run:

$ ./webpconverter source_folder/ destination_folder/

Windows 10/11

This doesn’t compile on Windows 10/11 because one of the modules doesn’t play nicely with Windows. This has been on my list to figure out, but this works on Linux and MacOS.

Compile Instructions

To compile this software on your system, make sure that you are running Go 1.21.5 or later.

$ git clone [email protected]:rickconlee/webp-converter.git

Make sure you are on the master branch, then run the following:

$ go build -o webpconvert main.go

Once the compile is complete, run:

$ ./webpconverter source_folder/ destination_folder/