本記事はMLTを画像に変換する処理を紹介します。
MLTとは?
まとめzip等で使われている拡張子です。たぶん、Multi Line Textの頭文字...。
内容は[SPLIT]でAAが分割されて記述されているテキストファイルって感じです。普通のメモ帳でも開いて中をみることができます。
ソースコード
ちょい長いですが、以下が変換処理です。ざっとわけて以下のことを行います。
- inputフォルダの中身をみてファイルなら変換、ディレクトリなら再帰処理
- ファイルの場合、1れつずつ読み込みSPLITがきたらひとつのAAとしてまとめる
- 各文字列をsjis→utf-8に変換。
- 各文字列のHTML特殊文字「
♥
」を「❤︎」に置き換え - 文字列をpngに変換
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"github.com/fogleman/gg"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
"image/png"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"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
for idx, aa := range mlt.Aa {
fileName = strconv.Itoa(idx)
lines := strings.Split(aa.Value, "\n")
outputDirectory := "output/" + mlt.Path
outputPath := outputDirectory + "/" + fileName + ".png"
if err := os.MkdirAll(outputDirectory, 0777); err != nil {
fmt.Println(err)
}
if _, err := ConvertTextToImage(lines, outputPath); err != nil {
fmt.Println(err)
}
}
return
}
func ConvertTextToImage(lines []string, path string) ([]byte, error) {
// 対象アスキーアートの縦横を図る
measure := gg.NewContext(S, S)
if err := measure.LoadFontFace("./Saitamaar.ttf", FONT_SIZE); err != nil {
return nil, err
}
maxWidth := 0.0
for _, line := range lines {
w, _ := measure.MeasureString(line)
if maxWidth <= w {
maxWidth = w
}
}
// 対象アスキーアートをpngに描画する
width := int(maxWidth) + 10
height := int(int(LINE_HEIGHT) * (len(lines) + 1))
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()
dc.SavePNG(path)
img := dc.Image()
buf := new(bytes.Buffer)
if err := png.Encode(buf, img); err != nil {
return nil, err
}
ret := buf.Bytes()
return ret, 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
}
リポジトリ
以下に実際に動くリポジトリがありますので興味があったらのぞいてみてください。
https://github.com/AAHub/MLT2Image