summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurenai <[email protected]>2021-10-10 15:52:13 +0800
committerKurenai <[email protected]>2021-10-10 15:52:13 +0800
commitde389aa53a1a0251ff6e40027f90bb8f9ba4bbe8 (patch)
tree51874e60d34fd2f8da50cdc1c1e29cc3aa8fe781
Initial commit
-rw-r--r--.idea/.gitignore8
-rw-r--r--.idea/deployment.xml70
-rw-r--r--go.mod3
-rw-r--r--go.sum0
-rw-r--r--main.go231
-rw-r--r--utils.go193
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
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..0803dc4
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module mkvtool
+
+go 1.17
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/go.sum
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..66f4fc6
--- /dev/null
+++ b/main.go
@@ -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)
+}