別記事にてMLTを画像に変換する処理を行いましたが、これを少し改造すればGifの出力も行えそうです。
MLTの説明や画像化の方法については下のリンクを参照。
GolangでMLTを画像に変換する
ソースコード
Gifへの変換は以下のことを行います。
- inputフォルダの中身をみてファイルなら変換、ディレクトリなら再帰処理
- ファイルの場合、1れつずつ読み込みSPLITがきたらひとつのAAとしてまとめる
- 各文字列をsjis→utf-8に変換。
- 各文字列のHTML特殊文字「
♥
」を「❤︎」に置き換え - 文字列をimageオブジェクトに変換
- MLT内のAAで最大の高さ、幅のAAを探す
- imageオブジェクトを全て結合しGifとして出力
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"github.com/fogleman/gg"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
"image"
"image/color/palette"
"image/draw"
"image/gif"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
const (
S = 1024
FONT_SIZE = 16.0
LINE_HEIGHT = 18.0
)
type Mlt struct {
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
Updated string `json:"updated,omitempty"`
Aa []*Aa `json:"aa,omitempty"`
}
type Aa struct {
Value string `json:"value,omitempty"`
}
type Character struct {
Value string `json:"value"`
Code string `json:"code"`
Unicode string `json:"unicode"`
}
func main() {
dirwalk("./input")
}
func dirwalk(dir string) {
files, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}
for _, file := range files {
if file.IsDir() {
dirwalk(filepath.Join(dir, file.Name()))
continue
}
path := filepath.Join(dir, file.Name())
fromFile(file.Name(), path)
}
}
func fromFile(name string, filePath string) {
if name == ".DS_Store" {
return
}
if strings.LastIndex(name, ".mlt") == -1 {
return
}
// ファイルを開く
f, err := os.Open(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "File %s could not read: %v\n", filePath, err)
os.Exit(1)
}
// 関数return時に閉じる
defer f.Close()
// Scannerで読み込む
lines := make([]string, 0, 300) // ある程度行数が事前に見積もれるようであれば、makeで初期capacityを指定して予めメモリを確保しておくことが望ましい
scanner := bufio.NewScanner(f)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if serr := scanner.Err(); serr != nil {
fmt.Fprintf(os.Stderr, "File %s scan error: %v\n", filePath, err)
}
var mlt Mlt
namepos := strings.LastIndex(name, ".")
new_name := name[:namepos]
mlt.Name = new_name
aa := &Aa{}
for idx, l := range lines {
l, _ = sjis_to_utf8(l)
l, _ = escape_html_special_caharacter(l)
if l == "[SPLIT]" {
if aa.Value != "" {
mlt.Aa = append(mlt.Aa, aa)
}
aa = &Aa{}
} else {
aa.Value = aa.Value + l + "\n"
}
if idx == len(lines)-1 {
if aa.Value != "" {
mlt.Aa = append(mlt.Aa, aa)
}
}
}
pos := strings.LastIndex(filePath, ".")
fileName := filePath[:pos]
pos = strings.Index(fileName, "/")
path := fileName[(pos + 1):]
mlt.Path = path
b := mlt.GenerateGifImage()
var buf bytes.Buffer
buf.Write(b)
firpos := strings.LastIndex(path, "/")
gifPath := ""
gifName := path
if firpos > 0 {
gifPath = path[:firpos]
gifName = path[firpos:]
}
if err := os.MkdirAll("output/"+gifPath, 0777); err != nil {
fmt.Println(err)
}
err = ioutil.WriteFile("output/"+gifPath+"/"+gifName+".gif", buf.Bytes(), 0644)
if err != nil {
fmt.Println("err:", err)
}
return
}
func (this *Mlt) GenerateGifImage() []byte {
outGif := &gif.GIF{}
delay := 0
sWidth, sHeight := calcrateMaxWidthAndHeight(this.Aa, FONT_SIZE, LINE_HEIGHT)
for _, m := range this.Aa {
lines := strings.Split(m.Value, "\n")
img, _ := ConvertTextToImage(lines, sWidth, sHeight)
pimg := image.NewPaletted(img.Bounds(), palette.WebSafe)
draw.FloydSteinberg.Draw(pimg, img.Bounds(), img, image.ZP)
outGif.Image = append(outGif.Image, pimg)
outGif.Delay = append(outGif.Delay, delay)
}
buf := new(bytes.Buffer)
err := gif.EncodeAll(buf, outGif)
if err != nil {
fmt.Println(err)
}
ret := buf.Bytes()
return ret
}
func calcrateMaxWidthAndHeight(aa []*Aa, fontSize float64, lineHeight float64) (int, int) {
const S = 1024
sWidth := 0.0
sHeight := 0.0
for _, a := range aa {
lines := strings.Split(a.Value, "\n")
measure := gg.NewContext(S, S)
if err := measure.LoadFontFace("./Saitamaar.ttf", fontSize); err != nil {
return 0, 0
}
maxWidth := 0.0
maxHeight := float64(int(lineHeight) * (len(lines) + 1))
for _, line := range lines {
w, _ := measure.MeasureString(line)
if maxWidth <= w {
maxWidth = w
}
}
if maxWidth >= sWidth {
sWidth = maxWidth
}
if maxHeight >= sHeight {
sHeight = maxHeight
}
}
return int(sWidth), int(sHeight)
}
func ConvertTextToImage(lines []string, sWidth int, sHeight int) (image.Image, error) {
width := sWidth
height := sHeight
dc := gg.NewContext(width, height)
if err := dc.LoadFontFace("./Saitamaar.ttf", FONT_SIZE); err != nil {
return nil, err
}
dc.SetRGB(1, 1, 1)
dc.Clear()
dc.SetHexColor("#333333")
for idx, line := range lines {
i := float64(idx + 1)
dc.DrawString(line, 10, LINE_HEIGHT*i)
}
dc.Clip()
img := dc.Image()
return img, nil
}
func sjis_to_utf8(str string) (string, error) {
ret, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ShiftJIS.NewDecoder()))
if err != nil {
return "", err
}
return string(ret), err
}
func escape_html_special_caharacter(str string) (string, error) {
jsonBlob := []byte(`[{"value":"ƒ","code":"ƒ","unicode":"ƒ"},{"value":"ε","code":"ε","unicode":"Ε"},{"value":"κ","code":"κ","unicode":"Κ"},{"value":"ο","code":"ο","unicode":"Ο"},{"value":"υ","code":"υ","unicode":"Υ"},{"value":"α","code":"α","unicode":"α"},{"value":"ζ","code":"ζ","unicode":"ζ"},{"value":"λ","code":"λ","unicode":"λ"},{"value":"π","code":"π","unicode":"π"},{"value":"υ","code":"υ","unicode":"υ"},{"value":"?","code":"ϑ","unicode":"ϑ"},{"value":"′","code":"′","unicode":"′"},{"value":"ℑ","code":"ℑ","unicode":"ℑ"},{"value":"↑","code":"↑","unicode":"↑"},{"value":"⇐","code":"←","unicode":"⇐"},{"value":"∀","code":"∀","unicode":"∀"},{"value":"∈","code":"∈","unicode":"∈"},{"value":"−","code":"−","unicode":"−"},{"value":"∠","code":"∠","unicode":"∠"},{"value":"∫","code":"∫","unicode":"∫"},{"value":"≠","code":"≠","unicode":"≠"},{"value":"⊃","code":"⊃","unicode":"⊃"},{"value":"⊗","code":"⊗","unicode":"⊗"},{"value":"?","code":"⌊","unicode":"⌊"},{"value":"♠","code":"♠","unicode":"♠"},{"value":"α","code":"α","unicode":"Α"},{"value":"ζ","code":"ζ","unicode":"Ζ"},{"value":"λ","code":"λ","unicode":"Λ"},{"value":"π","code":"π","unicode":"Π"},{"value":"φ","code":"φ","unicode":"Φ"},{"value":"β","code":"β","unicode":"β"},{"value":"η","code":"η","unicode":"η"},{"value":"μ","code":"μ","unicode":"μ"},{"value":"ρ","code":"ρ","unicode":"ρ"},{"value":"φ","code":"φ","unicode":"φ"},{"value":"?","code":"ϒ","unicode":"ϒ"},{"value":"″","code":"′","unicode":"″"},{"value":"ℜ","code":"ℜ","unicode":"ℜ"},{"value":"→","code":"→","unicode":"→"},{"value":"⇑","code":"↑","unicode":"⇑"},{"value":"∂","code":"∂","unicode":"∂"},{"value":"∉","code":"∉","unicode":"∉"},{"value":"∗","code":"∗","unicode":"∗"},{"value":"∧","code":"∧","unicode":"∧"},{"value":"∴","code":"∴","unicode":"∴"},{"value":"≡","code":"≡","unicode":"≡"},{"value":"⊄","code":"⊄","unicode":"⊄"},{"value":"⊥","code":"⊥","unicode":"⊥"},{"value":"?","code":"⌋","unicode":"⌋"},{"value":"♣","code":"♣","unicode":"♣"},{"value":"β","code":"β","unicode":"Β"},{"value":"η","code":"η","unicode":"Η"},{"value":"μ","code":"μ","unicode":"Μ"},{"value":"ρ","code":"ρ","unicode":"Ρ"},{"value":"χ","code":"χ","unicode":"Χ"},{"value":"γ","code":"γ","unicode":"γ"},{"value":"θ","code":"θ","unicode":"θ"},{"value":"ν","code":"ν","unicode":"ν"},{"value":"ς","code":"ς","unicode":"ς"},{"value":"χ","code":"χ","unicode":"χ"},{"value":"?","code":"ϖ","unicode":"ϖ"},{"value":"‾","code":"‾","unicode":"‾"},{"value":"™","code":"™","unicode":"™"},{"value":"↓","code":"↓","unicode":"↓"},{"value":"⇒","code":"→","unicode":"⇒"},{"value":"∃","code":"∃","unicode":"∃"},{"value":"∋","code":"∋","unicode":"∋"},{"value":"√","code":"√","unicode":"√"},{"value":"∨","code":"∨","unicode":"∨"},{"value":"∼","code":"∼","unicode":"∼"},{"value":"≤","code":"≤","unicode":"≤"},{"value":"⊆","code":"⊆","unicode":"⊆"},{"value":"⋅","code":"⋅","unicode":"⋅"},{"value":"?","code":"⟨","unicode":"〈"},{"value":"♥","code":"♥","unicode":"♥"},{"value":"γ","code":"γ","unicode":"Γ"},{"value":"θ","code":"θ","unicode":"Θ"},{"value":"ν","code":"ν","unicode":"Ν"},{"value":"σ","code":"σ","unicode":"Σ"},{"value":"ψ","code":"ψ","unicode":"Ψ"},{"value":"δ","code":"δ","unicode":"δ"},{"value":"ι","code":"ι","unicode":"ι"},{"value":"ξ","code":"ξ","unicode":"ξ"},{"value":"σ","code":"σ","unicode":"σ"},{"value":"ψ","code":"ψ","unicode":"ψ"},{"value":"•","code":"•","unicode":"•"},{"value":"⁄","code":"⁄","unicode":"⁄"},{"value":"ℵ","code":"ℵ","unicode":"ℵ"},{"value":"↔","code":"↔","unicode":"↔"},{"value":"⇓","code":"↓","unicode":"⇓"},{"value":"∅","code":"∅","unicode":"∅"},{"value":"∏","code":"∏","unicode":"∏"},{"value":"∝","code":"∝","unicode":"∝"},{"value":"∩","code":"∩","unicode":"∩"},{"value":"∝","code":"≅","unicode":"≅"},{"value":"≥","code":"≥","unicode":"≥"},{"value":"⊇","code":"⊇","unicode":"⊇"},{"value":"?","code":"⌈","unicode":"⌈"},{"value":"?","code":"⟩","unicode":"〉"},{"value":"♦","code":"♦","unicode":"♦"},{"value":"δ","code":"δ","unicode":"Δ"},{"value":"ι","code":"ι","unicode":"Ι"},{"value":"ξ","code":"ξ","unicode":"Ξ"},{"value":"τ","code":"τ","unicode":"Τ"},{"value":"ω","code":"ω","unicode":"Ω"},{"value":"ε","code":"ε","unicode":"ε"},{"value":"κ","code":"κ","unicode":"κ"},{"value":"ο","code":"ο","unicode":"ο"},{"value":"τ","code":"τ","unicode":"τ"},{"value":"ω","code":"ω","unicode":"ω"},{"value":"…","code":"…","unicode":"…"},{"value":"℘","code":"℘","unicode":"℘"},{"value":"←","code":"←","unicode":"←"},{"value":"↵","code":"↵","unicode":"↵"},{"value":"⇔","code":"↔","unicode":"⇔"},{"value":"∇","code":"∇","unicode":"∇"},{"value":"∑","code":"∑","unicode":"∑"},{"value":"∞","code":"∞","unicode":"∞"},{"value":"∪","code":"∪","unicode":"∪"},{"value":"≈","code":"≈","unicode":"≈"},{"value":"⊂","code":"⊂","unicode":"⊂"},{"value":"⊕","code":"⊕","unicode":"⊕"},{"value":"?","code":"⌉","unicode":"⌉"},{"value":"◊","code":"◊","unicode":"◊"}]`)
var characters []Character
if err := json.Unmarshal(jsonBlob, &characters); err != nil {
fmt.Println(err)
return str, err
}
for _, character := range characters {
str = strings.Replace(str, character.Code, character.Value, -1)
}
return str, nil
}
上記ソースコードを動作させるとMLTの全AAを合わせてGifアニメにすることができます。
リポジトリ
GitHubにソースコードをアップしているのでよかったら見てみてね。
https://github.com/AAHub/MLT2Gif/tree/master
まとめ
前回の記事とだいぶ似た内容になってしまいましたが以上です。