diff options
| author | Kurenai <[email protected]> | 2021-10-10 15:52:13 +0800 |
|---|---|---|
| committer | Kurenai <[email protected]> | 2021-10-10 15:52:13 +0800 |
| commit | de389aa53a1a0251ff6e40027f90bb8f9ba4bbe8 (patch) | |
| tree | 51874e60d34fd2f8da50cdc1c1e29cc3aa8fe781 | |
Initial commit
| -rw-r--r-- | .idea/.gitignore | 8 | ||||
| -rw-r--r-- | .idea/deployment.xml | 70 | ||||
| -rw-r--r-- | go.mod | 3 | ||||
| -rw-r--r-- | go.sum | 0 | ||||
| -rw-r--r-- | main.go | 231 | ||||
| -rw-r--r-- | utils.go | 193 |
6 files changed, 505 insertions, 0 deletions
diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 0000000..a706494 --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false"> + <serverData> + <paths name="archlinux"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + <paths name="azusa@hk"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + <paths name="azusa@shanghai"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + <paths name="cc"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + <paths name="forko@la"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + <paths name="forko@tokyo"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + <paths name="kurenai@99"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + <paths name="openwrt"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + <paths name="virmach@ca"> + <serverdata> + <mappings> + <mapping local="$PROJECT_DIR$" web="/" /> + </mappings> + </serverdata> + </paths> + </serverData> + </component> +</project>
\ No newline at end of file @@ -0,0 +1,3 @@ +module mkvtool + +go 1.17 @@ -0,0 +1,231 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "os" + "regexp" + "strings" +) + +const ( + mkvmerge = `mkvmerge` + mkvextract = `mkvextract` + assfontsubset = `AssFontSubset` +) + +type mkv struct { + Attachments []struct { + ID int `json:"id"` + FileName string `json:"file_name"` + Size int `json:"size"` + } `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 main() { + setWindowTitle("MKV Tool v2.0.5") + s := "" + c := false + d := false + m := false + n := false + sl, st := "", "" + 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(&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.Parse() + if s != "" { + if c { + if sl != "" { + createMKVs(s, sl, st) + return + } + } + if d { + dumpMKVs(s, !n) + 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 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) + buf := bytes.NewBufferString("") + p, _ := newProcess(nil, buf, nil, "", mkvmerge, "-J", item) + _, _ = p.Wait() + obj := new(mkv) + _ = json.Unmarshal(buf.Bytes(), obj) + 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))) + } + } + 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.Print(fmt.Sprintf("\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.Print(fmt.Sprintf("\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.Print(fmt.Sprintf("\rCreate (%d/%d) done.", i+1, l)) + } +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..4acd21d --- /dev/null +++ b/utils.go @@ -0,0 +1,193 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "syscall" + "unsafe" +) + +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 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) +} + +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) (file *os.File, err error) { + file, err = os.OpenFile(filepath, os.O_RDWR, os.ModePerm) + if err != nil { + 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 = fmt.Sprintf("%s/%s", dst, n) + } + sf, err := openFile(src) + if err != nil { + return err + } + defer sf.Close() + df, err := openFile(dst) + 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) +} |
