diff options
| author | a1e7cb88 <[email protected]> | 2021-10-18 00:09:23 +0800 |
|---|---|---|
| committer | a1e7cb88 <[email protected]> | 2021-10-18 00:09:23 +0800 |
| commit | 113fd8f700015308e663c8904c95ddf374987b78 (patch) | |
| tree | 0b81a126554c24b298b6e5e038f73beef2494ae9 /mkvlib | |
| parent | c0f88cb080950879bc8af5cf498ac3aa8a8c68fb (diff) | |
update mod
Diffstat (limited to 'mkvlib')
| -rw-r--r-- | mkvlib/ass.go | 446 | ||||
| -rw-r--r-- | mkvlib/go.mod | 17 | ||||
| -rw-r--r-- | mkvlib/mkv.go | 297 | ||||
| -rw-r--r-- | mkvlib/shared.go | 54 | ||||
| -rw-r--r-- | mkvlib/utils.go | 220 |
5 files changed, 1034 insertions, 0 deletions
diff --git a/mkvlib/ass.go b/mkvlib/ass.go new file mode 100644 index 0000000..7482ac8 --- /dev/null +++ b/mkvlib/ass.go @@ -0,0 +1,446 @@ +package mkvlib + +import ( + "encoding/binary" + "fmt" + "github.com/antchfx/xmlquery" + "github.com/asticode/go-astisub" + "io" + "log" + "os" + "path" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +const ( + ttx = "ttx" + pyftsubset = "pyftsubset" +) + +type fontInfo struct { + file string + str string + index string + oldName string + newName string + ttx string + sFont string +} + +type assProcessor struct { + files []string + _fonts string + output string + m map[string]*fontInfo + fonts []string + sFonts []string + subtitles map[string]string +} + +func (self *assProcessor) 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@?([^\r\n\\\}]+)[\\\}]`) + 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 strings.HasPrefix(name, "@") && len(name) > 1 { + name = name[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) + if str != "" { + str = reg.ReplaceAllString(str, "") + str += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + reg, _ = regexp.Compile("[1234567890]") + if reg.MatchString(str) { + str = reg.ReplaceAllString(str, "") + str += "1234567890" + } + 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 *assProcessor) 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 *assProcessor) dumpFont(file string, full bool) bool { + ok := false + count := 1 + _, n, _, _ := splitPath(file) + if strings.HasSuffix(file, ".ttc") { + 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 *assProcessor) 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 *assProcessor) getFontName(p string) []string { + f, err := openFile(p, true, false) + if err == nil { + defer func() { _ = 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 + } + } + } + return names + } + return nil +} + +func (self *assProcessor) getFontsName() map[string][]string { + files, _ := findPath(self._fonts, `\.ttx$`) + l := len(files) + wg := new(sync.WaitGroup) + wg.Add(l) + m := new(sync.Mutex) + _m := make(map[string][]string) + for _, item := range files { + go func(_item string) { + names := self.getFontName(_item) + if len(names) > 0 { + m.Lock() + _m[_item] = names + m.Unlock() + } + wg.Done() + }(item) + } + wg.Wait() + return _m +} + +func (self *assProcessor) matchFonts() bool { + if !self.dumpFonts(self.fonts, false) { + return false + } + m := self.getFontsName() + if len(m) > 0 { + reg, _ := regexp.Compile(`_(\d+)\.ttx$`) + for k, _ := range self.m { + for _k, v := range m { + for _, _v := range v { + if _v == k { + self.m[k].file = reg.ReplaceAllString(_k, "") + self.m[k].ttx = _k + self.m[k].index = reg.FindStringSubmatch(_k)[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 *assProcessor) createFontSubset(font *fontInfo) bool { + ok := false + fn := fmt.Sprintf(`%s.txt`, font.file) + _, n, e, ne := splitPath(font.file) + if e == ".ttc" { + e = ".ttf" + } + 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 *assProcessor) createFontsSubset() bool { + err := os.RemoveAll(self.output) + if !(err == nil || err == os.ErrNotExist) { + log.Println("Failed to clean the output folder.") + return false + } + 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 *assProcessor) 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 " + LibFName + " at " + time.Now().Format("2006-01-02 15:04:05") + 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 *assProcessor) 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 *assProcessor) 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 := regexp.QuoteMeta(v.oldName) + reg, _ := regexp.Compile(fmt.Sprintf(`(Style:[^,\r\n]+,|\\fn)(@?)%s([,\\\}])`, n)) + if reg.MatchString(s) { + r := fmt.Sprintf("${1}${2}%s${3}", v.newName) + s = reg.ReplaceAllString(s, r) + 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, "") + 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) + _, 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 +} diff --git a/mkvlib/go.mod b/mkvlib/go.mod new file mode 100644 index 0000000..7c0f017 --- /dev/null +++ b/mkvlib/go.mod @@ -0,0 +1,17 @@ +module github.com/KurenaiRyu/MkvAutoSubset/mkvlib + +go 1.17 + +require ( + github.com/antchfx/xmlquery v1.3.8 + 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/mkvlib/mkv.go b/mkvlib/mkv.go new file mode 100644 index 0000000..e5eeb85 --- /dev/null +++ b/mkvlib/mkv.go @@ -0,0 +1,297 @@ +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, "", "", false) { + 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, "", false) { + 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, dirSafe bool) 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 = d + dirSafe = true + } + if dirSafe { + obj.output = path.Join(obj.output, "subseted") + } + obj.fonts = findFonts(obj._fonts) + + return obj.parse() && obj.matchFonts() && obj.createFontsSubset() && obj.changeFontsName() && obj.replaceFontNameInAss() +} diff --git a/mkvlib/shared.go b/mkvlib/shared.go new file mode 100644 index 0000000..b0cfb2f --- /dev/null +++ b/mkvlib/shared.go @@ -0,0 +1,54 @@ +package mkvlib + +import ( + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +const libName = "mkvlib" +const libVer = "v1.0.2" + +const LibFName = libName + " " + libVer + +var _instance *mkvProcessor + +func GetInstance() *mkvProcessor { + ec := 0 + n := "PATH" + s := ":" + if runtime.GOOS == "windows" { + n = "path" + s = ";" + } + p := os.Getenv(n) + if !strings.HasSuffix(p, s) { + p += s + } + e, _ := os.Executable() + e, _ = filepath.Split(e) + p += e + _ = os.Setenv(n, p) + _, _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/mkvlib/utils.go b/mkvlib/utils.go new file mode 100644 index 0000000..a76a05d --- /dev/null +++ b/mkvlib/utils.go @@ -0,0 +1,220 @@ +package mkvlib + +import ( + "errors" + "fmt" + "io" + "math/rand" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strings" + "time" +) + +func newProcess(stdin io.Reader, stdout, stderr io.Writer, dir, prog string, args ...string) (p *os.Process, err error) { + cmd := exec.Command(prog, args...) + if dir != "" { + cmd.Dir = dir + } + if stdin != nil { + cmd.Stdin = stdin + } + if stdout != nil { + cmd.Stdout = stdout + } + if stderr != nil { + cmd.Stderr = stderr + } + err = cmd.Start() + if err == nil { + p = cmd.Process + } + return +} + +func newDir(path string) error { + return os.MkdirAll(path, os.ModePerm) +} + +func queryPath(path string, cb func(string) bool) error { + return filepath.Walk(path, func(path string, f os.FileInfo, err error) error { + if f == nil { + return err + } + if f.IsDir() { + return nil + } + if cb(path) { + return nil + } + return errors.New("call cb return false") + }) +} + +func findPath(path, expr string) (list []string, err error) { + list = make([]string, 0) + reg, e := regexp.Compile(expr) + if e != nil { + err = e + return + } + err = queryPath(path, func(path string) bool { + if expr == "" || reg.MatchString(path) { + list = append(list, path) + } + return true + }) + return +} + +func copyFolder(src, dst string) error { + e, f := isExists(src) + if !e { + return errors.New("src is not exists") + } + if !f { + return errors.New("src is not folder") + } + if newDir(dst) != nil { + return errors.New("faild to create dst folder") + } + s := len(src) + if _, n, _, _ := splitPath(dst); n == "" { + _, n, _, _ = splitPath(src) + if n == "" { + _, n, _, _ = splitPath(src[:len(src)-1]) + } + dst = fmt.Sprintf("%s/%s", dst, n) + } + return filepath.Walk(src, func(path string, f os.FileInfo, err error) error { + if f == nil { + return err + } + if f.IsDir() { + return nil + } + return copyFile(path, dst+"/"+path[s:]) + }) +} + +func newFile(fp string) (file *os.File, err error) { + dir, _ := filepath.Split(fp) + if dir != "" { + err = newDir(dir) + if err != nil { + return + } + } + if err == nil { + file, err = os.Create(fp) + } + return +} + +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 && create { + file, err = newFile(filepath) + } + return +} + +func copyFile(src, dst string) error { + e, f := isExists(src) + if !e { + return errors.New("src is not exists") + } + if f { + return errors.New("src is not file") + } + if _, n, _, _ := splitPath(dst); n == "" { + _, n, _, _ = splitPath(src) + dst = path.Join(dst, n) + } + sf, err := openFile(src, true, false) + if err != nil { + return err + } + defer sf.Close() + df, err := openFile(dst, false, true) + if err != nil { + return err + } + defer df.Close() + + _, err = io.Copy(df, sf) + return err +} + +func splitPath(p string) (dir, name, ext, namewithoutext string) { + dir, name = filepath.Split(p) + ext = filepath.Ext(name) + n := strings.LastIndex(name, ".") + if n > 0 { + namewithoutext = name[:n] + } + return +} + +func isExists(path string) (exists bool, isFolder bool) { + f, err := os.Stat(path) + exists = err == nil || os.IsExist(err) + if exists { + isFolder = f.IsDir() + } + return +} + +func copyFileOrDir(src, dst string) error { + e, f := isExists(src) + if !e { + return errors.New("src is not exists") + } + if !f { + return copyFile(src, dst) + } + 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) +} + +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 +} |
