[Go] 画像の拡張子の変更をするCLIコマンドを作る

2020/10/23に公開

はじめに

このまえ、Gopher道場 の自習室とやらに参加させてもらい、前々から気になっていたGopher道場の課題をgithubにて探して、(やっていいのかな…?)演習しました。

その課題1ってのが画像の拡張子変更 でした。とりあえずアウトプットをしたいので、ここに色々書いていきます。また、使用したパッケージから、flag,os,path/filepath,imageについて、ちょっとだけ解説していきます。

環境

# go version
go version go1.15.2 linux/amd64

# tree
.
|- conversion
|       |- conversion.go
|- go.mod
|- main.go

実際のコード

とりあえず、リポジトリはここです

main.go
package main

import (
	"cvs/conversion"
	"flag"
	"fmt"
	"os"
	"path/filepath"
)

var (
	extension string
	imagepath string
	dirpath   string
)

func main() {

	flag.StringVar(&extension, "e", "jpeg", "拡張子の指定")
	flag.StringVar(&imagepath, "f", "", "変換するファイルのパスの指定")
	flag.StringVar(&dirpath, "d", "", "変換後のファイル名の指定")
	flag.Parse()
	err := conversion.ExtensionCheck(extension)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	err = conversion.FilepathCheck(imagepath)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	err = conversion.DirpathCheck(dirpath)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	f := filepath.Ext(imagepath)
	err = conversion.FileExtCheck(f)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	fmt.Println("変換中・・・")

	err = conversion.FileExtension(extension, imagepath, dirpath)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}
}
conversion.go
/*
Conversion は 画像の拡張子の変更を行うためのパッケージです。
*/
package conversion

import (
	"errors"
	"fmt"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"os"

	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
)

const (
	JPEG = "jpeg"
	JPG  = "jpg"
	GIF  = "gif"
	PNG  = "png"
)

// -e で指定した拡張子が対応しているか、判断します。
func ExtensionCheck(ext string) error {
	switch ext {
	case JPEG, JPG, GIF, PNG:
		return nil
	default:
		return errors.New("指定できない拡張子です" + ext)
	}
}

// -f で指定したファイルが存在するか、判断します。
func FilepathCheck(imagepath string) error {
	switch imagepath {
	case "":
		return errors.New("ファイルの指定がされてません")
	default:
		if f, err := os.Stat(imagepath); os.IsNotExist(err) || f.IsDir() {
			return errors.New("ファイルが存在しません" + imagepath)
		} else {
			return nil
		}
	}
}

func DirpathCheck(dirpath string) error {
	switch dirpath {
	case "":
		return errors.New("変換後のファイル名が指定されていません")
	default:
		return nil
	}
}

func FileExtCheck(imagepath string) error {
	switch imagepath {
	case "." + JPEG, "." + JPG, "." + GIF, "." + PNG:
		return nil
	default:
		return errors.New("指定したファイルが対応していません。:" + imagepath)
	}
}

func FileExtension(extension string, imagepath string, dirpath string) error {
	exFile, err := os.Open(imagepath)
	defer exFile.Close()
	if err != nil {
		return errors.New("os.Create失敗")
	}

	output, err := os.Create(dirpath)
	defer output.Close()
	if err != nil {
		return errors.New("output失敗")
	}

	img, _, Err := image.Decode(exFile)
	if Err != nil {
		return errors.New("Decode失敗")
	}

	switch extension {
	case JPEG, JPG:
		err = jpeg.Encode(output, img, nil)
		if err != nil {
			return errors.New("Encode失敗")
		}
		fmt.Println("変換成功")
		return nil
	case GIF:
		err = gif.Encode(output, img, nil)
		if err != nil {
			return errors.New("Encode失敗")
		}
		fmt.Println("変換成功")
		return nil
	case PNG:
		err = png.Encode(output, img)
		if err != nil {
			return errors.New("Encode失敗")
		}
		fmt.Println("変換成功")
		return nil
	}
	return nil
}
go.mod
module cvs

go 1.15

とりあえず、エラーハンドリングとかは適当にしつつ、ちゃんと動作するようにしました。

コード解説

今回のコードのテーマとして、

  • go mod を使う
  • go doc を使う
  • パッケージを分ける

というのを決めました。一応、理由としては、

  1. go mod により、パッケージの分割を使いやすくする
  2. go doc により、ドキュメントを作成することにより、可用性をあげる
    この2つがあります。が、今回はパス。

今回使用したパッケージについて、色々書いていきます。

画像の拡張子を変更するCLIコマンドのために使用したパッケージを上げると、

  1. flag
  2. os
  3. path/filepath
  4. image

があります。
それぞれ解説していきます。

flag

ドキュメント
コマンドラインからオプションとして、引数を取得するときに使う

実装したコード(main.go)
var (
	extension string
	imagepath string
	dirpath   string
)
func main() {

	flag.StringVar(&extension, "e", "jpeg", "拡張子の指定")
 // -e "文字列" で、文字列を取得する/デフォルトで"jpeg"が指定されている
	flag.StringVar(&imagepath, "f", "", "変換するファイルのパスの指定")
	flag.StringVar(&dirpath, "d", "", "変換後のファイル名の指定")
	flag.Parse() 
// Parseしないと、変数にパースされない

~~~省略
書き方
flag.StringVar(&変数名, "オプションの記号", "デフォルトの値","このオプションの説明")

文字列の取得以外も取得はできる(はず)

os

ドキュメント
今回は、指定した画像が本当に存在するのか、画像を保存したりに使用しました。

実装したコード(conversion.go)
func FilepathCheck(imagepath string) error {
	switch imagepath {
	case "":
		return errors.New("ファイルの指定がされてません")
	default:
		if f, err := os.Stat(imagepath); os.IsNotExist(err) || f.IsDir() {
			return errors.New("ファイルが存在しません" + imagepath)
		} else {
			return nil
		}
	}
}

//-fで指定したファイルが存在するかを判定している関数です

こいつは結構なんでもできる。

path/filepath

ドキュメント
先程紹介した関数の引数の取得してます。stringからファイルパスを取得してます。

実装したコード(main.go)
f := filepath.Ext(imagepath)
err = conversion.FileExtCheck(f)
//imagepathが本当にあるのか、をチェックする

image

ドキュメント
画像処理系はこいつでできる。

実装したコード(conversion.go)
img, _, Err := image.Decode(exFile)
if Err != nil {
	return errors.New("Decode失敗")
}
//画像をデコードする処理

switch extension {
case JPEG, JPG:
	err = jpeg.Encode(output, img, nil)
	if err != nil {
		return errors.New("Encode失敗")
	}
	fmt.Println("変換成功")
	return nil
case GIF:
	err = gif.Encode(output, img, nil)
	if err != nil {
		return errors.New("Encode失敗")
	}
	fmt.Println("変換成功")
	return nil
case PNG:
	err = png.Encode(output, img)
	if err != nil {
		return errors.New("Encode失敗")
	}
	fmt.Println("変換成功")
	return nil
}
//変換後の拡張子に合わせてエンコードする

画像処理はほぼこいつだけでできるっぽい

まとめ

実際に実行してみると、、、

# go build -o cvs
# ./cvs -e png -f sample.jpeg -d sample.png
変換中・・・
変換成功

って感じで、変換できる。

ちなみに、-d のあとに.pngではなく、適当に書いても、拡張子としては、ちゃんと変換されています。

ブラウザで変換すると、へんなスクリプトを入れられるみたいなのもないし、安全。

やりごたえがあって、楽しかったから、またコマンド作ろうかしら。

では。

Discussion