summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurenai <[email protected]>2021-10-14 20:15:15 +0800
committerKurenai <[email protected]>2021-10-14 20:15:15 +0800
commit6ed59f73a2f6a53246ff29fdca0a73f3208352f8 (patch)
tree88831f74800a25727d4683191b8ddfc2026ef1a1
parentafdf70773acc30668fbcee62eb9d663fef54365d (diff)
Upgrade to 3.0.1
- 脱离对C#版本AssFontSubset的依赖 - 增加-a选项,可重复使用以代替AssFontSubset原有的功能 - 修正替换字体名称的正则表达式
-rw-r--r--ass.go436
-rw-r--r--go.mod14
-rw-r--r--go.sum0
-rw-r--r--main.go283
-rw-r--r--mkv.go295
-rw-r--r--title.go9
-rw-r--r--title_windows.go22
-rw-r--r--utils.go68
8 files changed, 870 insertions, 257 deletions
diff --git a/ass.go b/ass.go
new file mode 100644
index 0000000..3395bb4
--- /dev/null
+++ b/ass.go
@@ -0,0 +1,436 @@
+package main
+
+import (
+ "encoding/binary"
+ "fmt"
+ "github.com/antchfx/xmlquery"
+ "github.com/asticode/go-astisub"
+ "io"
+ "log"
+ "os"
+ "path"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+const (
+ ttx = "ttx"
+ pyftsubset = "pyftsubset"
+)
+
+type fontInfo struct {
+ file string
+ str string
+ index string
+ oldName string
+ newName string
+ ttx string
+ sFont string
+}
+
+type ass struct {
+ files []string
+ _fonts string
+ output string
+ m map[string]*fontInfo
+ fonts []string
+ sFonts []string
+ subtitles map[string]string
+}
+
+func (self *ass) parse() bool {
+ ec := 0
+ self.subtitles = make(map[string]string)
+ for _, file := range self.files {
+ f, err := openFile(file, true, false)
+ if err != nil {
+ ec++
+ } else {
+ data, _ := io.ReadAll(f)
+ str := string(data)
+ if err == nil {
+ self.subtitles[file] = str
+ } else {
+ ec++
+ }
+ }
+ if ec > 0 {
+ log.Printf(`Failed to read the ass file: "%s"`, file)
+ }
+ }
+ if ec == 0 {
+ reg, _ := regexp.Compile(`\{?\\fn@?([^\\]+)[\\\}]`)
+ m := make(map[string]map[rune]bool)
+ for k, v := range self.subtitles {
+ subtitle, err := astisub.ReadFromSSA(strings.NewReader(v))
+ if err != nil {
+ ec++
+ log.Printf(`Failed to read the ass file: "%s"`, k)
+ continue
+ }
+ for _, item := range subtitle.Items {
+ for _, _item := range item.Lines {
+ for _, __item := range _item.Items {
+ name := item.Style.InlineStyle.SSAFontName
+ if __item.InlineStyle != nil {
+ arr := reg.FindStringSubmatch(__item.InlineStyle.SSAEffect)
+ if len(arr) > 1 {
+ name = arr[1]
+ }
+ }
+ if m[name] == nil {
+ m[name] = make(map[rune]bool)
+ }
+ str := __item.Text
+ for _, char := range str {
+ m[name][char] = true
+ }
+ }
+ }
+ }
+ }
+ self.m = make(map[string]*fontInfo)
+ reg, _ = regexp.Compile("[A-Za-z0-9]]")
+ for k, v := range m {
+ str := ""
+ for _k, _ := range v {
+ str += string(_k)
+ }
+ str = strings.TrimSpace(str)
+ str = reg.ReplaceAllString(str, "")
+ str += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+ reg, _ = regexp.Compile("[1234567890]")
+ if reg.MatchString(str) {
+ str = reg.ReplaceAllString(str, "")
+ str += "1234567890"
+ }
+ if str != "" {
+ self.m[k] = new(fontInfo)
+ self.m[k].str = str
+ self.m[k].oldName = k
+ }
+ }
+ }
+ if len(self.m) == 0 {
+ log.Printf(`Not Found item in the ass file(s): "%d"`, len(self.files))
+ }
+ return ec == 0
+}
+
+func (self *ass) getTTCCount(file string) int {
+ f, err := openFile(file, true, false)
+ if err == nil {
+ defer func() { _ = f.Close() }()
+ data := make([]byte, 4)
+ if n, err := f.ReadAt(data, 8); err == nil && n == 4 {
+ return int(binary.BigEndian.Uint32(data))
+ }
+ }
+ return 0
+}
+
+func (self *ass) dumpFont(file string, full bool) bool {
+ ok := false
+ count := 1
+ _, n, _, _ := splitPath(file)
+ if strings.HasSuffix(file, ".ttc") && !full {
+ count = self.getTTCCount(file)
+ if count < 1 {
+ log.Printf(`Failed to get the ttc font count: "%s".`, n)
+ return ok
+ }
+ }
+ for i := 0; i < count; i++ {
+ fn := fmt.Sprintf("%s_%d.ttx", file, i)
+ args := make([]string, 0)
+ args = append(args, "-q")
+ args = append(args, "-f")
+ args = append(args, "-y", strconv.Itoa(i))
+ args = append(args, "-o", fn)
+ if !full {
+ args = append(args, "-t", "name")
+ }
+ args = append(args, file)
+ if p, err := newProcess(nil, nil, nil, "", ttx, args...); err == nil {
+ s, err := p.Wait()
+ ok = err == nil && s.ExitCode() == 0
+ }
+ if !ok {
+ log.Printf(`Failed to dump font(%t): "%s"[%d].`, full, n, i)
+ }
+ }
+ return ok
+}
+
+func (self *ass) dumpFonts(files []string, full bool) bool {
+ ok := 0
+ l := len(files)
+ wg := new(sync.WaitGroup)
+ wg.Add(l)
+ m := new(sync.Mutex)
+ for _, item := range files {
+ go func(_item string) {
+ _ok := self.dumpFont(_item, full)
+ if _ok {
+ m.Lock()
+ ok++
+ m.Unlock()
+ }
+ wg.Done()
+ }(item)
+ }
+ wg.Wait()
+ return ok == l
+}
+
+func (self *ass) matchFonts() bool {
+ if !self.dumpFonts(self.fonts, false) {
+ return false
+ }
+ files, _ := findPath(self._fonts, `\.ttx$`)
+ reg, _ := regexp.Compile(`_(\d+)\.ttx$`)
+ for _, item := range files {
+ f, err := openFile(item, true, false)
+ if err == nil {
+ defer f.Close()
+ names := make([]string, 0)
+ if xml, err := xmlquery.Parse(f); err == nil {
+ for _, v := range xml.SelectElements(`ttFont/name/namerecord[@platformID=3]`) {
+ id := v.SelectAttr("nameID")
+ name := strings.TrimSpace(v.FirstChild.Data)
+ switch id {
+ case "1":
+ names = append(names, name)
+ break
+ case "4":
+ names = append(names, name)
+ break
+ }
+ }
+ }
+ for k, _ := range self.m {
+ for _, v := range names {
+ if v == k {
+ self.m[k].file = reg.ReplaceAllString(item, "")
+ self.m[k].ttx = item
+ self.m[k].index = reg.FindStringSubmatch(item)[1]
+ self.m[k].newName = randomStr(8)
+ break
+ }
+ }
+ }
+ }
+ }
+ ok := true
+ for _, v := range self.m {
+ if v.file == "" {
+ ok = false
+ log.Printf(`Missing the font: "%s".`, v.oldName)
+ }
+ }
+ return ok
+}
+
+func (self *ass) createFontSubset(font *fontInfo) bool {
+ ok := false
+ fn := fmt.Sprintf(`%s.txt`, font.file)
+ _, n, e, ne := splitPath(font.file)
+ if e == ".ttc" {
+ e = ".ttf"
+ }
+ err := os.RemoveAll(self.output)
+ if !(err == nil || err == os.ErrNotExist) {
+ log.Println("Failed to clean the output folder.")
+ return false
+ }
+ if os.MkdirAll(self.output, os.ModePerm) != nil {
+ log.Println("Failed to create the output folder.")
+ return false
+ }
+ if os.WriteFile(fn, []byte(font.str), os.ModePerm) == nil {
+ _fn := fmt.Sprintf("%s.%s%s", ne, font.newName, e)
+ _fn = path.Join(self.output, _fn)
+ args := make([]string, 0)
+ args = append(args, "--text-file="+fn)
+ args = append(args, "--output-file="+_fn)
+ args = append(args, "--name-languages="+"*")
+ args = append(args, "--font-number="+font.index)
+ args = append(args, font.file)
+ if p, err := newProcess(nil, nil, nil, "", pyftsubset, args...); err == nil {
+ s, err := p.Wait()
+ ok = err == nil && s.ExitCode() == 0
+ }
+ if !ok {
+ log.Printf(`Failed to subset font: "%s"[%s].`, n, font.index)
+ } else {
+ font.sFont = _fn
+ }
+
+ } else {
+ log.Printf(`Failed to write the font text: "%s".`, n)
+ }
+ return ok
+}
+
+func (self *ass) createFontsSubset() bool {
+ ok := 0
+ l := len(self.m)
+ wg := new(sync.WaitGroup)
+ wg.Add(l)
+ m := new(sync.Mutex)
+ for _, item := range self.m {
+ go func(_item *fontInfo) {
+ _ok := self.createFontSubset(_item)
+ if _ok {
+ m.Lock()
+ ok++
+ m.Unlock()
+ }
+ wg.Done()
+ }(item)
+ }
+ wg.Wait()
+ return ok == l
+}
+
+func (self *ass) changeFontName(font *fontInfo) bool {
+ ec := 0
+ if self.dumpFont(font.sFont, true) {
+ fn := fmt.Sprintf("%s_0.ttx", font.sFont)
+ f, err := openFile(fn, true, false)
+ if err == nil {
+ defer func() {
+ _ = f.Close()
+ _ = os.Remove(fn)
+ }()
+ if xml, err := xmlquery.Parse(f); err == nil {
+ for _, v := range xml.SelectElements(`ttFont/name/namerecord`) {
+ id := v.SelectAttr("nameID")
+ switch id {
+ case "0":
+ v.FirstChild.Data = "Processed by " + pName
+ break
+ case "1", "3", "4", "6":
+ v.FirstChild.Data = font.newName
+ break
+ }
+ }
+ str := `<?xml version="1.0" encoding="UTF-8"?>`
+ str += xml.SelectElement("ttFont").OutputXML(true)
+ if os.WriteFile(fn, []byte(str), os.ModePerm) == nil {
+ args := make([]string, 0)
+ args = append(args, "-q")
+ args = append(args, "-f")
+ args = append(args, "-o", font.sFont)
+ args = append(args, fn)
+ ok := false
+ if p, err := newProcess(os.Stdin, nil, nil, "", ttx, args...); err == nil {
+ s, err := p.Wait()
+ ok = err == nil && s.ExitCode() == 0
+ }
+ if !ok {
+ ec++
+ _, n, _, _ := splitPath(font.sFont)
+ log.Printf(`Failed to compile the font: "%s".`, n)
+ }
+ }
+ } else {
+ log.Printf(`Faild to change the font name: "%s".`, font.oldName)
+ }
+ }
+ }
+ return ec == 0
+}
+
+func (self *ass) changeFontsName() bool {
+ ok := 0
+ l := len(self.m)
+ wg := new(sync.WaitGroup)
+ wg.Add(l)
+ m := new(sync.Mutex)
+ for _, item := range self.m {
+ go func(_item *fontInfo) {
+ _ok := self.changeFontName(_item)
+ if _ok {
+ m.Lock()
+ ok++
+ m.Unlock()
+ }
+ wg.Done()
+ }(item)
+ }
+ wg.Wait()
+ return ok == l
+}
+
+func (self *ass) replaceFontNameInAss() bool {
+ ec := 0
+ m := make(map[string]map[string]bool)
+ for _, v := range self.m {
+ for f, s := range self.subtitles {
+ if m[f] == nil {
+ m[f] = make(map[string]bool)
+ }
+ n := regEx(v.oldName)
+ reg, _ := regexp.Compile(fmt.Sprintf(`(Style:[^,\n]+),(@?)%s,`, n))
+ s = reg.ReplaceAllString(s, fmt.Sprintf("${1},${2}%s,", v.newName))
+ reg, _ = regexp.Compile(fmt.Sprintf(`\\fn(@?)%s`, n))
+ s = reg.ReplaceAllString(s, fmt.Sprintf(`\fn${1}%s`, v.newName))
+ reg, _ = regexp.Compile(fmt.Sprintf(`(\\fn)?@?%s,?`, n))
+ if reg.MatchString(s) {
+ m[f][v.oldName] = true
+ }
+ self.subtitles[f] = s
+ }
+ }
+ for f, s := range self.subtitles {
+ comments := make([]string, 0)
+ 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, "; ----- Font subset end -----")
+ comments = append(comments, "")
+ s = strings.Replace(s, "[Script Info]\r\n", strings.Join(comments, "\r\n"), 1)
+ _, n, _, _ := splitPath(f)
+ fn := path.Join(self.output, n)
+ ok := false
+ if os.WriteFile(fn, []byte(s), os.ModePerm) == nil {
+ ok = true
+ } else {
+ ec++
+ }
+ if !ok {
+ log.Printf(`Failed to write the new ass file: "%s".`, fn)
+ }
+ }
+ }
+ 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/go.mod
index 0803dc4..bbd0e01 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,17 @@
module mkvtool
go 1.17
+
+require (
+ github.com/antchfx/xmlquery v1.3.7
+ github.com/asticode/go-astisub v0.19.0
+)
+
+require (
+ github.com/antchfx/xpath v1.2.0 // indirect
+ github.com/asticode/go-astikit v0.20.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
+)
diff --git a/go.sum b/go.sum
deleted file mode 100644
index e69de29..0000000
--- a/go.sum
+++ /dev/null
diff --git a/main.go b/main.go
index e0607da..750b4d7 100644
--- a/main.go
+++ b/main.go
@@ -1,41 +1,27 @@
package main
import (
- "bytes"
- "encoding/json"
"flag"
"fmt"
"os"
- "regexp"
- "strings"
+ "runtime"
)
-const (
- mkvmerge = `mkvmerge`
- mkvextract = `mkvextract`
- assfontsubset = `AssFontSubset`
-)
+const pName = "MKV Tool v3.0.1"
-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"`
- }
+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("MKV Tool v2.1.1")
+ setWindowTitle(pName)
s := ""
c := false
d := false
@@ -43,240 +29,67 @@ func main() {
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()
- if s != "" {
+
+ ec := 0
+ if len(*arr) > 0 {
+ if !genASSes(*arr, af, ao) {
+ ec++
+ }
+ return
+ } else if s != "" {
if q {
- queryFolder(s)
+ if !queryFolder(s) {
+ ec++
+ }
return
}
if c {
if sl != "" {
- createMKVs(s, sl, st)
+ if !createMKVs(s, sl, st) {
+ ec++
+ }
return
}
}
if d {
- dumpMKVs(s, !n)
+ if !dumpMKVs(s, !n) {
+ ec++
+ }
return
}
- arr := strings.Split(s, `\`)
- p := fmt.Sprintf(`data\%s`, arr[len(arr)-1])
if m {
- makeMKVs(s, p)
- return
- }
- dumpMKVs(s, true)
- makeMKVs(s, p)
- return
- }
- flag.PrintDefaults()
-}
-
-func getMKVInfo(path string) *mkv {
- buf := bytes.NewBufferString("")
- p, _ := newProcess(nil, buf, nil, "", mkvmerge, "-J", path)
- _, _ = p.Wait()
- obj := new(mkv)
- _ = json.Unmarshal(buf.Bytes(), obj)
- return obj
-}
-
-func dumpMKVs(dir string, subset bool) {
- files, _ := findPath(dir, `\.mkv$`)
- arr := strings.Split(dir, `\`)
- p := fmt.Sprintf(`data\%s`, arr[len(arr)-1])
- l := len(files)
- for i, item := range files {
- tmp := strings.Replace(item, dir, p, 1)
- obj := getMKVInfo(item)
- 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, fmt.Sprintf(`%s%s\fonts\%s`, d, f, _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, fmt.Sprintf(`%s%s\%s`, d, f, s)))
+ if !makeMKVs(s) {
+ ec++
}
+ return
}
- args := make([]string, 0)
- args = append(args, item)
- args = append(args, "attachments")
- args = append(args, attachments...)
- args = append(args, "tracks")
- args = append(args, tracks...)
- p, _ := newProcess(nil, nil, nil, "", mkvextract, args...)
- _, _ = p.Wait()
- 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 {
- p, _ = newProcess(nil, nil, nil, "", assfontsubset, asses...)
- _, _ = p.Wait()
- }
- }
- }
- fmt.Printf("\rDump (%d/%d) done.", i+1, l)
- }
-}
-
-func makeMKVs(dir, dir2 string) {
- files, _ := findPath(dir, `\.mkv$`)
- arr := strings.Split(dir2, `\`)
- p := arr[len(arr)-1]
- 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 := fmt.Sprintf(`%s%s%s\`, dir2, d, f)
- __p := _p + "output"
- attachments, _ := findPath(__p, `\.(ttf)|(otf)|(ttc)|(fon)$`)
- subs, _ := findPath(_p, `\.sub`)
- asses, _ := findPath(__p, `\.ass$`)
- tracks := append(subs, asses...)
- args := make([]string, 0)
- args = append(args, "--output", fmt.Sprintf(`dist\%s`, 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)
- }
- p, _ := newProcess(nil, nil, nil, "", mkvmerge, args...)
- _, _ = p.Wait()
- fmt.Printf("\rMake (%d/%d) done.", i+1, l)
- }
-}
-
-func createMKVs(dir string, slang, stitle string) {
- v := dir + `\v`
- s := dir + `\s`
- f := dir + `\f`
- t := dir + `\t`
- o := dir + `\o`
- files, _ := findPath(v, fmt.Sprintf(`\.\S+$`))
- l := len(files)
- _ = os.RemoveAll(t)
- reg, _ := regexp.Compile(`[\*\.\?\+\$\^\[\]\(\)\{\}\|\\\/]`)
- for i, item := range files {
- _, _, _, _f := splitPath(item)
- _tf := reg.ReplaceAllString(_f, `\$0`)
- tmp, _ := findPath(s, fmt.Sprintf(`%s\S*\.\S+$`, _tf))
- asses := make([]string, 0)
- subs := make([]string, 0)
- p := fmt.Sprintf(`%s\%s\`, t, _f)
- for _, sub := range tmp {
- if strings.HasSuffix(sub, ".ass") {
- _, _, _, __f := splitPath(sub)
- __s := fmt.Sprintf(`%s%s.ass`, p, __f)
- _ = copyFileOrDir(sub, __s)
- asses = append(asses, __s)
- } else {
- subs = append(subs, sub)
- }
- }
- if len(asses) > 0 {
- asses = append([]string{"|f" + f}, asses...)
- _p, _ := newProcess(nil, nil, nil, "", assfontsubset, asses...)
- _, _ = _p.Wait()
- }
- __p := fmt.Sprintf(`%s\output`, p)
- attachments, _ := findPath(__p, `\.(ttf)|(otf)|(ttc)|(fon)$`)
- tracks, _ := findPath(__p, `\.ass$`)
- tracks = append(tracks, subs...)
- args := make([]string, 0)
- args = append(args, "--output", fmt.Sprintf(`%s\%s.mkv`, o, _f))
- 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)
- }
- _p, _ := newProcess(nil, nil, nil, "", mkvmerge, args...)
- _, _ = _p.Wait()
- fmt.Printf("\rCreate (%d/%d) done.", i+1, l)
- }
-}
-
-func checkSubset(path string) bool {
- obj := getMKVInfo(path)
- 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
+ if !dumpMKVs(s, true) {
+ ec++
+ } else if !makeMKVs(s) {
+ ec++
}
+ return
+ } else {
+ ec++
+ flag.PrintDefaults()
}
- return !ass || (ass && ok)
+ defer os.Exit(ec)
}
-func queryFolder(dir string) {
- lines := make([]string, 0)
- files, _ := findPath(dir, `\.mkv$`)
- l := len(files)
- for i, file := range files {
- if !checkSubset(file) {
- lines = append(lines, file)
- }
- fmt.Printf("\rQuery (%d/%d) done.", i+1, l)
- }
- if len(lines) > 0 {
- fmt.Print("\rHas item(s).")
- data := []byte(strings.Join(lines, "\n"))
- _ = os.WriteFile("list.txt", data, os.ModePerm)
- } else {
- fmt.Print("\rNo item.")
- }
+func init() {
+ runtime.GOMAXPROCS(runtime.NumCPU())
}
diff --git a/mkv.go b/mkv.go
new file mode 100644
index 0000000..c8ebed9
--- /dev/null
+++ b/mkv.go
@@ -0,0 +1,295 @@
+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)
+ _tf := reg.ReplaceAllString(_f, `\$0`)
+ tmp, _ := findPath(s, fmt.Sprintf(`%s\S*\.\S+$`, _tf))
+ 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
+}
diff --git a/title.go b/title.go
new file mode 100644
index 0000000..a67e1ec
--- /dev/null
+++ b/title.go
@@ -0,0 +1,9 @@
+//go:build !windows
+
+package main
+
+import "fmt"
+
+func setWindowTitle(title string) {
+ fmt.Printf("\033]0;%s\007", title)
+}
diff --git a/title_windows.go b/title_windows.go
new file mode 100644
index 0000000..b53fe23
--- /dev/null
+++ b/title_windows.go
@@ -0,0 +1,22 @@
+//go:build windows
+
+package main
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func setWindowTitle(title string) {
+ kernel32, err := syscall.LoadLibrary("kernel32.dll")
+ if err == nil {
+ defer syscall.FreeLibrary(kernel32)
+ setConsoleTitle, err := syscall.GetProcAddress(kernel32, "SetConsoleTitleW")
+ if err == nil {
+ ptr, err := syscall.UTF16PtrFromString(title)
+ if err == nil {
+ syscall.Syscall(setConsoleTitle, 1, uintptr(unsafe.Pointer(ptr)), 0, 0)
+ }
+ }
+ }
+}
diff --git a/utils.go b/utils.go
index 9cfded1..d274a60 100644
--- a/utils.go
+++ b/utils.go
@@ -4,13 +4,14 @@ import (
"errors"
"fmt"
"io"
+ "math/rand"
"os"
"os/exec"
+ "path"
"path/filepath"
"regexp"
"strings"
- "syscall"
- "unsafe"
+ "time"
)
func newProcess(stdin io.Reader, stdout, stderr io.Writer, dir, prog string, args ...string) (p *os.Process, err error) {
@@ -34,20 +35,6 @@ func newProcess(stdin io.Reader, stdout, stderr io.Writer, dir, prog string, arg
return
}
-func setWindowTitle(title string) {
- kernel32, err := syscall.LoadLibrary("kernel32.dll")
- if err == nil {
- defer syscall.FreeLibrary(kernel32)
- setConsoleTitle, err := syscall.GetProcAddress(kernel32, "SetConsoleTitleW")
- if err == nil {
- ptr, err := syscall.UTF16PtrFromString(title)
- if err == nil {
- syscall.Syscall(setConsoleTitle, 1, uintptr(unsafe.Pointer(ptr)), 0, 0)
- }
- }
- }
-}
-
func newDir(path string) error {
return os.MkdirAll(path, os.ModePerm)
}
@@ -127,13 +114,13 @@ func newFile(fp string) (file *os.File, err error) {
return
}
-func openFile(filepath string, readOnly bool) (file *os.File, err error) {
- f := os.O_RDWR
+func openFile(filepath string, readOnly, create bool) (file *os.File, err error) {
+ f := os.O_RDWR | os.O_CREATE
if readOnly {
f = os.O_RDONLY
}
file, err = os.OpenFile(filepath, f, os.ModePerm)
- if err != nil {
+ if err != nil && create {
file, err = newFile(filepath)
}
return
@@ -149,14 +136,14 @@ func copyFile(src, dst string) error {
}
if _, n, _, _ := splitPath(dst); n == "" {
_, n, _, _ = splitPath(src)
- dst = fmt.Sprintf("%s/%s", dst, n)
+ dst = path.Join(dst, n)
}
- sf, err := openFile(src, true)
+ sf, err := openFile(src, true, false)
if err != nil {
return err
}
defer sf.Close()
- df, err := openFile(dst, false)
+ df, err := openFile(dst, false, true)
if err != nil {
return err
}
@@ -195,3 +182,40 @@ func copyFileOrDir(src, dst string) error {
}
return copyFolder(src, dst)
}
+
+func randomStr(l int) string {
+ str := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ bytes := []byte(str)
+ var result []byte
+ lstr := len(str) - 1
+ for i := 0; i < l; i++ {
+ n := randomNumber(0, lstr)
+ result = append(result, bytes[n])
+ }
+ return string(result)
+}
+
+var r = rand.New(rand.NewSource(time.Now().UnixNano()))
+
+func randomN(n int) int {
+ return r.Intn(n)
+}
+
+func randomNumber(min, max int) int {
+ sub := max - min + 1
+ if sub <= 1 {
+ return min
+ }
+ return min + randomN(sub)
+}
+
+var reg, _ = regexp.Compile(`[\*\.\?\+\$\^\[\]\(\)\{\}\|\\\/]`)
+
+func regEx(str string) string {
+ return reg.ReplaceAllString(str, `\$0`)
+}
+
+func findFonts(dir string) []string {
+ list, _ := findPath(dir, `\.((ttf)|(otf)|(ttc)|(fon))$`)
+ return list
+}