自分一人で何番煎じかわからないネタですが、ASTをGifに変換してみたいと思います。類似ネタのMLTをGifに変換する記事は以下のリンクを参照してください。
GolangでMLTをGifに変換する
ASTとは?
おそらくAsciiArt Story Textの略です、たぶん。
やる夫スレなどのストーリ専用のファイル形式です。拡張子はast。
MLTが[SPLIT]でAAを区切っていたのに対し、ASTは[AA]で区切ります。いちばんの違いは[AA]の後ろに[]で好きな名前をつけられることです。例えば、[AA][やる夫]のように。
ASTをGifに変換する
さて、基本的な処理は別記事のMLTをGifに変換するのと同じ内容です。が、しかし、せっかくASTなので、Delayに対応してみたいと思います。つまり、[AA][(Delayさせたい数値)]とすることでDelayに対応しました。ソースコードは以下。
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"
"strconv"
"strings"
)
const (
S = 1024
FONT_SIZE = 16.0
LINE_HEIGHT = 18.0
)
type Ast struct {
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
Updated string `json:"updated,omitempty"`
Aa []*Aa `json:"aa,omitempty"`
}
type Aa struct {
Name string `json:"name,omitempty"`
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, ".ast") == -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 ast Ast
namepos := strings.LastIndex(name, ".")
new_name := name[:namepos]
ast.Name = new_name
aa := &Aa{}
for idx, l := range lines {
l, _ = sjis_to_utf8(l)
l, _ = escape_html_special_caharacter(l)
if strings.Index(l, "[AA][") == 0 {
str := l[5:]
aa.Name = str[:len(str)-1]
if aa.Value != "" {
ast.Aa = append(ast.Aa, aa)
}
aa = &Aa{}
} else {
aa.Value = aa.Value + l + "\n"
}
if idx == len(lines)-1 {
if aa.Value != "" {
ast.Aa = append(ast.Aa, aa)
}
}
}
pos := strings.LastIndex(filePath, ".")
fileName := filePath[:pos]
pos = strings.Index(fileName, "/")
path := fileName[(pos + 1):]
ast.Path = path
b := ast.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 *Ast) GenerateGifImage() []byte {
outGif := &gif.GIF{}
delay := 0
sWidth, sHeight := calcrateMaxWidthAndHeight(this.Aa, FONT_SIZE, LINE_HEIGHT)
for _, a := range this.Aa {
lines := strings.Split(a.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)
delay, _ = strconv.Atoi(a.Name)
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
}
上記、ソースコードを実行してみた結果です。3枚に1枚Delayが入っているのがわかると思います。
ソースコード
例によってGitHubにソースコードをあげています。
https://github.com/AAHub/AST2Gif
まとめ
以上、Delayに対応したGifを作ることができました。もし、何か応用できそうなことがあったら幸いです。参考になったなら一声かけてもらったりいいね押してもらったりすると喜びます。
それでは。