/imagemagick

A simple ImageMagick command wrapper for Go that doesn't depend on the ImageMagick shared library

Primary LanguageGoBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

kamermans/imagemagick

Build Status Go Report Card Godoc

High-level Go wrapper for the ImageMagick convert command and a replacement for the identify command to gather detailed information on images like width, height, exif tags, colorspace, etc, without requiring the ImageMagick shared libraries; works on Linux, Windows, MacOS and any other system that has access to the convert command.

Check the godocs for the latest documentation: https://godoc.org/github.com/kamermans/imagemagick

Usage

For usage examples, check out the godocs and the examples_*.go files.

One of the great features of kamermans/imagemagick is that it comes with built-in support for processing files in parallel:

package main

import (
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/kamermans/imagemagick"
)

func main() {
    var (
        convertCmd    = `/usr/local/bin/convert`
        imageFilesDir = `/tmp/sample_images`
    )

    parser := imagemagick.NewParser()
    parser.ConvertCommand = convertCmd

    files := make(chan string)
    results := make(chan *imagemagick.ImageResult)
    errs := make(chan *imagemagick.ParserError)

    // Used to tell us when the results have all be consumed
    done := make(chan bool)

    parser.GetImageDetailsParallel(files, results, errs)

    // Send in files
    go func() {
        defer close(files)

        filepath.Walk(imageFilesDir, func(path string, info os.FileInfo, err error) error {
            if err != nil {
                fmt.Printf("Unable to access path %q: %v\n", imageFilesDir, err)
                return err
            }

            if info.IsDir() {
                return nil
            }

            // Send this image into the files channel
            files <- path

            return nil
        })
    }()

    numErrors := 0
    numResults := 0
    startTime := time.Now()

    // Store the number of images of each format that we've seen
    resultsByFormat := map[string]int64{}
    // Store the total size of the images we've seen
    totalSize := int64(0)
    // Report progress this often
    reportInterval := 2 * time.Second

    // Report progress
    go func() {
        time.Sleep(reportInterval)
        for {
            // Get a sorted list of formats so it looks consistent
            formats := []string{}
            for format := range resultsByFormat {
                formats = append(formats, format)
            }
            sort.Strings(formats)

            outLines := []string{}
            for _, format := range formats {
                outLines = append(outLines, fmt.Sprintf("%v: %v", format, resultsByFormat[format]))
            }

            numPerSecond := float64(numResults+numErrors) / time.Since(startTime).Seconds()
            fmt.Printf("Results: %v, Errors: %v, Rate: %.0f/sec, Image Data: %v MB, Formats: {%v}\n",
                numResults,
                numErrors,
                numPerSecond,
                totalSize/1000000,
                strings.Join(outLines, ", "),
            )

            time.Sleep(reportInterval)
        }
    }()

    // Consume results and errors
    moreErrs := true
    moreResults := true
    for {
        if !moreErrs && !moreResults {
            break
        }

        select {
        case _, ok := <-errs:
            if !ok {
                moreErrs = false
                continue
            }
            numErrors++
        case details, ok := <-results:
            if !ok {
                moreResults = false
                continue
            }
            numResults++
            image := details.Image

            // Collect some stats for the progress function above
            totalSize += image.Size()
            if details.Image.Format != "" {
                resultsByFormat[details.Image.Format]++
            }

            // You can get the image details here if you want
            // fmt.Printf("Received result for image: %v (%v)\n",
            // 	image.BaseName,
            // 	image.Format,
            // )
        }
    }

    fmt.Printf("Received %v results and %v errors\n", numResults, numErrors)

    // Here's what the output looks like on my laptop with 4523 sample images:
    //
    // Results: 40, Errors: 0, Rate: 20/sec, Image Data: 3 MB, Formats: {JPEG: 40}
    // Results: 160, Errors: 0, Rate: 40/sec, Image Data: 9 MB, Formats: {JPEG: 159, PNG: 1}
    // Results: 280, Errors: 0, Rate: 46/sec, Image Data: 18 MB, Formats: {JPEG: 279, PNG: 1}
    // ... lots of output ...
    // Results: 4304, Errors: 16, Rate: 46/sec, Image Data: 303 MB, Formats: {JPEG: 4281, PNG: 23}
    // Results: 4386, Errors: 17, Rate: 46/sec, Image Data: 305 MB, Formats: {JPEG: 4362, PNG: 24}
    // Results: 4465, Errors: 19, Rate: 46/sec, Image Data: 309 MB, Formats: {JPEG: 4440, PNG: 25}
    // Received 4503 results and 20 errors
}