summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/go.mod18
-rw-r--r--cmd/main.go155
-rw-r--r--cmd/title.go (renamed from title.go)4
-rw-r--r--cmd/title_windows.go (renamed from title_windows.go)1
-rw-r--r--lib/ass.go (renamed from ass.go)65
-rw-r--r--lib/go.mod (renamed from go.mod)4
-rw-r--r--lib/mkv.go295
-rw-r--r--lib/shared.go36
-rw-r--r--lib/utils.go (renamed from utils.go)7
-rw-r--r--main.go95
-rw-r--r--mkv.go294
11 files changed, 536 insertions, 438 deletions
diff --git a/cmd/go.mod b/cmd/go.mod
new file mode 100644
index 0000000..00db11c
--- /dev/null
+++ b/cmd/go.mod
@@ -0,0 +1,18 @@
+module mkvtool
+
+go 1.17
+
+require mkvlib v0.0.0
+
+require (
+ github.com/antchfx/xmlquery v1.3.8 // indirect
+ github.com/antchfx/xpath v1.2.0 // indirect
+ github.com/asticode/go-astikit v0.20.0 // indirect
+ github.com/asticode/go-astisub v0.19.0 // indirect
+ github.com/asticode/go-astits v1.8.0 // indirect
+ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
+ golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
+ golang.org/x/text v0.3.2 // indirect
+)
+
+replace mkvlib => ../lib
diff --git a/cmd/main.go b/cmd/main.go
new file mode 100644
index 0000000..509de29
--- /dev/null
+++ b/cmd/main.go
@@ -0,0 +1,155 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "mkvlib"
+ "os"
+ "path"
+ "runtime"
+ "strings"
+)
+
+const appName = "MKV Tool"
+const appVer = "3.1.1"
+const tTitle = appName + " " + appVer
+
+var processer = mkvlib.GetInstance()
+
+var appFN = fmt.Sprintf("%s %s %s/%s", appName, appVer, runtime.GOOS, runtime.GOARCH)
+
+type arrayArg []string
+
+func (self *arrayArg) String() string {
+ return fmt.Sprintf("%v", []string(*self))
+}
+
+func (self *arrayArg) Set(value string) error {
+ *self = append(*self, value)
+ return nil
+}
+
+func main() {
+ setWindowTitle(tTitle)
+ s := ""
+ data := ""
+ dist := ""
+ f := ""
+ c := false
+ d := false
+ m := false
+ n := false
+ q := false
+ v := false
+ clean := false
+ sl, st := "", ""
+ af, ao := "", ""
+ asses := new(arrayArg)
+ flag.StringVar(&s, "s", "", "Source folder.")
+ flag.StringVar(&f, "f", "", "MKV file. (join single mode)")
+ flag.BoolVar(&c, "c", false, "Create mode.")
+ flag.BoolVar(&d, "d", false, "Dump mode.")
+ flag.BoolVar(&m, "m", false, "Make mode.")
+ flag.BoolVar(&q, "q", false, "Query mode.")
+ flag.Var(asses, "a", "ASS files. (multiple & join ass mode)")
+ flag.BoolVar(&n, "n", false, "Not do ass font subset. (dump mode only)")
+ flag.BoolVar(&clean, "clean", false, "Clean original file subtitles and fonts. (create mode only)")
+ flag.StringVar(&sl, "sl", "chi", " Subtitle language. (create & make mode only)")
+ flag.StringVar(&st, "st", "", " Subtitle title. (create & make mode only)")
+ flag.StringVar(&af, "af", "", " ASS fonts folder. (ASS mode only)")
+ flag.StringVar(&ao, "ao", "", " ASS output folder. (ASS mode only)")
+ flag.StringVar(&data, "data", "data", "Subtitles & Fonts folder (dump & make mode only)")
+ flag.StringVar(&dist, "dist", "dist", "Results output folder (make mode only)")
+
+ flag.BoolVar(&v, "v", false, "Show app info.")
+ flag.Parse()
+
+ ec := 0
+ if v {
+ fmt.Println(appFN + " (powered by " + mkvlib.LibFName + ")")
+ return
+ }
+
+ if processer == nil {
+ ec++
+ return
+ }
+
+ if len(*asses) > 0 {
+ if !processer.ASSFontSubset(*asses, af, ao) {
+ ec++
+ }
+ return
+ }
+ if f != "" {
+ if d {
+ if !processer.DumpMKV(f, data, !n) {
+ ec++
+ }
+ return
+ }
+ if q {
+ r, err := processer.CheckSubset(f)
+ if err {
+ ec++
+ } else {
+ log.Printf("Need font subset: %v", !r)
+ }
+ return
+
+ }
+ }
+ if s != "" {
+ if q {
+ lines := processer.QueryFolder(s)
+ if len(lines) > 0 {
+ log.Printf("Has item(s).")
+ data := []byte(strings.Join(lines, "\n"))
+ if os.WriteFile("list.txt", data, os.ModePerm) != nil {
+ log.Printf("Faild to write the result file")
+ ec++
+ }
+ } else {
+ log.Printf("No item.")
+ }
+ return
+ }
+ if c {
+ v := path.Join(s, "v")
+ s := path.Join(s, "s")
+ f := path.Join(s, "f")
+ o := path.Join(s, "o")
+ if !processer.CreateMKVs(v, s, f, "", o, sl, st, clean) {
+ ec++
+ }
+ return
+ }
+ if d {
+ if !processer.DumpMKVs(s, data, !n) {
+ ec++
+ }
+ return
+ }
+ if m {
+ if !processer.MakeMKVs(s, data, dist, sl, st) {
+ ec++
+ }
+ return
+ }
+ if !processer.DumpMKVs(s, data, true) {
+ ec++
+ } else if !processer.MakeMKVs(s, data, dist, sl, st) {
+ ec++
+ }
+ return
+ } else {
+ ec++
+ flag.PrintDefaults()
+ }
+ defer os.Exit(ec)
+}
+
+func init() {
+ runtime.GOMAXPROCS(runtime.NumCPU())
+}
diff --git a/title.go b/cmd/title.go
index a67e1ec..b292eb5 100644
--- a/title.go
+++ b/cmd/title.go
@@ -1,9 +1,7 @@
-//go:build !windows
+//go:build linux
package main
-import "fmt"
-
func setWindowTitle(title string) {
fmt.Printf("\033]0;%s\007", title)
}
diff --git a/title_windows.go b/cmd/title_windows.go
index b53fe23..fc40785 100644
--- a/title_windows.go
+++ b/cmd/title_windows.go
@@ -1,5 +1,4 @@
//go:build windows
-
package main
import (
diff --git a/ass.go b/lib/ass.go
index 45cf86c..d1c114c 100644
--- a/ass.go
+++ b/lib/ass.go
@@ -1,4 +1,4 @@
-package main
+package mkvlib
import (
"encoding/binary"
@@ -13,6 +13,7 @@ import (
"strconv"
"strings"
"sync"
+ "time"
)
const (
@@ -30,7 +31,7 @@ type fontInfo struct {
sFont string
}
-type ass struct {
+type assProcessor struct {
files []string
_fonts string
output string
@@ -40,7 +41,7 @@ type ass struct {
subtitles map[string]string
}
-func (self *ass) parse() bool {
+func (self *assProcessor) parse() bool {
ec := 0
self.subtitles = make(map[string]string)
for _, file := range self.files {
@@ -61,7 +62,7 @@ func (self *ass) parse() bool {
}
}
if ec == 0 {
- reg, _ := regexp.Compile(`\{?\\fn@?([^\\]+)[\\\}]`)
+ reg, _ := regexp.Compile(`\{?\\fn@?([^\r\n\\\}]+)[\\\}]`)
m := make(map[string]map[rune]bool)
for k, v := range self.subtitles {
subtitle, err := astisub.ReadFromSSA(strings.NewReader(v))
@@ -122,7 +123,7 @@ func (self *ass) parse() bool {
return ec == 0
}
-func (self *ass) getTTCCount(file string) int {
+func (self *assProcessor) getTTCCount(file string) int {
f, err := openFile(file, true, false)
if err == nil {
defer func() { _ = f.Close() }()
@@ -134,7 +135,7 @@ func (self *ass) getTTCCount(file string) int {
return 0
}
-func (self *ass) dumpFont(file string, full bool) bool {
+func (self *assProcessor) dumpFont(file string, full bool) bool {
ok := false
count := 1
_, n, _, _ := splitPath(file)
@@ -167,7 +168,7 @@ func (self *ass) dumpFont(file string, full bool) bool {
return ok
}
-func (self *ass) dumpFonts(files []string, full bool) bool {
+func (self *assProcessor) dumpFonts(files []string, full bool) bool {
ok := 0
l := len(files)
wg := new(sync.WaitGroup)
@@ -188,7 +189,7 @@ func (self *ass) dumpFonts(files []string, full bool) bool {
return ok == l
}
-func (self *ass) getFontName(p string) []string {
+func (self *assProcessor) getFontName(p string) []string {
f, err := openFile(p, true, false)
if err == nil {
defer func() { _ = f.Close() }()
@@ -212,7 +213,7 @@ func (self *ass) getFontName(p string) []string {
return nil
}
-func (self *ass) getFontsName() map[string][]string {
+func (self *assProcessor) getFontsName() map[string][]string {
files, _ := findPath(self._fonts, `\.ttx$`)
l := len(files)
wg := new(sync.WaitGroup)
@@ -234,7 +235,7 @@ func (self *ass) getFontsName() map[string][]string {
return _m
}
-func (self *ass) matchFonts() bool {
+func (self *assProcessor) matchFonts() bool {
if !self.dumpFonts(self.fonts, false) {
return false
}
@@ -265,7 +266,7 @@ func (self *ass) matchFonts() bool {
return ok
}
-func (self *ass) createFontSubset(font *fontInfo) bool {
+func (self *assProcessor) createFontSubset(font *fontInfo) bool {
ok := false
fn := fmt.Sprintf(`%s.txt`, font.file)
_, n, e, ne := splitPath(font.file)
@@ -301,7 +302,7 @@ func (self *ass) createFontSubset(font *fontInfo) bool {
return ok
}
-func (self *ass) createFontsSubset() bool {
+func (self *assProcessor) createFontsSubset() bool {
err := os.RemoveAll(self.output)
if !(err == nil || err == os.ErrNotExist) {
log.Println("Failed to clean the output folder.")
@@ -327,7 +328,7 @@ func (self *ass) createFontsSubset() bool {
return ok == l
}
-func (self *ass) changeFontName(font *fontInfo) bool {
+func (self *assProcessor) changeFontName(font *fontInfo) bool {
ec := 0
if self.dumpFont(font.sFont, true) {
fn := fmt.Sprintf("%s_0.ttx", font.sFont)
@@ -342,7 +343,7 @@ func (self *ass) changeFontName(font *fontInfo) bool {
id := v.SelectAttr("nameID")
switch id {
case "0":
- v.FirstChild.Data = "Processed by " + pName
+ v.FirstChild.Data = "Processed by " + LibFName + " at " + time.Now().Format("2006-01-02 15:04:05")
break
case "1", "3", "4", "6":
v.FirstChild.Data = font.newName
@@ -376,7 +377,7 @@ func (self *ass) changeFontName(font *fontInfo) bool {
return ec == 0
}
-func (self *ass) changeFontsName() bool {
+func (self *assProcessor) changeFontsName() bool {
ok := 0
l := len(self.m)
wg := new(sync.WaitGroup)
@@ -397,7 +398,7 @@ func (self *ass) changeFontsName() bool {
return ok == l
}
-func (self *ass) replaceFontNameInAss() bool {
+func (self *assProcessor) replaceFontNameInAss() bool {
ec := 0
m := make(map[string]map[string]bool)
for _, v := range self.m {
@@ -406,9 +407,10 @@ func (self *ass) replaceFontNameInAss() bool {
m[f] = make(map[string]bool)
}
n := regexp.QuoteMeta(v.oldName)
- reg, _ := regexp.Compile(fmt.Sprintf(`(Style:[^,\n]+,|\\fn)(@?)%s`, n))
+ reg, _ := regexp.Compile(fmt.Sprintf(`(Style:[^,\r\n]+,|\\fn)(@?)%s([,\\\}])`, n))
if reg.MatchString(s) {
- s = reg.ReplaceAllString(s, "${1}${2}"+v.newName)
+ r := fmt.Sprintf("${1}${2}%s${3}", v.newName)
+ s = reg.ReplaceAllString(s, r)
m[f][v.oldName] = true
self.subtitles[f] = s
}
@@ -416,13 +418,14 @@ func (self *ass) replaceFontNameInAss() bool {
}
for f, s := range self.subtitles {
comments := make([]string, 0)
- comments = append(comments, "[script info]")
+ comments = append(comments, "[Script Info]")
comments = append(comments, "; ----- Font subset begin -----")
for k, _ := range m[f] {
comments = append(comments, fmt.Sprintf("; Font subset: %s - %s", self.m[k].newName, k))
}
if len(comments) > 2 {
- comments = append(comments, "; Processed by "+pName)
+ comments = append(comments, "")
+ comments = append(comments, "; Processed by "+LibFName+" at "+time.Now().Format("2006-01-02 15:04:05"))
comments = append(comments, "; ----- Font subset end -----")
comments = append(comments, "")
s = strings.Replace(s, "[Script Info]\r\n", strings.Join(comments, "\r\n"), 1)
@@ -441,25 +444,3 @@ func (self *ass) replaceFontNameInAss() bool {
}
return ec == 0
}
-
-func genASSes(files []string, fonts, output string) bool {
- if len(files) == 0 {
- return false
- }
- obj := new(ass)
- obj.files = files
- obj._fonts = fonts
- obj.output = output
-
- d, _, _, _ := splitPath(obj.files[0])
- if obj._fonts == "" {
- obj._fonts += path.Join(d, "fonts")
- }
- if obj.output == "" {
- obj.output += path.Join(d, "output")
- }
-
- obj.fonts = findFonts(obj._fonts)
-
- return obj.parse() && obj.matchFonts() && obj.createFontsSubset() && obj.changeFontsName() && obj.replaceFontNameInAss()
-}
diff --git a/go.mod b/lib/go.mod
index bbd0e01..3be11ce 100644
--- a/go.mod
+++ b/lib/go.mod
@@ -1,9 +1,9 @@
-module mkvtool
+module mkvlib
go 1.17
require (
- github.com/antchfx/xmlquery v1.3.7
+ github.com/antchfx/xmlquery v1.3.8
github.com/asticode/go-astisub v0.19.0
)
diff --git a/lib/mkv.go b/lib/mkv.go
new file mode 100644
index 0000000..6dd76c9
--- /dev/null
+++ b/lib/mkv.go
@@ -0,0 +1,295 @@
+package mkvlib
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "path"
+ "regexp"
+ "strings"
+)
+
+const (
+ mkvmerge = `mkvmerge`
+ mkvextract = `mkvextract`
+)
+
+type mkvInfo struct {
+ Attachments []struct {
+ ID int `json:"id"`
+ FileName string `json:"file_name"`
+ Size int `json:"size"`
+ ContentType string `json:"content_type"`
+ } `json:"attachments"`
+ Tracks []struct {
+ ID int `json:"id"`
+ Type string `json:"type"`
+ Codec string `json:"codec"`
+ Properties struct {
+ Language string `json:"language"`
+ TrackName string `json:"track_name"`
+ } `json:"properties"`
+ }
+}
+
+type mkvProcessor bool
+
+func (self *mkvProcessor) GetMKVInfo(file string) *mkvInfo {
+ buf := bytes.NewBufferString("")
+ if p, err := newProcess(nil, buf, nil, "", mkvmerge, "-J", file); err == nil {
+ if s, err := p.Wait(); err == nil && s.ExitCode() == 0 {
+ obj := new(mkvInfo)
+ _ = json.Unmarshal(buf.Bytes(), obj)
+ return obj
+ }
+ }
+ return nil
+}
+
+func (self *mkvProcessor) DumpMKV(file, output string, subset bool) bool {
+ ec := 0
+ obj := self.GetMKVInfo(file)
+ if obj == nil {
+ log.Printf(`Failed to get the mkv file info: "%s".`, file)
+ return false
+ }
+ attachments := make([]string, 0)
+ tracks := make([]string, 0)
+ for _, _item := range obj.Attachments {
+ attachments = append(attachments, fmt.Sprintf(`%d:%s`, _item.ID, path.Join(output, "fonts", _item.FileName)))
+ }
+ for _, _item := range obj.Tracks {
+ if _item.Type == "subtitles" {
+ s := fmt.Sprintf(`%d_%s_%s`, _item.ID, _item.Properties.Language, _item.Properties.TrackName)
+ if _item.Codec == "SubStationAlpha" {
+ s += ".ass"
+ } else {
+ s += ".sub"
+ }
+ tracks = append(tracks, fmt.Sprintf(`%d:%s`, _item.ID, path.Join(output, s)))
+ }
+ }
+ args := make([]string, 0)
+ args = append(args, file)
+ args = append(args, "attachments")
+ args = append(args, attachments...)
+ args = append(args, "tracks")
+ args = append(args, tracks...)
+ if p, err := newProcess(nil, nil, nil, "", mkvextract, args...); err == nil {
+ s, err := p.Wait()
+ ok := err == nil && s.ExitCode() == 0
+ if ok {
+ if subset {
+ asses := make([]string, 0)
+ for _, _item := range tracks {
+ _arr := strings.Split(_item, ":")
+ f := _arr[len(_arr)-1]
+ if strings.HasSuffix(f, ".ass") {
+ asses = append(asses, f)
+ }
+ if len(asses) > 0 {
+ if !self.ASSFontSubset(asses, "", "") {
+ ec++
+ }
+ }
+ }
+ }
+ } else {
+ ec++
+ }
+ } else {
+ ec++
+ }
+ return ec == 0
+}
+
+func (self *mkvProcessor) CheckSubset(file string) (bool, bool) {
+ obj := self.GetMKVInfo(file)
+ if obj == nil {
+ log.Printf(`Failed to get the mkv file info: "%s".`, file)
+ return false, true
+ }
+ ass := false
+ ok := false
+ reg, _ := regexp.Compile(`\.[A-Z0-9]{8}\.\S+$`)
+ for _, track := range obj.Tracks {
+ ass = track.Type == "subtitles" && track.Codec == "SubStationAlpha"
+ if ass {
+ break
+ }
+ }
+ for _, attachment := range obj.Attachments {
+ ok = !ass || (strings.HasPrefix(attachment.ContentType, "font/") && reg.MatchString(attachment.FileName))
+ if ok {
+ break
+ }
+ }
+ return !ass || (ass && ok), false
+}
+
+func (self *mkvProcessor) CreateMKV(file string, tracks, attachments []string, output, slang, stitle string, clean bool) bool {
+ args := make([]string, 0)
+ args = append(args, "--output", output)
+ if clean {
+ args = append(args, "--no-subtitles", "--no-attachments")
+ }
+ args = append(args, file)
+ for _, _item := range attachments {
+ args = append(args, "--attach-file", _item)
+ }
+ for _, _item := range tracks {
+ _, _, _, f := splitPath(_item)
+ _arr := strings.Split(f, "_")
+ _sl := slang
+ _st := stitle
+ if len(_arr) > 1 {
+ _sl = _arr[1]
+ }
+ if len(_arr) > 2 {
+ _st = _arr[2]
+ }
+ if _sl != "" {
+ args = append(args, "--language", "0:"+_sl)
+ }
+ if _st != "" {
+ args = append(args, "--track-name", "0:"+_st)
+ }
+ args = append(args, _item)
+ }
+ if p, err := newProcess(nil, nil, nil, "", mkvmerge, args...); err == nil {
+ s, err := p.Wait()
+ return err == nil && s.ExitCode() == 0
+ }
+ return false
+}
+
+func (self *mkvProcessor) DumpMKVs(dir, output string, subset bool) bool {
+ ec := 0
+ files := findMKVs(dir)
+ l := len(files)
+ for i, item := range files {
+ p := strings.TrimPrefix(item, dir)
+ d, _, _, f := splitPath(p)
+ p = path.Join(output, d, f)
+ if !self.DumpMKV(item, p, subset) {
+ ec++
+ log.Printf(`Failed to dump the mkv file "%s".`, item)
+ }
+ log.Printf("Dump (%d/%d) done.", i+1, l)
+ }
+ return ec == 0
+}
+
+func (self *mkvProcessor) QueryFolder(dir string) []string {
+ ec := 0
+ lines := make([]string, 0)
+ files := findMKVs(dir)
+ l := len(files)
+ for i, file := range files {
+ a, b := self.CheckSubset(file)
+ if b {
+ ec++
+ } else if !a {
+ lines = append(lines, file)
+ }
+ log.Printf("Query (%d/%d) done.", i+1, l)
+ }
+ return lines
+}
+
+func (self *mkvProcessor) CreateMKVs(vDir, sDir, fDir, tDir, oDir string, slang, stitle string, clean bool) bool {
+ ec := 0
+ if tDir == "" {
+ tDir = os.TempDir()
+ }
+ tDir = path.Join(tDir, randomStr(8))
+ files, _ := findPath(vDir, fmt.Sprintf(`\.\S+$`))
+ l := len(files)
+ for i, item := range files {
+ _, _, _, _f := splitPath(item)
+ tmp, _ := findPath(sDir, fmt.Sprintf(`%s\S*\.\S+$`, regexp.QuoteMeta(_f)))
+ asses := make([]string, 0)
+ subs := make([]string, 0)
+ p := path.Join(tDir, _f)
+ for _, sub := range tmp {
+ if strings.HasSuffix(sub, ".ass") {
+ _, _, _, __f := splitPath(sub)
+ _s := path.Join(p, __f) + ".ass"
+ _ = copyFileOrDir(sub, _s)
+ asses = append(asses, _s)
+ } else {
+ subs = append(subs, sub)
+ }
+ }
+ attachments := make([]string, 0)
+ tracks := make([]string, 0)
+ if len(asses) > 0 {
+ _ = os.RemoveAll(tDir)
+ if !self.ASSFontSubset(asses, fDir, "") {
+ ec++
+ } else {
+ __p := path.Join(p, "subsetted")
+ attachments = findFonts(__p)
+ tracks, _ = findPath(__p, `\.ass$`)
+ }
+ }
+ tracks = append(tracks, subs...)
+ fn := path.Join(oDir, _f) + ".mkv"
+ if !self.CreateMKV(item, tracks, attachments, fn, slang, stitle, clean) {
+ ec++
+ }
+ if ec > 0 {
+ log.Printf(`Failed to create the mkv file: "%s".`, item)
+ }
+ log.Printf("Create (%d/%d) done.", i+1, l)
+ }
+ _ = os.RemoveAll(tDir)
+ return ec == 0
+}
+
+func (self *mkvProcessor) MakeMKVs(dir, data, output, slang, sttlte string) bool {
+ ec := 0
+ files := findMKVs(dir)
+ l := len(files)
+ for i, item := range files {
+ p := strings.TrimPrefix(item, dir)
+ d, n, _, f := splitPath(p)
+ p = path.Join(data, d, f)
+ _p := path.Join(p, "subsetted")
+ subs, _ := findPath(p, `\.sub`)
+ asses, _ := findPath(_p, `\.ass$`)
+ attachments := findFonts(_p)
+ tracks := append(subs, asses...)
+ fn := path.Join(output, d, n)
+ if !self.CreateMKV(item, tracks, attachments, fn, slang, sttlte, true) {
+ ec++
+ log.Printf(`Faild to make the mkv file: "%s".`, item)
+ }
+ log.Printf("Make (%d/%d) done.", i+1, l)
+ }
+ return ec == 0
+}
+
+func (self *mkvProcessor) ASSFontSubset(files []string, fonts, output string) bool {
+ if len(files) == 0 {
+ return false
+ }
+ obj := new(assProcessor)
+ obj.files = files
+ obj._fonts = fonts
+ obj.output = output
+
+ d, _, _, _ := splitPath(obj.files[0])
+ if obj._fonts == "" {
+ obj._fonts += path.Join(d, "fonts")
+ }
+ if obj.output == "" {
+ obj.output += path.Join(d, "subsetted")
+ }
+
+ obj.fonts = findFonts(obj._fonts)
+
+ return obj.parse() && obj.matchFonts() && obj.createFontsSubset() && obj.changeFontsName() && obj.replaceFontNameInAss()
+}
diff --git a/lib/shared.go b/lib/shared.go
new file mode 100644
index 0000000..215ce7a
--- /dev/null
+++ b/lib/shared.go
@@ -0,0 +1,36 @@
+package mkvlib
+
+import (
+ "log"
+ "os/exec"
+)
+
+const libName = "mkvlib"
+const libVer = "1.0.0"
+
+const LibFName = libName + " " + libVer
+
+var _instance *mkvProcessor
+
+func GetInstance() *mkvProcessor {
+ ec := 0
+ _, _ttx := exec.LookPath(ttx)
+ _, _pyftsubset := exec.LookPath(pyftsubset)
+ _, _mkvextract := exec.LookPath(mkvextract)
+ _, _mkvmerge := exec.LookPath(mkvmerge)
+ if _ttx != nil || _pyftsubset != nil {
+ log.Printf(`Missing dependency: fonttools (need "%s" & "%s").`, ttx, pyftsubset)
+ ec++
+ }
+ if _mkvextract != nil || _mkvmerge != nil {
+ log.Printf(`Missing dependency: mkvtoolnix (need "%s" & "%s").`, mkvextract, mkvmerge)
+ ec++
+ }
+ if ec > 0 {
+ return nil
+ }
+ if _instance == nil {
+ _instance = new(mkvProcessor)
+ }
+ return _instance
+}
diff --git a/utils.go b/lib/utils.go
index ff91308..a76a05d 100644
--- a/utils.go
+++ b/lib/utils.go
@@ -1,4 +1,4 @@
-package main
+package mkvlib
import (
"errors"
@@ -209,6 +209,11 @@ func randomNumber(min, max int) int {
return min + randomN(sub)
}
+func findMKVs(dir string) []string {
+ list, _ := findPath(dir, `\.mkv$`)
+ return list
+}
+
func findFonts(dir string) []string {
list, _ := findPath(dir, `\.((ttf)|(otf)|(ttc)|(fon))$`)
return list
diff --git a/main.go b/main.go
deleted file mode 100644
index fe32de0..0000000
--- a/main.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package main
-
-import (
- "flag"
- "fmt"
- "os"
- "runtime"
-)
-
-const pName = "MKV Tool v3.0.3"
-
-type arrayArg []string
-
-func (self *arrayArg) String() string {
- return fmt.Sprintf("%v", []string(*self))
-}
-
-func (self *arrayArg) Set(value string) error {
- *self = append(*self, value)
- return nil
-}
-
-func main() {
- setWindowTitle(pName)
- s := ""
- c := false
- d := false
- m := false
- n := false
- q := false
- sl, st := "", ""
- af, ao := "", ""
- arr := new(arrayArg)
- flag.StringVar(&s, "s", "", "Source folder.")
- flag.BoolVar(&c, "c", false, "Create mode.")
- flag.BoolVar(&d, "d", false, "Dump mode.")
- flag.BoolVar(&m, "m", false, "Make mode.")
- flag.BoolVar(&q, "q", false, "Query mode.")
- flag.Var(arr, "a", "ASS files. (multiple & join ass mode)")
- flag.BoolVar(&n, "n", false, "Not do ass font subset. (dump mode only)")
- flag.StringVar(&sl, "sl", "chi", " Subtitle language. (create mode only)")
- flag.StringVar(&st, "st", "", " Subtitle title. (create mode only)")
- flag.StringVar(&af, "af", "", " ASS fonts folder. (ASS mode only)")
- flag.StringVar(&ao, "ao", "", " ASS output folder. (ASS mode only)")
- flag.Parse()
-
- ec := 0
- if len(*arr) > 0 {
- if !genASSes(*arr, af, ao) {
- ec++
- }
- return
- } else if s != "" {
- if q {
- if !queryFolder(s) {
- ec++
- }
- return
- }
- if c {
- if sl != "" {
- if !createMKVs(s, sl, st) {
- ec++
- }
- return
- }
- }
- if d {
- if !dumpMKVs(s, !n) {
- ec++
- }
- return
- }
- if m {
- if !makeMKVs(s) {
- ec++
- }
- return
- }
- if !dumpMKVs(s, true) {
- ec++
- } else if !makeMKVs(s) {
- ec++
- }
- return
- } else {
- ec++
- flag.PrintDefaults()
- }
- defer os.Exit(ec)
-}
-
-func init() {
- runtime.GOMAXPROCS(runtime.NumCPU())
-}
diff --git a/mkv.go b/mkv.go
deleted file mode 100644
index 41f7a2f..0000000
--- a/mkv.go
+++ /dev/null
@@ -1,294 +0,0 @@
-package main
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "log"
- "os"
- "path"
- "regexp"
- "strings"
-)
-
-const (
- mkvmerge = `mkvmerge`
- mkvextract = `mkvextract`
-)
-
-type mkv struct {
- Attachments []struct {
- ID int `json:"id"`
- FileName string `json:"file_name"`
- Size int `json:"size"`
- ContentType string `json:"content_type"`
- } `json:"attachments"`
- Tracks []struct {
- ID int `json:"id"`
- Type string `json:"type"`
- Codec string `json:"codec"`
- Properties struct {
- Language string `json:"language"`
- TrackName string `json:"track_name"`
- } `json:"properties"`
- }
-}
-
-func getMKVInfo(file string) *mkv {
- buf := bytes.NewBufferString("")
- if p, err := newProcess(nil, buf, nil, "", mkvmerge, "-J", file); err == nil {
- if s, err := p.Wait(); err == nil && s.ExitCode() == 0 {
- obj := new(mkv)
- _ = json.Unmarshal(buf.Bytes(), obj)
- return obj
- }
- }
- return nil
-}
-
-func dumpMKVs(dir string, subset bool) bool {
- ec := 0
- files, _ := findPath(dir, `\.mkv$`)
- arr := strings.Split(dir, string(os.PathSeparator))
- p := path.Join(`data`, arr[len(arr)-1])
- l := len(files)
- for i, item := range files {
- tmp := strings.Replace(item, dir, p, 1)
- obj := getMKVInfo(item)
- if obj == nil {
- ec++
- log.Printf(`Failed to get the mkv file info: "%s".`, item)
- break
- }
- attachments := make([]string, 0)
- tracks := make([]string, 0)
- for _, _item := range obj.Attachments {
- d, _, _, f := splitPath(tmp)
- attachments = append(attachments, fmt.Sprintf(`%d:%s`, _item.ID, path.Join(d, f, "fonts"+
- "", _item.FileName)))
- }
- for _, _item := range obj.Tracks {
- if _item.Type == "subtitles" {
- d, _, _, f := splitPath(tmp)
- s := fmt.Sprintf(`%d_%s_%s`, _item.ID, _item.Properties.Language, _item.Properties.TrackName)
- if _item.Codec == "SubStationAlpha" {
- s += ".ass"
- } else {
- s += ".sub"
- }
- tracks = append(tracks, fmt.Sprintf(`%d:%s`, _item.ID, path.Join(d, f, s)))
- }
- }
- args := make([]string, 0)
- args = append(args, item)
- args = append(args, "attachments")
- args = append(args, attachments...)
- args = append(args, "tracks")
- args = append(args, tracks...)
- if p, err := newProcess(nil, nil, nil, "", mkvextract, args...); err == nil {
- s, err := p.Wait()
- ok := err == nil && s.ExitCode() == 0
- if ok {
- if subset {
- asses := make([]string, 0)
- for _, _item := range tracks {
- _arr := strings.Split(_item, ":")
- f := _arr[len(_arr)-1]
- if strings.HasSuffix(f, ".ass") {
- asses = append(asses, f)
- }
- if len(asses) > 0 {
- if !genASSes(asses, "", "") {
- ec++
- }
- }
- }
- }
- } else {
- ec++
- }
- } else {
- ec++
- }
- if ec > 0 {
- log.Printf(`Failed to dump the mkv file "%s".`, item)
- }
- log.Printf("Dump (%d/%d) done.", i+1, l)
- }
- return ec == 0
-}
-
-func makeMKVs(dir string) bool {
- ec := 0
- _arr := strings.Split(dir, string(os.PathSeparator))
- p := _arr[len(_arr)-1]
- dir2 := path.Join(`data`, p)
- files, _ := findPath(dir, `\.mkv$`)
- l := len(files)
- for i, item := range files {
- tmp := strings.Replace(item, dir, p, 1)
- d, _, _, f := splitPath(tmp)
- d = strings.Replace(d, p, "", 1)
- _p := path.Join(dir2, d, f)
- __p := path.Join(_p, "output")
- attachments := findFonts(__p)
- subs, _ := findPath(_p, `\.sub`)
- asses, _ := findPath(__p, `\.ass$`)
- tracks := append(subs, asses...)
- args := make([]string, 0)
- args = append(args, "--output", path.Join("dist", tmp))
- args = append(args, "--no-subtitles", "--no-attachments")
- args = append(args, item)
- for _, _item := range attachments {
- args = append(args, "--attach-file", _item)
- }
- for _, _item := range tracks {
- _, _, _, f = splitPath(_item)
- _arr := strings.Split(f, "_")
- args = append(args, "--language", "0:"+_arr[1])
- if len(_arr) > 2 {
- args = append(args, "--track-name", "0:"+_arr[2])
- }
- args = append(args, _item)
- }
- if p, err := newProcess(nil, nil, nil, "", mkvmerge, args...); err == nil {
- s, err := p.Wait()
- ok := err == nil && s.ExitCode() == 0
- if !ok {
- ec++
- }
- } else {
- ec++
- }
- if ec > 0 {
- log.Printf(`Faild to make the mkv file: "%s".`, item)
- }
- log.Printf("Make (%d/%d) done.", i+1, l)
- }
- return ec == 0
-}
-
-func createMKVs(dir string, slang, stitle string) bool {
- ec := 0
- v := path.Join(dir, "v")
- s := path.Join(dir, "s")
- f := path.Join(dir, "f")
- t := path.Join(dir, "t")
- o := path.Join(dir, "o")
- files, _ := findPath(v, fmt.Sprintf(`\.\S+$`))
- l := len(files)
- _ = os.RemoveAll(t)
- for i, item := range files {
- _, _, _, _f := splitPath(item)
- tmp, _ := findPath(s, fmt.Sprintf(`%s\S*\.\S+$`, regexp.QuoteMeta(_f)))
- asses := make([]string, 0)
- subs := make([]string, 0)
- p := path.Join(t, _f)
- for _, sub := range tmp {
- if strings.HasSuffix(sub, ".ass") {
- _, _, _, __f := splitPath(sub)
- __s := path.Join(p, __f) + ".ass"
- _ = copyFileOrDir(sub, __s)
- asses = append(asses, __s)
- } else {
- subs = append(subs, sub)
- }
- }
- if len(asses) > 0 {
- if !genASSes(asses, f, "") {
- ec++
- }
- }
- __p := path.Join(p, "output")
- attachments := findFonts(__p)
- tracks, _ := findPath(__p, `\.ass$`)
- tracks = append(tracks, subs...)
- args := make([]string, 0)
- args = append(args, "--output", path.Join(o, _f)+".mkv")
- args = append(args, item)
- for _, _item := range attachments {
- args = append(args, "--attach-file", _item)
- }
- for _, _item := range tracks {
- _, _, _, _f = splitPath(_item)
- _arr := strings.Split(_f, "_")
- _l := len(_arr)
- _sl := slang
- _st := stitle
- if _l > 1 {
- _sl = _arr[1]
- }
- if _l > 2 {
- _st = _arr[2]
- }
- args = append(args, "--language", "0:"+_sl)
- args = append(args, "--track-name", "0:"+_st)
- args = append(args, _item)
- }
- if p, err := newProcess(nil, nil, nil, "", mkvmerge, args...); err == nil {
- s, err := p.Wait()
- ok := err == nil && s.ExitCode() == 0
- if !ok {
- ec++
- }
- } else {
- ec++
- }
- if ec > 0 {
- log.Printf(`Failed to create the mkv file: "%s".`, item)
- }
- log.Printf("Create (%d/%d) done.", i+1, l)
- }
- return ec == 0
-}
-
-func checkSubset(path string) (bool, bool) {
- obj := getMKVInfo(path)
- if obj == nil {
- log.Printf(`Failed to get the mkv file info: "%s".`, path)
- return false, true
- }
- ass := false
- ok := false
- reg, _ := regexp.Compile(`\.[A-Z0-9]{8}\.\S+$`)
- for _, track := range obj.Tracks {
- ass = track.Type == "subtitles" && track.Codec == "SubStationAlpha"
- if ass {
- break
- }
- }
- for _, attachment := range obj.Attachments {
- ok = !ass || (strings.HasPrefix(attachment.ContentType, "font/") && reg.MatchString(attachment.FileName))
- if ok {
- break
- }
- }
- return !ass || (ass && ok), false
-}
-
-func queryFolder(dir string) bool {
- ec := 0
- lines := make([]string, 0)
- files, _ := findPath(dir, `\.mkv$`)
- l := len(files)
- for i, file := range files {
- a, b := checkSubset(file)
- if b {
- ec++
- } else if !a {
- lines = append(lines, file)
- }
- log.Printf("Query (%d/%d) done.", i+1, l)
- }
- if len(lines) > 0 {
- fmt.Print("Has item(s).")
- data := []byte(strings.Join(lines, "\n"))
- if os.WriteFile("list.txt", data, os.ModePerm) != nil {
- log.Printf(`Faild to write the dir result file: "%s".`, dir)
- ec++
- }
- } else {
- fmt.Print("No item.")
- }
- return ec == 0
-}