diff options
| author | Kurenai <[email protected]> | 2021-10-17 00:02:28 +0800 |
|---|---|---|
| committer | Kurenai <[email protected]> | 2021-10-17 00:24:41 +0800 |
| commit | 70a7c4edba281122c05ef46e9efffe5309ef8448 (patch) | |
| tree | 9e22aa0754ec0bea7f040874dfe3d626a4549503 | |
| parent | 41568b669ab6bf44e01c7b95584a03a9ce7de8eb (diff) | |
Bump to 3.1.1
| -rw-r--r-- | cmd/go.mod | 18 | ||||
| -rw-r--r-- | cmd/main.go | 155 | ||||
| -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.go | 295 | ||||
| -rw-r--r-- | lib/shared.go | 36 | ||||
| -rw-r--r-- | lib/utils.go (renamed from utils.go) | 7 | ||||
| -rw-r--r-- | main.go | 95 | ||||
| -rw-r--r-- | mkv.go | 294 |
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()) +} @@ -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 ( @@ -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() -} @@ -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 +} @@ -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()) -} @@ -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 -} |
