From 46b314dc18c23713e75d1969983ca3ec6c401b47 Mon Sep 17 00:00:00 2001
From: mi wsl This is the home page for the HelloWorld Web application. To prove that they work, you can execute either of the following links:
+
"
+)
+const (
+ Default = Serena
+ Allison = "Allison" // 深沉美式女声
+ Ava = "Ava" // 深沉美式女声
+ Daniel = "Daniel" // 正式英式男声
+ Lanlan = "Lanlan" // 童声中文女声
+ Meijia = "Meijia" // 正式中文女声
+ Lilian = "Lilian" // 柔和中文女声
+ Samantha = "Samantha" // 正经美式女声
+ Serena = "Serena" // 沉稳英式女声
+ Shanshan = "Shanshan" // 浑厚中文女声
+ Shasha = "Shasha" // 成熟中文女声
+ Sinji = "Sinji" // 粤语中文女声
+ Tingting = "Tingting" // 机械中文女声
+ Victoria = "Victoria" // 压缩美式女声
+)
diff --git a/alert/email.go b/alert/email.go
new file mode 100644
index 0000000..9321262
--- /dev/null
+++ b/alert/email.go
@@ -0,0 +1,84 @@
+package alert
+
+import (
+ "fmt"
+ "gopkg.in/gomail.v2"
+ "log/slog"
+ "strings"
+ "time"
+)
+
+type Info struct {
+ Form string `json:"form"`
+ To []string `json:"to"`
+ Subject string `json:"subject"`
+ Text string `json:"text"`
+ Image string `json:"image"`
+ Host string `json:"host"`
+ Port int `json:"port"`
+ Username string `json:"username"`
+ Password string `json:"password"`
+}
+
+func init() {
+ initLocal()
+}
+
+func initLocal() {
+ var cstZone = time.FixedZone("CST", 8*3600) // 东八
+ time.Local = cstZone
+}
+
+func Send(info *Info) {
+ defer func() {
+ if err := recover(); err != nil {
+ slog.Warn("发送邮件发生错误", slog.String("错误原文", fmt.Sprint(err)))
+ }
+ }()
+ m := gomail.NewMessage()
+ m.SetHeader("From", info.Form)
+ m.SetHeader("To", info.To...)
+ //m.SetAddressHeader("Cc", "dan@example.com", "Dan")
+ m.SetHeader("Subject", info.Subject)
+ m.SetBody("text/html", info.Text)
+ if info.Image != "" {
+ m.Attach("/home/Alex/lolcat.jpg")
+ }
+ d := gomail.NewDialer(info.Host, info.Port, info.Username, info.Password)
+ if err := d.DialAndSend(m); err != nil {
+ panic(err)
+ }
+ slog.Info("发送邮件", slog.Any("内容", info))
+}
+func (i *Info) SetFrom(s string) {
+ i.Form = s
+}
+func (i *Info) SetTo(s []string) {
+ i.To = s
+}
+func (i *Info) SetSubject(s string) {
+ i.Subject = s
+}
+
+func (i *Info) SetText(s string) {
+ i.Text = s
+}
+func (i *Info) AppendText(s string) {
+ i.Text = strings.Join([]string{i.Text, s}, "
")
+}
+
+func (i *Info) SetImage(s string) {
+ i.Image = s
+}
+func (i *Info) SetHost(s string) {
+ i.Host = s
+}
+func (i *Info) SetPort(n int) {
+ i.Port = n
+}
+func (i *Info) SetUsername(s string) {
+ i.Username = s
+}
+func (i *Info) SetPassword(s string) {
+ i.Password = s
+}
diff --git a/alert/voice.go b/alert/voice.go
new file mode 100644
index 0000000..ed879f0
--- /dev/null
+++ b/alert/voice.go
@@ -0,0 +1,67 @@
+package alert
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "runtime"
+)
+
+//const (
+// Default = Serena
+// Allison = "Allison" // 深沉美式女声
+// Ava = "Ava" // 深沉美式女声
+// Daniel = "Daniel" // 正式英式男声
+// Lanlan = "Lanlan" // 童声中文女声
+// Meijia = "Meijia" // 正式中文女声
+// Lilian = "Lilian" // 柔和中文女声
+// Samantha = "Samantha" // 正经美式女声
+// Serena = "Serena" // 沉稳英式女声
+// Shanshan = "Shanshan" // 浑厚中文女声
+// Shasha = "Shasha" // 成熟中文女声
+// Sinji = "Sinji" // 粤语中文女声
+// Tingting = "Tingting" // 机械中文女声
+// Victoria = "Victoria" // 性感美式女声
+//)
+
+/*
+运行在mac上的发声命令
+*/
+func customizedOnMac(spoker, content string) {
+ defer func() {
+ if err := recover(); err != nil {
+ slog.Warn("执行发声命令出现错误", slog.Any("错误信息", err))
+ }
+ }()
+ cmd := exec.Command("say", "-v", spoker, content)
+ slog.Debug("成功执行命令", slog.String("命令", fmt.Sprint(cmd)))
+ cmd.Run()
+}
+
+/*
+运行在linux上的发声命令
+*/
+func customizedOnLinux(content string) {
+ defer func() {
+ if err := recover(); err != nil {
+ slog.Warn("执行发声命令出现错误", slog.Any("是否安装espeak?", err))
+ }
+ }()
+ //espeak "Testing espeak from the Ubuntu 18.04 terminal"
+ cmd := exec.Command("ekho", content)
+ cmd.Run()
+}
+func Customize(content, teller string) {
+ if os.Getenv("QUIET") == "True" {
+ return
+ }
+ switch runtime.GOOS {
+ case "darwin":
+ customizedOnMac(teller, content)
+ case "linux":
+ customizedOnLinux(content)
+ default:
+ slog.Warn("系统问题")
+ }
+}
diff --git a/asmrgay/decoder.go b/asmrgay/decoder.go
new file mode 100644
index 0000000..02136fc
--- /dev/null
+++ b/asmrgay/decoder.go
@@ -0,0 +1,24 @@
+package asmrgay
+
+import (
+ "fmt"
+ "net/url"
+)
+
+func decoder(OriginalUrl string) string {
+ //fmt.Println(OriginalUrl)
+ encodeurl := url.QueryEscape(OriginalUrl)
+ fmt.Println(encodeurl)
+ //decodeurl, err := url.QueryUnescape(OriginalUrl)
+ //if err != nil {
+ // fmt.Println(err)
+ //}
+ return encodeurl
+}
+func encoder(OriginalUrl string) string {
+ decodeurl, err := url.QueryUnescape(OriginalUrl)
+ if err != nil {
+ fmt.Println(err)
+ }
+ return decodeurl
+}
diff --git a/asmrgay/exam.html b/asmrgay/exam.html
new file mode 100644
index 0000000..e69de29
diff --git a/asmrgay/exam.txt b/asmrgay/exam.txt
new file mode 100644
index 0000000..e69de29
diff --git a/asmrgay/fromFile.go b/asmrgay/fromFile.go
new file mode 100644
index 0000000..c1e430b
--- /dev/null
+++ b/asmrgay/fromFile.go
@@ -0,0 +1,139 @@
+package asmrgay
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "path"
+ "processAll/model"
+ "processAll/soup"
+ "processAll/util"
+ "strconv"
+ "strings"
+)
+
+/*
+读取html文件
+*/
+func ReadHtml(fname string) string {
+ file, err := os.ReadFile(fname)
+ if err != nil {
+ return ""
+ }
+ return string(file)
+}
+
+/*
+解析html文件
+*/
+func Parse(html string) []string {
+ var srcs []string
+ doc := soup.HTMLParse(html)
+ //prefix := "https://asmr.121231234.xyz/asmr/中文音声/步非烟/第二季/301-400/"
+ middle := doc.FindAll("a")[0].Attrs()["href"]
+ middle = strings.TrimSuffix(middle, path.Base(middle))
+ slog.Info(middle, slog.String("url", middle))
+ middle = strings.Replace(middle, " ", "", -1)
+ prefix := strings.Join([]string{"https://asmr.121231234.xyz", middle}, "")
+ //prefix := "https://asmr.121231234.xyz/asmr/中文音声/P站ASMR录音/"
+ suffix := "?sign=TsXbjWWtYVpdfY96jDbkqmaJEhzMQYWGzLLKu9vL5YI=:1772008546"
+ imgs := doc.FindAll("p")
+ for _, img := range imgs {
+ src := img.Attrs()["title"]
+ if strings.Contains(src, "mp3") {
+ src = strings.Join([]string{prefix, src, suffix}, "")
+ fmt.Println(src)
+ srcs = append(srcs, src)
+ }
+
+ }
+ return srcs
+}
+
+/*
+解析html网站
+*/
+func ParseWeb(html string) (string, []string) {
+
+ var srcs []string
+ doc := soup.HTMLParse(html)
+ div := doc.Find("div", "class", "ql-editor")
+ title := div.Find("h1").Text()
+ title = strings.Replace(title, "\n", "", -1)
+ title = strings.Replace(title, "[", "", -1)
+ title = strings.Replace(title, "]", "", -1)
+ title = strings.Replace(title, "\u00A0", "", -1)
+ title = strings.Replace(title, ",", "", -1)
+ title = strings.Replace(title, "《", "", -1)
+ title = strings.Replace(title, "》", "", -1)
+ title = strings.Replace(title, " ", "", -1)
+ title = strings.Replace(title, "[", "", -1)
+ title = strings.Replace(title, "]", "", -1)
+ title = strings.Replace(title, "(", "", -1)
+ title = strings.Replace(title, ")", "", -1)
+ for strings.Contains(title, " ") {
+ title = strings.Replace(title, " ", "", -1)
+ }
+ slog.Debug("获取并替换标题", slog.String("标题", title))
+ imgs := doc.FindAll("img")
+ for _, img := range imgs {
+ src := img.Attrs()["src"]
+ if strings.Contains(src, "=") {
+ src = strings.Split(src, "=")[1]
+ }
+ src = strings.Replace(src, "https://", "http://", -1)
+ srcs = append(srcs, src)
+ }
+ return title, srcs
+}
+
+/*
+使用wget下载
+*/
+func DownloadSrc(title string, images []string) {
+ total := len(images)
+ success := 0
+ for index, image := range images {
+ fname := strings.Join([]string{strconv.Itoa(index + 1), "jpg"}, ".")
+ if strings.HasPrefix(image, "/file") {
+ image = strings.Join([]string{"http://telegra.ph", image}, "")
+ }
+
+ //title = strings.Replace(title, "", "", -1)
+
+ os.Mkdir(title, 0777)
+ dir := strings.Join([]string{title, fname}, string(os.PathSeparator))
+ //"wget -e use_proxy=yes -e http_proxy=127.0.0.1:8889 -e https_proxy=127.0.0.1:8889"
+ cmd := exec.Command("wget", "-e", "use_proxy=yes", "-e", "http_proxy=127.0.0.1:8889", "-e", "https_proxy=127.0.0.1:8889", "--no-check-certificate", "--tries=0", "--continue", "-O", dir, image)
+ slog.Debug("wget下载前", slog.String("生成的命令", fmt.Sprint(cmd)))
+ shName := strings.Join([]string{title, "sh"}, ".")
+ file, err := os.OpenFile(shName, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0777)
+ if err != nil {
+ return
+ }
+ defer file.Close()
+ file.WriteString(fmt.Sprint(cmd))
+ file.WriteString("\n")
+
+ //output, err := cmd.CombinedOutput()
+ err = util.ExecCommand(cmd)
+ if err != nil {
+ slog.Warn("跳过", slog.Any("当前下载文件出错", err), slog.String("文件名", fname))
+ fmt.Printf("出错的命令\n%s\n", cmd)
+ continue
+ } else {
+ //slog.Debug("下载命令结束", slog.String("命令返回", string(output)))
+ success++
+ }
+
+ one := model.Telegraph{
+ Name: fname,
+ Url: image,
+ Shell: fmt.Sprint(cmd),
+ }
+ go one.InsertOne()
+
+ }
+ slog.Info("全部下载完毕", slog.Int("共有文件", total), slog.Int("成功下载", success))
+}
diff --git a/asmrgay/readme.md b/asmrgay/readme.md
new file mode 100644
index 0000000..5446bc3
--- /dev/null
+++ b/asmrgay/readme.md
@@ -0,0 +1 @@
+https://www.asmrgay.com/d/asmr/%E4%B8%AD%E6%96%87%E9%9F%B3%E5%A3%B0/%E6%9D%8F%E5%90%A7%E9%AA%9A%E9%BA%A6/%E9%AA%9A%E9%BA%A6/034%E3%80%8A%E5%A5%B3%E6%B5%81%E6%B0%93%E3%80%8B
\ No newline at end of file
diff --git a/asmrgay/unit_test.go b/asmrgay/unit_test.go
new file mode 100644
index 0000000..fd2d447
--- /dev/null
+++ b/asmrgay/unit_test.go
@@ -0,0 +1,23 @@
+package asmrgay
+
+import (
+ "processAll/util"
+ "testing"
+)
+
+func TestDecoder(t *testing.T) {
+ ans := decoder("http://www.baidu.com/s?wd=自由度")
+ t.Log(ans)
+}
+func TestUnit(t *testing.T) {
+ html := ReadHtml("C:\\Users\\zen\\go\\src\\ProcessAVI\\asmrgay\\exam.html")
+ srcs := Parse(html)
+ //for _, src := range srcs {
+ // t.Log(src)
+ //}
+ util.WriteByLine("C:\\Users\\zen\\go\\src\\ProcessAVI\\asmrgay\\exam.txt", srcs)
+}
+func TestEncoder(t *testing.T) {
+ ret := encoder("https://www.asmrgay.com/d/asmr/%E4%B8%AD%E6%96%87%E9%9F%B3%E5%A3%B0/%E6%9D%8F%E5%90%A7%E9%AA%9A%E9%BA%A6/%E9%AA%9A%E9%BA%A6/001%E3%80%8A%E5%93%8E%E5%91%80%E6%88%91%E6%93%8D%E5%A4%A7%E8%89%B2%E7%8B%BC%E3%80%8B%E3%80%90%E5%B0%8F%E8%8E%AB%E3%80%91.mp3?sign=TsXbjWWtYVpdfY96jDbkqmaJEhzMQYWGzLLKu9vL5YI=:1772008546\n")
+ t.Log(ret)
+}
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..2aa8655
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+echo 删除旧的日志文件
+find . -type f -name "*.log" -exec rm {} \;
+echo 格式化当前目录下go文件
+find . ! -path "./vendor/*" -name "*.go" -exec gofmt -w {} \;
+echo 删除多余隐藏文件
+find . -name "*DS_Store*" -exec rm {} \;
+echo 编译二进制文件forLinux32
+# CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o process4Linux32 main.go
+echo 编译二进制文件forLinux64
+CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o process4Linux64 main.go
+echo 编译二进制文件forRaspi
+CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o process4Raspi main.go
+echo 编译二进制文件forRaspi64
+# CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o process4Raspi64 main.go
+echo 编译二进制文件forWin32.exe
+# CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o process4Win32.exe main.go
+echo 编译二进制文件forWin64.exe
+CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o process4Win64.exe main.go
+echo 编译二进制文件forMac
+CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o process4Mac main.go
+echo 编译二进制文件forM1
+CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o process4M1 main.go
+echo 编译二进制文件forAndroid
+CGO_ENABLED=0 GOOS=android GOARCH=arm64 go build -o process4Android main.go
+echo 统计代码行数
+find . -type f ! -path "*vendor*" -name "*.go" -print0 | xargs -0 wc -l | tee lines.txt
+echo '转换\r\n为\n'
+# shellcheck disable=SC2038
+find . -type f ! -path "./.git/*" ! -path "./.idea/*" ! -name "build.sh" ! -name "conf.ini" -print0 | xargs -0 dos2unix
+# find . -name "*.go" -exec dos2unix {} \;
\ No newline at end of file
diff --git a/chang/Monitor.go b/chang/Monitor.go
new file mode 100644
index 0000000..08cbb09
--- /dev/null
+++ b/chang/Monitor.go
@@ -0,0 +1,24 @@
+package chang
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "runtime"
+ "time"
+)
+
+func RunNumGoroutineMonitor() {
+ slog.Info(fmt.Sprintf("程序初始协程数量->%d\n", runtime.NumGoroutine()))
+ file, err := os.OpenFile("go.txt", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0777)
+ if err != nil {
+ return
+ }
+ for {
+ select {
+ case <-time.After(1 * time.Second):
+ fmt.Printf("协程数量->%d\n", runtime.NumGoroutine())
+ file.WriteString(fmt.Sprintf("协程数量->%d\n", runtime.NumGoroutine()))
+ }
+ }
+}
diff --git a/chang/example.go b/chang/example.go
new file mode 100644
index 0000000..329ddf5
--- /dev/null
+++ b/chang/example.go
@@ -0,0 +1,76 @@
+package chang
+
+import (
+ "fmt"
+ "sync"
+ "time"
+)
+
+/*
+主程序,之前试过让最后一个协程关闭msg通道,但是最后一个协程并不知道一起并发的另外四个进程是否结束,有可能丢数据
+现在存在的问题
+1.使用延时方法关闭channel具有不确定性,跑真实数据的时候不确定什么时候正常结束退出
+2.使用了据说不推荐的goto LABEL 方法
+*/
+
+//func msaster() {
+// limit := make(chan struct{}, 5)
+// msg := make(chan string, 1)
+// go func() {
+// for i := 0; i < 50; i++ {
+// limit <- struct{}{}
+// go mission(i, msg, limit)
+// }
+// }()
+// //用来保存从所有通道获取的结果
+// var loop = true
+// var finally []string
+// for loop {
+// select {
+// case data := <-msg:
+// finally = append(finally, data)
+// case <-time.After(5 * time.Second):
+// fmt.Println("Timeout: No data received,after 3 second")
+// loop = false
+//
+// fmt.Printf("finally is %v\ntype is %T\n", finally, finally)
+// }
+
+func mission(index int, msg chan string, limit chan struct{}, wg *sync.WaitGroup) {
+ defer wg.Done()
+ // 这是一个长时间任务
+ for i := 0; i < 10; i++ {
+ fmt.Printf("这是第%d个协程的第%d次响应\n", index+1, i)
+ time.Sleep(300 * time.Millisecond)
+ }
+ //实际上msg需要传输经过函数处理的内容
+ msg <- fmt.Sprintf("这是第%d个协程\n", index)
+ <-limit
+ fmt.Printf("从通道中释放一个空结构体\n")
+}
+
+func master() {
+ limit := make(chan struct{}, 5)
+ msg := make(chan string, 50)
+ var wg sync.WaitGroup
+
+ for i := 0; i < 50; i++ {
+ limit <- struct{}{}
+ wg.Add(1)
+ go mission(i, msg, limit, &wg)
+ }
+
+ go func() {
+ wg.Wait()
+ close(msg)
+ }()
+
+ // 用来保存从所有通道获取的结果
+ var finally []string
+ for data := range msg {
+ fmt.Printf("从msg通道获取的msg:%v\n", data)
+ finally = append(finally, data)
+ }
+
+ fmt.Printf("finally is %v\ntype is %T\n", finally, finally)
+}
diff --git a/chang/unit_test.go b/chang/unit_test.go
new file mode 100644
index 0000000..003a30b
--- /dev/null
+++ b/chang/unit_test.go
@@ -0,0 +1,14 @@
+package chang
+
+import "testing"
+
+func init() {
+ go RunNumGoroutineMonitor()
+}
+
+/*
+go test -v -run TestMaster ./
+*/
+func TestMaster(t *testing.T) {
+ master()
+}
diff --git a/conf.ini b/conf.ini
new file mode 100644
index 0000000..d350b70
--- /dev/null
+++ b/conf.ini
@@ -0,0 +1,74 @@
+[main]
+# mission = i&v
+# mission = video
+# mission = OGG
+# mission = audio
+# mission = image
+# mission = rotate
+# mission = resize
+# mission = avmerger
+# mission = extractAAC
+# mission = speedUpAudio
+# mission = speedUpVideo
+# mission = gif
+# mission = encoder
+# mission = telegraph
+# mission = list
+# mission = v2a
+# mission = aspect
+# mission = louder
+mission = ytdlp
+[log]
+level = Debug
+# level = Info
+# level = Warn
+# level = Error
+[pattern]
+image = jpeg;jpg;png;webp;tif
+audio = mp3;m4a;flac;wma;wav;m4a;aac
+video = webm;mkv;m4v;mp4;mov;avi;wmv;ts;rmvb;wma;avi;flv;rmvb;mpg;f4v
+gif = gif;webm
+txt = txt
+[root]
+folder = /Users/zen/git
+image = /Volumes/noname
+audio = /mnt/e/audio
+video = /media/zen/Disk2/video
+gif = /Users/zen/Downloads/Fugtrup collection/archive/webp
+txt = /Users/zen/Downloads/tele
+bilibili = /sdcard/Android/data/tv.danmaku.bili/download
+louder = E:\aac
+[thread]
+threads = 5
+[rotate]
+direction = ToRight
+# direction = ToLeft
+[StartAt]
+time = 08
+[alert]
+quiet = yes
+email = yes
+[email]
+username = 18904892728@163.com
+password = RGFYKHNDDHWTLHMK
+from = 18904892728@163.com
+tos = 1914301892@qq.com;578779391@qq.com;zhangyiming748@outlook.com;zhangyiming748@protonmail.com;zhangyiming7480@qq.com;zhangyiming748@qq.com
+[mysql]
+switch = ons
+user = zen
+passwd = 163453
+database = mydb
+ip = 127.0.0.1
+port = 3306
+[Telegraph]
+links = /Users/zen/github/processAVIWithXorm/telegraph/list.txt
+proxy = http://127.0.0.1:8889
+[speed]
+audition = 70
+# 需要被1整除
+ffmpeg = 1.25
+[yt-dlp]
+saveTo = /mnt/c/Users/zen/Videos
+concurrency = 1
+links = /home/zen/git/processAll/lines.txt
+proxy = 192.168.1.20:8889
diff --git a/console.sql b/console.sql
new file mode 100644
index 0000000..d5b404d
--- /dev/null
+++ b/console.sql
@@ -0,0 +1,14 @@
+select * from avif join task on task.id = avif.task_id;
+select * from task order by create_time desc ;
+select * from avif;
+drop table av,custom,err,file,image,save,telegraph,text,video,audio,avif,task;
+drop table video;
+show tables;
+drop table file;
+
+select * from video order by id desc ;
+select * from task order by id desc ;
+select * from err order by create_time desc ;
+
+SELECT src,dst,create_time FROM history;
+SELECT id,src_name,dst_name,create_time FROM save ORDER BY id DESC;
\ No newline at end of file
diff --git a/constant/constant.go b/constant/constant.go
new file mode 100644
index 0000000..3286288
--- /dev/null
+++ b/constant/constant.go
@@ -0,0 +1,20 @@
+package constant
+
+var (
+ BitRate = map[string]string{
+ "avc": "5000K",
+ "hevc": "1800K",
+ }
+)
+
+const (
+ Type = iota + 1
+ Kilobyte = 1000 * Type
+ Megabyte = 1000 * Kilobyte
+ Gigabyte = 1000 * Megabyte
+ Terabyte = 1000 * Gigabyte
+ Petabyte = 1000 * Terabyte
+ Exabyte = 1000 * Petabyte
+ Zettabyte = 1000 * Exabyte
+ Yottabyte = 1000 * Zettabyte
+)
diff --git a/duplicate/dup.go b/duplicate/dup.go
new file mode 100644
index 0000000..55ad5aa
--- /dev/null
+++ b/duplicate/dup.go
@@ -0,0 +1,22 @@
+package duplicate
+
+import (
+ "log/slog"
+ "processAll/util"
+)
+
+func DuplicateFromFile(fp string) {
+ m := make(map[string]bool)
+ src := util.ReadByLine(fp)
+ dst := make([]string, 0)
+ for _, v := range src {
+ if _, ok := m[v]; ok {
+ slog.Warn("skip", slog.String("文件名", v))
+ continue
+ } else {
+ dst = append(dst, v)
+ m[v] = true
+ }
+ }
+ util.WriteByLine("dup.sh", dst)
+}
diff --git a/duplicate/unit_test.go b/duplicate/unit_test.go
new file mode 100644
index 0000000..6783975
--- /dev/null
+++ b/duplicate/unit_test.go
@@ -0,0 +1,7 @@
+package duplicate
+
+import "testing"
+
+func TestDup(t *testing.T) {
+ DuplicateFromFile("C:\\Users\\zen\\go\\src\\ProcessAVI\\asmrgay\\exam.txt")
+}
diff --git a/ffmpeg.sh b/ffmpeg.sh
new file mode 100644
index 0000000..e5b5844
--- /dev/null
+++ b/ffmpeg.sh
@@ -0,0 +1,22 @@
+#!/bin/zsh
+echo 在不符合golang版本的系统上临时使用批量ffmpeg命令的方法
+# 定义源文件夹和目标文件夹
+source_dir=/data/folder
+target_dir=/data/folder/h265
+# 判断文件夹是否存在
+if [ ! -d $target_dir ]; then
+ # 文件夹不存在,创建文件夹
+ mkdir -p "$target_dir"
+fi
+# 遍历源文件夹中的所有文件
+for file in "$source_dir"/*; do
+ # 获取文件名
+ filename=$(basename "$file")
+ # 构造源文件的完整路径
+ source_file="$source_dir/$filename"
+ echo '源文件: $source_file'
+ # 构造目标文件的完整路径
+ target_file="$target_dir/$filename"
+ echo '目标文件: $target_file'
+ ffmpeg -i "$source_file" -c:v libx265 -c:a aac -ac 1 -tag:v hvc1 "$target_file"
+done
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..d60429c
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,36 @@
+module processAll
+
+go 1.21
+
+require (
+ github.com/go-sql-driver/mysql v1.7.1
+ github.com/klauspost/cpuid/v2 v2.2.6
+ github.com/stretchr/testify v1.8.1
+ github.com/zhangyiming748/filetype v0.0.1
+ github.com/zhangyiming748/mahonia v0.0.1
+ golang.org/x/net v0.19.0
+ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
+ gorm.io/driver/sqlite v1.5.4
+ gorm.io/gorm v1.25.5
+ xorm.io/xorm v1.3.4
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/mattn/go-sqlite3 v1.14.17 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/syndtr/goleveldb v1.0.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ golang.org/x/tools v0.13.0 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ xorm.io/builder v0.3.13 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..7c0f8a9
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,346 @@
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
+gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
+github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
+github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
+github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
+github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
+github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
+github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
+github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
+github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
+github.com/zhangyiming748/filetype v0.0.1 h1:C7a49LHKzqiYAiw/u2lTmPU6gP6Nwb6/sSDBmBMoJ/k=
+github.com/zhangyiming748/filetype v0.0.1/go.mod h1:DFFw06VgsQVSriUKe95IC+HKaY4u6hCCbXJE1L1HNUk=
+github.com/zhangyiming748/mahonia v0.0.1 h1:zsk33+dSWU5D6qNpxN23gyEjIFe8x7r5IbzP2ggAKuI=
+github.com/zhangyiming748/mahonia v0.0.1/go.mod h1:GlWmGjv246VJ+x2Q4t+1tkrss7WEjJcFzqKfWrkk6j4=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
+gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
+gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
+gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
+lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
+modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
+modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
+modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
+modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI=
+modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0=
+modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g=
+modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
+modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
+modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
+modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA=
+modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0=
+modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
+modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
+modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
+modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
+modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
+modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
+modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
+modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
+modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
+modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
+modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
+modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
+modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE=
+modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
+modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
+xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
+xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/xorm v1.3.4 h1:vWFKzR3DhGUDl5b4srhUjhDwjxkZAc4C7BFszpu0swI=
+xorm.io/xorm v1.3.4/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
diff --git a/help.sql b/help.sql
new file mode 100644
index 0000000..c7e3d89
--- /dev/null
+++ b/help.sql
@@ -0,0 +1,9 @@
+SELECT COUNT(*) FROM image;
+SELECT SUM(id) FROM image;
+show tables;
+select * from task;
+select * from avif join task on task.id = avif.task_id;
+select * from task;
+select * from avif;
+drop table av,custom,err,file,image,save,telegraph,text,video,audio,avif,task;
+show tables;
\ No newline at end of file
diff --git a/information/machine.go b/information/machine.go
new file mode 100644
index 0000000..5d3665f
--- /dev/null
+++ b/information/machine.go
@@ -0,0 +1,10 @@
+package information
+
+import (
+ "runtime"
+ "strings"
+)
+
+func GetMachineInfo() string {
+ return strings.Join([]string{runtime.GOOS, runtime.GOARCH}, "/")
+}
diff --git a/lines.txt b/lines.txt
new file mode 100644
index 0000000..9513eef
--- /dev/null
+++ b/lines.txt
@@ -0,0 +1,92 @@
+https://www.pornhub.com/view_video.php?viewkey=649a035061b79
+https://www.pornhub.com/view_video.php?viewkey=ph5f92a33489986
+https://www.pornhub.com/view_video.php?viewkey=ph5eb45b6b4486d
+https://www.pornhub.com/view_video.php?viewkey=ph6268b33c54eef
+https://www.pornhub.com/view_video.php?viewkey=ph62cf71eea1024
+https://www.pornhub.com/view_video.php?viewkey=ph6064fb466b949
+https://www.pornhub.com/view_video.php?viewkey=ph5fdd2fb789cde
+https://www.pornhub.com/view_video.php?viewkey=64eb63812fd94
+https://www.pornhub.com/view_video.php?viewkey=64177de74c731
+https://www.pornhub.com/view_video.php?viewkey=63f6419806928
+https://www.pornhub.com/view_video.php?viewkey=ph631be5df18e35
+https://www.pornhub.com/view_video.php?viewkey=ph60f11e5c50738
+https://www.pornhub.com/view_video.php?viewkey=ph5fca22d1ac073
+https://www.pornhub.com/view_video.php?viewkey=ph60f9409402d2f
+https://www.pornhub.com/view_video.php?viewkey=ph5c7d8f0bcd297
+https://www.pornhub.com/view_video.php?viewkey=63d570689bf65
+https://www.pornhub.com/view_video.php?viewkey=ph59b6ae5b20957
+https://www.pornhub.com/view_video.php?viewkey=ph5a1c70a98951c
+https://www.pornhub.com/view_video.php?viewkey=ph58c693f593fd3
+https://www.pornhub.com/view_video.php?viewkey=ph5697885a148aa
+https://www.pornhub.com/view_video.php?viewkey=ph5e7488cdd3574
+https://www.pornhub.com/view_video.php?viewkey=355003756
+https://www.pornhub.com/view_video.php?viewkey=ph55ea2f49e7a79
+https://www.pornhub.com/view_video.php?viewkey=ph616d9c3627335
+https://www.pornhub.com/view_video.php?viewkey=ph6270f695802f5
+https://www.pornhub.com/view_video.php?viewkey=ph5b5652acf19c8
+https://www.pornhub.com/view_video.php?viewkey=ph5d4684456c7d5
+https://www.pornhub.com/view_video.php?viewkey=ph5b35621663d02
+https://www.pornhub.com/view_video.php?viewkey=ph5ad90dafdd014
+https://www.pornhub.com/view_video.php?viewkey=ph5e8fb43001ffb
+https://www.pornhub.com/view_video.php?viewkey=ph5f5f7ad2b5469
+https://www.pornhub.com/view_video.php?viewkey=ph5c2fb5b509ded
+https://www.pornhub.com/view_video.php?viewkey=644174f9c69fd
+https://www.pornhub.com/view_video.php?viewkey=ph5b92f3d1a5408
+https://www.pornhub.com/view_video.php?viewkey=ph5f9b4a654dc66
+https://www.pornhub.com/view_video.php?viewkey=ph5d8175a3d1a0e
+https://www.pornhub.com/view_video.php?viewkey=ph5edeb97ac8c2b
+https://www.pornhub.com/view_video.php?viewkey=ph5ed022230c961
+https://www.pornhub.com/view_video.php?viewkey=ph5e98d10b5ee39
+https://www.pornhub.com/view_video.php?viewkey=ph5dbd204e9047a
+https://www.pornhub.com/view_video.php?viewkey=ph5d5c8639206a2
+https://www.pornhub.com/view_video.php?viewkey=ph5d51f12becd6e
+https://www.pornhub.com/view_video.php?viewkey=ph5d4684456c7d5
+https://www.pornhub.com/view_video.php?viewkey=ph5d0547709f54a
+https://www.pornhub.com/view_video.php?viewkey=ph5ce45257b7fa5
+https://www.pornhub.com/view_video.php?viewkey=ph5cc5ddb473a32
+https://www.pornhub.com/view_video.php?viewkey=ph5c993a21d74f5
+https://www.pornhub.com/view_video.php?viewkey=ph5cabe7c3cb3f9
+https://www.pornhub.com/view_video.php?viewkey=ph5c5b54dc38739
+https://www.pornhub.com/view_video.php?viewkey=ph5c6b3de690ed3
+https://www.pornhub.com/view_video.php?viewkey=ph5c4911dcc533a
+https://www.pornhub.com/view_video.php?viewkey=ph5c39013c63159
+https://www.pornhub.com/view_video.php?viewkey=ph5c2fd8896e35e
+https://www.pornhub.com/view_video.php?viewkey=ph5be22788e8680
+https://www.pornhub.com/view_video.php?viewkey=ph5bdce5525fa20
+https://www.pornhub.com/view_video.php?viewkey=ph5bda3105380ab
+https://www.pornhub.com/view_video.php?viewkey=ph5bd1633e1e18e
+https://www.pornhub.com/view_video.php?viewkey=ph5bb7a6f9aa7b4
+https://www.pornhub.com/view_video.php?viewkey=ph5bb7a5d6b7be6
+https://www.pornhub.com/view_video.php?viewkey=ph5b92f3d1a5408
+https://www.pornhub.com/view_video.php?viewkey=ph5b92f2c2c2567
+https://www.pornhub.com/view_video.php?viewkey=ph5b92f2c2c2567
+https://www.pornhub.com/view_video.php?viewkey=ph5b5f9a5593a97
+https://www.pornhub.com/view_video.php?viewkey=ph5b74defff36bb
+https://www.pornhub.com/view_video.php?viewkey=ph5b5f9b76c45d8
+https://www.pornhub.com/view_video.php?viewkey=ph5b44970ca8e8a
+https://www.pornhub.com/view_video.php?viewkey=ph5b51127dcc97c
+https://www.pornhub.com/view_video.php?viewkey=ph5b35667b1071d
+https://www.pornhub.com/view_video.php?viewkey=ph5b3568eb2afc8
+https://www.pornhub.com/view_video.php?viewkey=ph5b35621663d02
+https://www.pornhub.com/view_video.php?viewkey=ph5b2a71dec8589
+https://www.pornhub.com/view_video.php?viewkey=ph5b0752903742f
+https://www.pornhub.com/view_video.php?viewkey=ph5b074f50b5ad7
+https://www.pornhub.com/view_video.php?viewkey=ph5af0d676cf60f
+https://www.pornhub.com/view_video.php?viewkey=ph5ad510856d384
+https://www.pornhub.com/view_video.php?viewkey=ph5acd5d1e2a004
+https://www.pornhub.com/view_video.php?viewkey=ph5a75229edbb19
+https://www.pornhub.com/view_video.php?viewkey=ph59f7e1d160615
+https://www.pornhub.com/view_video.php?viewkey=ph59f0e5f49fdd0
+https://www.pornhub.com/view_video.php?viewkey=ph59ee95da5e0aa
+https://www.pornhub.com/view_video.php?viewkey=ph59ea7394cccef
+https://www.pornhub.com/view_video.php?viewkey=ph574507c384724
+https://www.pornhub.com/view_video.php?viewkey=1995962564
+https://www.pornhub.com/view_video.php?viewkey=64182468614e3
+https://www.pornhub.com/view_video.php?viewkey=6570ed3d7685f
+https://www.pornhub.com/view_video.php?viewkey=654a08d605bab
+https://www.pornhub.com/view_video.php?viewkey=653b6eb3948fc
+https://www.pornhub.com/view_video.php?viewkey=65675b73ac2f3
+https://www.pornhub.com/view_video.php?viewkey=65606eb0268b9
+https://www.pornhub.com/view_video.php?viewkey=ph6348729c1d955
+https://www.pornhub.com/view_video.php?viewkey=ph63869caec9e86
+https://www.pornhub.com/view_video.php?viewkey=ph63735a0e31720
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..f19ba7c
--- /dev/null
+++ b/main.go
@@ -0,0 +1,363 @@
+package main
+
+import (
+ "fmt"
+ "github.com/klauspost/cpuid/v2"
+ "io"
+ "log/slog"
+ "os"
+ "os/exec"
+ "path"
+ "processAll/GBK2UTF8"
+ "processAll/GetAllFolder"
+ "processAll/GetFileInfo"
+ "processAll/alert"
+ "processAll/merge"
+ "processAll/processAudio"
+ "processAll/processImage"
+ "processAll/processVideo"
+ "processAll/sql"
+ "processAll/telegraph"
+ "processAll/util"
+ "processAll/ytdlp"
+ "runtime"
+ "strings"
+ "time"
+)
+
+// todo 除图片流程添加数据库代码
+
+type summarize struct {
+ Files int64 // 总处理文件数
+ SuccessAudios int64 // 总成功的音频数
+ SuccessVideo int64 // 总成功的视频数
+ SuccessImage int64 // 总成功的图片数
+ Failure int64 // 总失败数
+}
+
+/*
+程序运行前选择日志和数据库配置
+*/
+func init() {
+ setLog()
+ util.SetRoot()
+ sql.SetEngine()
+}
+
+func main() {
+ slog.Info("当前的机器信息", slog.String("CPU名称", cpuid.CPU.BrandName), slog.Int("物理核心数", cpuid.CPU.PhysicalCores), slog.Int("每个核心的线程数", cpuid.CPU.ThreadsPerCore), slog.Int("逻辑核心数", cpuid.CPU.LogicalCores), slog.String("品牌", fmt.Sprintf("%v", cpuid.CPU.VendorID)), slog.Int64("频率", cpuid.CPU.Hz))
+ if len(os.Args) > 1 {
+ //go run main.go <任务> <参数1> <参数2> ...
+ slog.Info("切换手动模式")
+ opt := slog.HandlerOptions{ // 自定义option
+ AddSource: true,
+ Level: slog.LevelDebug, // slog 默认日志级别是 info
+ }
+ file := "Process.log"
+ logf, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777)
+ if err != nil {
+ panic(err)
+ }
+ logger := slog.New(slog.NewJSONHandler(io.MultiWriter(logf, os.Stdout), &opt))
+ slog.SetDefault(logger)
+ for i, v := range os.Args {
+ fmt.Printf("第%d个参数%s\n", i+1, v)
+ }
+ m := make(map[string]string)
+ m["bilibili"] = "/sdcard/Android/data/tv.danmaku.bili/download"
+ m["bilibilihd"] = "/sdcard/Android/data/tv.danmaku.bilibilihd/download"
+ if len(os.Args) >= 3 {
+ switch os.Args[1] {
+ case "merge": // main merge bilibilihd
+ /*
+ #!/data/data/com.termux/files/usr/bin/bash
+ echo 合成bilibili缓存
+ sudo /data/data/com.termux/files/home/ProcessAVI/main merge bilibilihd
+ */
+ fmt.Println("main merge bilibilihd")
+ merge.Merge(m[os.Args[2]])
+ slog.Debug("", slog.String("运行目录", m[os.Args[2]]))
+ os.Exit(0)
+ }
+ } else {
+ slog.Warn("参数个数错误 退出", slog.String("例如", "go run main.go bilibilihd merge"))
+ os.Exit(0)
+ }
+
+ }
+ slog.Info("start!", slog.String("程序运行的根目录", util.GetRoot()))
+ defer final()
+ mission := util.GetVal("main", "mission")
+ var (
+ root string
+ pattern string
+ threads string
+ direction string
+ )
+ staterOn := util.GetVal("StartAt", "time")
+ if level := util.GetVal("log", "level"); level != "Debug" {
+ // Debug 模式下不等待开始运行时间
+ startOn(staterOn)
+ }
+ start := time.Now()
+ end := time.Now()
+ if quiet := util.GetVal("alert", "quiet"); quiet == "yes" {
+ os.Setenv("QUIET", "True")
+ slog.Debug("静音模式")
+ }
+ defer func() {
+ if email := util.GetVal("alert", "email"); email == "yes" {
+ slog.Debug("发送任务完成邮件")
+ sendEmail(start, end)
+ }
+ }()
+ util.ExitAfterRun()
+ switch mission {
+ case "OGG":
+ pattern = util.GetVal("pattern", "audio")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ root = util.GetVal("root", "audio")
+ slog.Debug("开始音频处理进程", slog.String("根目录", root), slog.String("pattern", pattern), slog.String("进程数", threads))
+ processAudio.AllAudios2OGG(root, pattern)
+ case "i&v":
+ {
+ pattern = util.GetVal("pattern", "video")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ root = util.GetVal("root", "video")
+ threads = util.GetVal("thread", "threads")
+ slog.Debug("开始视频处理进程", slog.String("根目录", root), slog.String("pattern", pattern), slog.String("进程数", threads))
+ processVideo.ProcessAllVideos2H265(root, pattern, threads)
+ }
+ {
+ pattern = util.GetVal("pattern", "image")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ root = util.GetVal("root", "image")
+ threads = util.GetVal("thread", "threads")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ slog.Debug("开始图片处理进程", slog.String("根目录", root), slog.String("pattern", pattern), slog.String("进程数", threads))
+ processImage.ProcessAllImages(root, pattern, threads)
+ }
+ case "video":
+ pattern = util.GetVal("pattern", "video")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ root = util.GetVal("root", "video")
+ threads = util.GetVal("thread", "threads")
+ slog.Debug("开始视频处理进程", slog.String("根目录", root), slog.String("pattern", pattern), slog.String("进程数", threads))
+ processVideo.ProcessAllVideos2H265(root, pattern, threads)
+ case "audio":
+ pattern = util.GetVal("pattern", "audio")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ root = util.GetVal("root", "audio")
+ slog.Debug("开始音频处理进程", slog.String("根目录", root), slog.String("pattern", pattern), slog.String("进程数", threads))
+ processAudio.AllAudios2AAC(root, pattern)
+ case "image":
+ pattern = util.GetVal("pattern", "image")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ root = util.GetVal("root", "image")
+ threads = util.GetVal("thread", "threads")
+ slog.Debug("开始图片处理进程", slog.String("根目录", root), slog.String("pattern", pattern), slog.String("进程数", threads))
+ processImage.ProcessAllImages(root, pattern, threads)
+ case "rotate":
+ pattern = util.GetVal("pattern", "video")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ root = util.GetVal("root", "video")
+ threads = util.GetVal("thread", "threads")
+ direction = util.GetVal("rotate", "direction")
+ slog.Debug("开始旋转视频处理进程", slog.String("根目录", root), slog.String("pattern", pattern), slog.String("进程数", threads), slog.String("方向", direction))
+ processVideo.RotateAllVideos(root, pattern, direction, threads)
+ case "resize":
+ pattern = util.GetVal("pattern", "video")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ root = util.GetVal("root", "video")
+ threads = util.GetVal("thread", "threads")
+ slog.Debug("开始缩小视频处理进程", slog.String("根目录", root), slog.String("pattern", pattern), slog.String("进程数", threads))
+ processVideo.ResizeAllVideos(root, pattern, threads)
+ case "avmerger":
+ root = util.GetVal("root", "bilibili")
+ slog.Debug("开始合并哔哩哔哩进程", slog.String("根目录", root))
+ merge.Merge(root)
+ case "extractAAC":
+ root = util.GetVal("root", "bilibili")
+ slog.Debug("开始提取哔哩哔哩音频进程", slog.String("根目录", root))
+ merge.ExtractAAC(root)
+ case "speedUpAudio":
+ root = util.GetVal("root", "speedUp")
+ pattern = util.GetVal("pattern", "audio")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ speed := util.GetVal("speed", "audition")
+ processAudio.SpeedUpAllAudios(root, pattern, speed)
+ slog.Debug("开始有声小说加速处理", slog.String("根目录", root))
+ case "speedUpVideo":
+ root = util.GetVal("root", "video")
+ pattern = util.GetVal("pattern", "video")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ speed := util.GetVal("speed", "ffmpeg")
+ processVideo.SpeedUpAllVideos(root, pattern, speed)
+ slog.Debug("开始音视频同步加速处理", slog.String("根目录", root))
+ case "txt":
+ root = util.GetVal("root", "txt")
+ pattern = util.GetVal("pattern", "txt")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ GBK2UTF8.AllGBKs2UTF8(root, pattern)
+ case "telegraph":
+ links := util.GetVal("Telegraph", "links")
+ urls := util.ReadByLine(links)
+ slog.Debug(fmt.Sprint(urls))
+ for _, uri := range urls {
+ telegraph.GetAndDownload(uri)
+ }
+ _, filename, _, _ := runtime.Caller(0)
+ processImage.ProcessAllImages(path.Dir(filename), "jpg", "0")
+ case "list":
+ root = util.GetVal("root", "folder")
+ for index, folder := range GetAllFolder.List(root) {
+ for idx, file := range GetFileInfo.GetAllFiles(folder) {
+ fmt.Printf("读取第%d/%d个文件夹的第%d/%d个文件:%s\n", index+1, len(folder), idx+1, len(file), file)
+ }
+ }
+ case "v2a":
+ root = util.GetVal("root", "video")
+ pattern = util.GetVal("pattern", "video")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ threads = util.GetVal("thread", "threads")
+ processVideo.AllVideos2Audio(root, pattern, threads)
+ case "aspect":
+ root = util.GetVal("root", "video")
+ pattern = util.GetVal("pattern", "video")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ threads = util.GetVal("thread", "threads")
+ processVideo.FixAll4x3s(root, pattern, threads)
+ case "louder":
+ root = util.GetVal("root", "louder")
+ pattern = util.GetVal("pattern", "audio")
+ pattern = strings.Join([]string{pattern, strings.ToUpper(pattern)}, ";")
+ processAudio.LouderAllAudios(root, pattern)
+ slog.Debug("开始有声小说增大电平处理", slog.String("根目录", root))
+ case "ytdlp":
+ go runNumGoroutineMonitor()
+ list := util.GetVal("yt-dlp", "links")
+ ytdlp.Ytdlp(list)
+ default:
+ fmt.Println("参数错误")
+ }
+ end = time.Now()
+}
+
+/*
+程序结束后发送电子邮件通知,可选追加内容
+*/
+func sendEmail(start, end time.Time, ss ...string) {
+ i := new(alert.Info)
+ i.SetUsername(util.GetVal("email", "username"))
+ i.SetPassword(util.GetVal("email", "password"))
+ i.SetTo(strings.Split(util.GetVal("email", "tos"), ";"))
+ i.SetFrom(util.GetVal("email", "from"))
+ i.SetHost(alert.NetEase.SMTP)
+ i.SetPort(alert.NetEase.SMTPProt)
+ i.SetSubject(strings.Join([]string{"AllInOne", util.GetVal("main", "mission"), "任务完成"}, ":"))
+ text := strings.Join([]string{start.Format("任务开始时间 2006年01月02日 15:04:05"), end.Format("任务结束时间 2006年01月02日 15:04:05"), fmt.Sprintf("任务用时%.3f分", end.Sub(start).Minutes())}, "
")
+ i.SetText(text)
+ for _, s := range ss {
+ i.AppendText(s)
+ }
+ alert.Send(i)
+}
+
+/*
+等待程序开始的循环函数
+*/
+func startOn(t string) {
+ for true {
+ now := time.Now().Local().Format("15")
+ if t == now {
+ return
+ } else {
+ slog.Warn("still alive", slog.Any("time", now), slog.String("target", t))
+ time.Sleep(30 * time.Minute)
+ }
+ }
+}
+
+/*
+初始化数据库和数据表
+*/
+
+/*
+设置程序运行的日志等级
+*/
+func setLog() {
+ var opt slog.HandlerOptions
+ level := util.GetVal("log", "level")
+ switch level {
+ case "Debug":
+ opt = slog.HandlerOptions{ // 自定义option
+ AddSource: true,
+ Level: slog.LevelDebug, // slog 默认日志级别是 info
+ }
+ case "Info":
+ opt = slog.HandlerOptions{ // 自定义option
+ AddSource: true,
+ Level: slog.LevelInfo, // slog 默认日志级别是 info
+ }
+ case "Warn":
+ opt = slog.HandlerOptions{ // 自定义option
+ AddSource: true,
+ Level: slog.LevelWarn, // slog 默认日志级别是 info
+ }
+ case "Err":
+ opt = slog.HandlerOptions{ // 自定义option
+ AddSource: true,
+ Level: slog.LevelError, // slog 默认日志级别是 info
+ }
+ default:
+ slog.Warn("需要正确设置环境变量 Debug,Info,Warn or Err")
+ slog.Debug("默认使用Debug等级")
+ opt = slog.HandlerOptions{ // 自定义option
+ AddSource: true,
+ Level: slog.LevelDebug, // slog 默认日志级别是 info
+ }
+ }
+ file := "Process.log"
+ logf, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0770)
+ if err != nil {
+ panic(err)
+ }
+ logger := slog.New(slog.NewJSONHandler(io.MultiWriter(logf, os.Stdout), &opt))
+ slog.SetDefault(logger)
+}
+
+/*
+程序运行结束后生成新的二进制可执行文件
+*/
+func final() {
+ if runtime.GOOS == "darwin" {
+ slog.Info("M1重新生成预编译文件")
+ if out, err := exec.Command("zsh", "-c", "/Users/zen/git/ProcessAVI/build.sh").CombinedOutput(); err != nil {
+ slog.Warn("程序结束后重新编译失败")
+ } else {
+ slog.Debug("编译新版本二进制文件", slog.String("输出", string(out)))
+ }
+ }
+ if runtime.GOOS == "linux" {
+ slog.Info("linux64重新生成预编译文件")
+ if out, err := exec.Command("bash", "-c", "/home/zen/git/ProcessAVI/build.sh").CombinedOutput(); err != nil {
+ slog.Warn("程序结束后重新编译失败")
+ } else {
+ slog.Debug("编译新版本二进制文件", slog.String("输出", string(out)))
+ }
+ }
+
+}
+
+/*
+runNumGoroutineMonitor 协程数量监控
+*/
+func runNumGoroutineMonitor() {
+ slog.Info(fmt.Sprintf("程序初始协程数量->%d\n", runtime.NumGoroutine()))
+ for {
+ select {
+ case <-time.After(10 * time.Second):
+ fmt.Printf("协程数量->%d\n", runtime.NumGoroutine())
+ }
+ }
+}
diff --git a/mediaInfo/Audio.go b/mediaInfo/Audio.go
new file mode 100644
index 0000000..49afa2b
--- /dev/null
+++ b/mediaInfo/Audio.go
@@ -0,0 +1,111 @@
+package mediaInfo
+
+import (
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "os/exec"
+ "runtime"
+ "strconv"
+)
+
+type AudioMedia struct {
+ CreatingLibrary struct {
+ Name string `json:"Name"`
+ Version string `json:"Version"`
+ Url string `json:"Url"`
+ } `json:"CreatingLibrary"`
+ Media struct {
+ Ref string `json:"@ref"`
+ Track []struct {
+ Type string `json:"@type"` // General Audio
+ AudioCount string `json:"AudioCount,omitempty"` // 音轨数
+ FileExtension string `json:"FileExtension,omitempty"` // 扩展名
+ Format string `json:"Format,omitempty"` // 容器格式 MPEG-4 AAC
+ FileSize string `json:"FileSize,omitempty"` // 文件大小
+ Duration string `json:"Duration,omitempty"` // 持续时间 秒
+ OverallBitRate string `json:"OverallBitRate,omitempty"` // 总比特率
+ BitRate string `json:"BitRate,omitempty"` // 比特率
+ Channels string `json:"Channels,omitempty"` // 声道数
+ FrameRate string `json:"FrameRate,omitempty"` // 帧率
+ FrameCount string `json:"FrameCount,omitempty"` // 帧数
+ } `json:"track"`
+ } `json:"media"`
+}
+
+type AudioInfo struct {
+ Type string `json:"@type"` // General Audio
+ AudioCount string `json:"AudioCount,omitempty"` // 音轨数
+ FileExtension string `json:"FileExtension,omitempty"` // 扩展名
+ Format string `json:"Format,omitempty"` // 容器格式 MPEG-4 AAC
+ FileSize uint64 `json:"FileSize,omitempty"` // 文件大小
+ Duration float64 `json:"Duration,omitempty"` // 持续时间 秒
+ OverallBitRate string `json:"OverallBitRate,omitempty"` // 总比特率
+ BitRate string `json:"BitRate,omitempty"` // 比特率
+ Channels int `json:"Channels,omitempty"` // 声道数
+ FrameRate float64 `json:"FrameRate,omitempty"` // 帧率
+ FrameCount int `json:"FrameCount,omitempty"` // 帧数
+}
+
+/*
+获取音频文件的mediainfo信息
+*/
+func GetAudioMedia(absPath string) AudioInfo {
+ var am AudioMedia
+ cmd := exec.Command("mediainfo", absPath, "--Output=JSON")
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("C:/Program Files/MediaInfo/MediaInfo.exe", absPath, "--Output=JSON")
+ }
+ slog.Debug("生成的命令", slog.String("命令原文", fmt.Sprint(cmd)))
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ slog.Warn("运行mediainfo命令", slog.String("命令原文", fmt.Sprint(cmd)), slog.Any("产生的错误", err))
+ }
+ if err = json.Unmarshal(output, &am); err != nil {
+ slog.Warn("解析json", slog.Any("产生的错误", err))
+ } else {
+ slog.Debug("解析json成功", slog.Any("解析后的json", am))
+ }
+ return AudioMedia2Info(am)
+}
+
+func AudioMedia2Info(am AudioMedia) AudioInfo {
+ ai := new(AudioInfo)
+ ai.Type = "Audio"
+ for _, kind := range am.Media.Track {
+ if kind.Type == "General" {
+ ai.AudioCount = kind.AudioCount
+ ai.FileExtension = kind.FileExtension
+ FileSize, err := strconv.ParseUint(kind.FileSize, 10, 64)
+ if err != nil {
+ slog.Warn("文件大小转换错误", slog.String("原始数值", kind.FileSize))
+ } else {
+ ai.FileSize = FileSize
+ }
+ if Duration, err := strconv.ParseFloat(kind.Duration, 64); err != nil {
+ slog.Warn("文件时长转换错误", slog.String("原始数值", kind.Duration))
+ } else {
+ ai.Duration = Duration
+ }
+ }
+ if kind.Type == "Audio" {
+ ai.Format = kind.Format
+ if FrameRate, err := strconv.ParseFloat(kind.FrameRate, 64); err != nil {
+ slog.Warn("音频帧率转换错误")
+ } else {
+ ai.FrameRate = FrameRate
+ }
+ if FrameCount, err := strconv.Atoi(kind.FrameCount); err != nil {
+ slog.Warn("音频帧数转换错误")
+ } else {
+ ai.FrameCount = FrameCount
+ }
+ if Channels, err := strconv.Atoi(kind.Channels); err != nil {
+ slog.Warn("音频声道数转换错误")
+ } else {
+ ai.Channels = Channels
+ }
+ }
+ }
+ return *ai
+}
diff --git a/mediaInfo/General.go b/mediaInfo/General.go
new file mode 100644
index 0000000..687aea8
--- /dev/null
+++ b/mediaInfo/General.go
@@ -0,0 +1,73 @@
+package mediaInfo
+
+import (
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "os/exec"
+ "runtime"
+ "strconv"
+)
+
+type GeneralMedia struct {
+ CreatingLibrary struct {
+ Name string `json:"Name"`
+ Version string `json:"Version"`
+ Url string `json:"Url"`
+ } `json:"CreatingLibrary"`
+ Media struct {
+ Ref string `json:"@ref"`
+ Track []struct {
+ Type string `json:"@type"` // General
+ FileExtension string `json:"FileExtension,omitempty"` // 文件扩展名
+ FileSize string `json:"FileSize,omitempty"` // 文件大小 字节
+ StreamSize string `json:"StreamSize,omitempty"`
+ FileModifiedDate string `json:"FileModifiedDate,omitempty"` // 文件最后修改时间
+ FileModifiedDateLocal string `json:"FileModifiedDateLocal,omitempty"` // 文件最后修改本地时间
+ } `json:"track"`
+ } `json:"media"`
+}
+type GeneralInfo struct {
+ Type string `json:"@type"` // General
+ FileExtension string `json:"FileExtension,omitempty"` // 扩展名
+ FileSize uint64 `json:"FileSize,omitempty"` // 文件大小
+}
+
+/*
+获取其他文件的mediainfo信息
+*/
+func GetGeneralMedia(absPath string) GeneralInfo {
+ var gm GeneralMedia
+ cmd := exec.Command("mediainfo", absPath, "--Output=JSON")
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("C:/Program Files/MediaInfo/MediaInfo.exe", absPath, "--Output=JSON")
+ }
+ slog.Debug("生成的命令", slog.String("命令原文", fmt.Sprint(cmd)))
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ slog.Warn("运行mediainfo命令", slog.String("命令原文", fmt.Sprint(cmd)), slog.Any("产生的错误", err))
+ }
+ if err = json.Unmarshal(output, &gm); err != nil {
+ slog.Warn("解析json", slog.Any("产生的错误", err))
+ } else {
+ slog.Debug("解析json成功", slog.Any("解析后的json", gm))
+ }
+ return GeneralMedia2Info(gm)
+}
+
+func GeneralMedia2Info(gm GeneralMedia) GeneralInfo {
+ gi := new(GeneralInfo)
+ gi.Type = "General"
+ for _, kind := range gm.Media.Track {
+ if kind.Type == "General" {
+ gi.FileExtension = kind.FileExtension
+ FileSize, err := strconv.ParseUint(kind.FileSize, 10, 64)
+ if err != nil {
+ slog.Warn("文件大小转换错误", slog.String("原始数值", kind.FileSize))
+ } else {
+ gi.FileSize = FileSize
+ }
+ }
+ }
+ return *gi
+}
diff --git a/mediaInfo/Image.go b/mediaInfo/Image.go
new file mode 100644
index 0000000..70b8e79
--- /dev/null
+++ b/mediaInfo/Image.go
@@ -0,0 +1,96 @@
+package mediaInfo
+
+import (
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "os/exec"
+ "runtime"
+ "strconv"
+)
+
+type ImageMedia struct {
+ CreatingLibrary struct {
+ Name string `json:"Name"`
+ Version string `json:"Version"`
+ Url string `json:"Url"`
+ } `json:"CreatingLibrary"`
+ Media struct {
+ Ref string `json:"@ref"`
+ Track []struct {
+ Type string `json:"@type"` // General Image
+ ImageCount string `json:"ImageCount,omitempty"` // 图片数
+ FileExtension string `json:"FileExtension,omitempty"` // jpg
+ Format string `json:"Format,omitempty"` // JPEG
+ FileSize string `json:"FileSize,omitempty"` // 文件大小 字节
+ Width string `json:"Width,omitempty"` // 图片宽度
+ Height string `json:"Height,omitempty"` // 图片高度
+ BitDepth string `json:"BitDepth,omitempty"` // 位深
+ } `json:"track"`
+ } `json:"media"`
+}
+type ImageInfo struct {
+ Type string `json:"@type"` // General Audio
+ ImageCount string `json:"ImageCount,omitempty"` // 图片数
+ FileExtension string `json:"FileExtension,omitempty"` // 扩展名
+ Format string `json:"Format"` // 容器格式 MPEG-4 AAC
+ FileSize uint64 `json:"FileSize,omitempty"` // 文件大小
+ Width int `json:"Width"` // 宽度 像素
+ Height int `json:"Height"` // 高度 像素
+ BitDepth string `json:"BitDepth"` // 位深
+}
+
+/*
+获取音频文件的mediainfo信息
+*/
+func GetImageMedia(absPath string) ImageInfo {
+ var im ImageMedia
+ cmd := exec.Command("mediainfo", absPath, "--Output=JSON")
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("C:/Program Files/MediaInfo/MediaInfo.exe", absPath, "--Output=JSON")
+ }
+ slog.Debug("生成的命令", slog.String("命令原文", fmt.Sprint(cmd)))
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ slog.Warn("运行mediainfo命令", slog.String("命令原文", fmt.Sprint(cmd)), slog.Any("产生的错误", err))
+ }
+ if err = json.Unmarshal(output, &im); err != nil {
+ slog.Warn("解析json", slog.Any("产生的错误", err))
+ } else {
+ slog.Debug("解析json成功", slog.Any("解析后的json", im))
+ }
+ return ImageMedia2Info(im)
+}
+
+func ImageMedia2Info(im ImageMedia) ImageInfo {
+ ii := new(ImageInfo)
+ ii.Type = "Image"
+ for _, kind := range im.Media.Track {
+ if kind.Type == "General" {
+ ii.ImageCount = kind.ImageCount
+ ii.FileExtension = kind.FileExtension
+ FileSize, err := strconv.ParseUint(kind.FileSize, 10, 64)
+ if err != nil {
+ slog.Warn("文件大小转换错误", slog.String("原始数值", kind.FileSize))
+ } else {
+ ii.FileSize = FileSize
+ }
+ }
+ if kind.Type == "Image" {
+ ii.ImageCount = kind.Format
+ ii.Format = kind.Format
+ if Width, err := strconv.Atoi(kind.Width); err != nil {
+ slog.Warn("图片视频宽度转换错误")
+ } else {
+ ii.Width = Width
+ }
+ if Height, err := strconv.Atoi(kind.Height); err != nil {
+ slog.Warn("图片高度转换错误")
+ } else {
+ ii.Height = Height
+ }
+ ii.BitDepth = kind.BitDepth
+ }
+ }
+ return *ii
+}
diff --git a/mediaInfo/README.md b/mediaInfo/README.md
new file mode 100644
index 0000000..bc72a98
--- /dev/null
+++ b/mediaInfo/README.md
@@ -0,0 +1,1367 @@
+>>Language_ISO639|zh-CN
+Author_Email:stevenlele@outlook.com
+Author_Name:stevenlele
+Author_OldNames:chriszxl,erabbit,weiq530,nkh0472
+
+|单词|中文翻译|
+|:---:|:---:|
+audiostream1|1个音频流
+audiostream2|2个音频流
+audiostream3|3个音频流
+bit1|位
+bit2|位
+bit3|位
+bps|b/s
+Bps|B/s
+Byte1|字节
+Byte2|字节
+Byte3|字节
+channel1|声道
+channel2|声道
+channel3|声道
+chapter1|个章节
+chapter2|个章节
+chapter3|个章节
+chaptersstream1|个章节流
+chaptersstream2|个章节流
+chaptersstream3|个章节流
+character1|个字符
+character2|个字符
+character3|个字符
+day1|天
+day2|天
+day3|天
+dB0|0dB
+dB1|dB
+dB2|dB
+dB3|dB
+file1|个文件
+file2|个文件
+file3|个文件
+fps1|FPS
+fps2|FPS
+fps3|FPS
+frame1|帧
+frame2|帧
+frame3|帧
+GB|GB
+Gb|Gb
+Gbps|Gb/s
+GBps|GB/s
+GHz|GHz
+GiB|GiB
+GibiByte1|GibiBytes
+GibiByte2|GibiBytes
+GibiByte3|GibiBytes
+GiBps|GiB/s
+GigaBit1|GigaBit
+GigaBit2|GigaBits
+GigaBit3|GigaBits
+GigaByte1|GigaByte
+GigaByte2|GigaBytes
+GigaByte3|GigaBytes
+hour1|小时
+hour2|小时
+hour3|小时
+Hz|Hz
+imagestream1|个图像流
+imagestream2|个图像流
+imagestream3|个图像流
+KB|kB
+Kb|kb
+KBps|kB/s
+Kbps|kb/s
+KHz|kHz
+KiB|KiB
+KibiBit1|KibiBit
+KibiBit2|KibiBits
+KibiBit3|KibiBits
+KibiByte1|KibiByte
+KibiByte2|KibiBytes
+KibiByte3|KibiBytes
+KiBps|KiB/s
+KiloBit1|KiloBit
+KiloBit2|KiloBits
+KiloBit3|KiloBits
+KiloByte1|KiloByte
+KiloByte2|KiloBytes
+KiloByte3|KiloBytes
+MB|MB
+Mb|Mb
+Mbps|Mb/s
+MBps|MebiBytes
+MebiBit1|MebiBit
+MebiBit2|MebiBits
+MebiBit3|MebiBits
+MebiByte1|MebiByte
+MebiByte2|MebiBytes
+MebiByte3|MebiBytes
+MegaBit1|MegaBit
+MegaBit2|MegaBits
+MegaBit3|MegaBits
+MegaByte1|MegaByte
+MegaByte2|MegaBytes
+MegaByte3|MegaBytes
+MHz|MHz
+MiB|MiB
+Mib|Mib
+MiBps|MiB/s
+millisecond1|毫秒
+millisecond2|毫秒
+millisecond3|毫秒
+minute1|分
+minute2|分
+minute3|分
+month1|月
+month2|月
+month3|月
+object1|个对象
+object2|个对象
+object3|个对象
+pixel1|像素
+pixel2|像素
+pixel3|像素
+second1|秒
+second2|秒
+second3|秒
+textstream1|个字幕流
+textstream2|个字幕流
+textstream3|个字幕流
+videoframes1|个视频帧
+videoframes2|个视频帧
+videoframes3|个视频帧
+videostream1|个视频流
+videostream2|个视频流
+videostream3|个视频流
+warppoint0|无变形参考点(warppoint)
+warppoint1|个变形参考点(warppoint)
+warppoint2|个变形参考点(warppoint)
+warppoint3|个变形参考点(warppoint)
+week1|周
+week2|周
+week3|周
+year1|年
+year2|年
+year3|年
+,|,
+:|:
+3D|3D
+3DType|3D类型
+5.1.2ch|5.1.2
+5.1.4ch|5.1.4
+5.1ch|5.1
+608_Mode|EIA-608转换模式
+7.1.2ch|7.1.2
+7.1ch|7.1
+About|关于
+About_Hint|联系作者和检查更新
+Accompaniment|伴奏
+ActiveFormatDescription|活动图像标识符(AFD)
+ActiveFormatDescription_MuxingMode|活动图像标识符(AFD),混流模式
+Actor|演员
+Actor_Character|饰演角色
+Addcreationdatetotextoutput|将创建日期添加到文本输出
+Addversiontotextoutput|将版本添加到文本输出
+Added_Date|添加日期
+Address|地址
+AdID|广告ID标识符
+ADM:ShowChannelFormats|ADM:显示ChannelFormat
+ADM:ShowTrackUIDs|ADM:显示TrackUID
+AdmProfile|ADM配置(Profile)
+Advanced|高级
+Advancedmode|高级模式
+Album|专辑
+Album_ReplayGain_Gain|专辑播放增益
+Album_ReplayGain_Peak|专辑播放增益峰值
+Alignment|对齐方式
+Alignment_Aligned|对齐交错
+Alignment_Split|分割交错
+All|全部
+AlternateGroup|组别
+Archival_Location|归档位置
+Arranger|编曲
+ArtDirector|艺术指导
+AspectRatio|宽高比
+AssistantDirector|副导演
+AssociatedVideo_FrameRate|关联视频帧率
+at|@
+Atleastonefile|(至少要打开一个文件)
+Audio|音频
+Audiostream(s)|音频流
+Audio_Codec_List|音频解码器
+Audio_No|无音频
+Audio1|音频1
+Audio2|音频2
+AudioComments|音频注释
+AudioCount|音频流总数
+AudioDescription|音频说明
+AudioDescriptionPresent|含有音频说明
+AudioDescriptionType|音频说明类型
+AudioLoudnessStandard|音频响度标准
+AudioRenderingIndication|音频渲染指示
+AudioSceneInfoID|音频场景信息ID
+AudioTrackLayout|音轨格式
+Author|作者
+Back4ToBack2|Fourbackstotwobacks
+Balance_FrontBackListener|Front/backlistenerbalance
+Balance_FrontBackOverheadFloor|Front/backoverhead/floorbalance
+BarCode|条形码
+Basic|基本
+Basic_Note|提示:请选择表格、树状图等其他视图了解更多文件信息
+BedChannelConfiguration|音床声道配置
+BedChannelCount|音床声道数
+BinauralRenderMode|双耳渲染模式
+BitDepth|位深
+BitDepth_Detected|检测到的位深
+BitDepth_Stored|存储的位深
+BitRate|码率
+BitRate_Encoded|编码码率
+BitRate_Maximum|最大码率
+BitRate_Minimum|最小码率
+BitRate_Mode|码率模式
+BitRate_Mode_CBR|恒定码率(CBR)
+BitRate_Mode_VBR|动态码率(VBR)
+BitRate_Nominal|额定码率
+Bits-(Pixel*Frame)|数据密度[码率/(像素*帧率)]
+BufferSize|缓冲大小
+Cancel|取消
+CaptionServiceName|字幕服务名称
+CatalogNumber|
+Channel(s)|声道数
+ChannelCoded|声道是否编码
+ChannelConfiguration|声道配置
+ChannelFormat|声道格式
+ChannelLayout|声道布局
+ChannelMode|声道模式
+ChannelPositions|声道位置
+Chapter(s)|章节数
+Chapters|章节
+Chaptersstream(s)|章节流
+Chapters_Codec_List|章节解码器
+Chapters_No|无章节
+ChaptersCount|章节流总数
+CharacterSet|
+CheckNewVersion|检查新版本
+Choosecustom|自定义
+Choosecustomsheet|选择自定义表格
+Choosecustomtext|选择自定义文字
+Chooseexportformat|选择输出格式
+Choosefile(s)|选择文件
+Choosefilename|选择文件名
+Chooselanguage|选择语言
+Choreographer|动作指导
+Chroma|色度
+ChromaSubsampling|色度抽样
+Classifier|内容类别
+ClearList|清除列表
+Close|关闭
+Closeallbeforeopen|打开新文件前关闭其他文件
+ClosedCaptionsLanguage|隐藏式字幕语言
+ClosedCaptionsPresent|含有隐藏式字幕
+ClosedCaptionsType|隐藏式字幕类型
+Codec|编解码器
+Codec_Description|编解码器说明
+Codec_Info|编解码器详情
+Codec_Profile|编码配置(Profile)
+Codec_Settings|编码设置
+Codec_Settings_BVOP|编码设置,B帧
+Codec_Settings_CABAC|编码设置,CABAC
+Codec_Settings_Endianness|编码设置,字节顺序
+Codec_Settings_Firm|编码设置,Firm
+Codec_Settings_Floor|编码设置,Floor
+Codec_Settings_GMC|编码设置,GMC
+Codec_Settings_ITU|编码设置,ITU
+Codec_Settings_Law|编码设置,Law
+Codec_Settings_Matrix|编码设置,矩阵
+Codec_Settings_PacketBitStream|编码设置,Packetbitstream
+Codec_Settings_QPel|编码设置,QPel
+Codec_Settings_Sign|编码设置,Sign
+Codec_Url|编解码器网址
+CodecConfigurationBox|编码配置区块(box)
+CodecID|编解码器ID
+CodecID_Description|编解码器说明
+CoDirector|联合导演
+Collection|系列
+Colorimetry|比色法
+ColorSpace|色彩空间
+colour_primaries|色彩原色
+colour_range|色彩范围
+Comment|注释
+CommissionedBy|委托人
+Compilation|选集
+ComplementaryObject|互补对象
+CompleteName|完整名称
+CompletionDate|完整日期
+ComplexityIndex|复杂度指数
+Composer|作曲
+Compression_Mode|压缩模式
+Compression_Mode_Lossless|无损
+Compression_Mode_Lossy|有损
+Compression_Ratio|压缩率
+Conductor|指挥
+ConformanceCheck|一致性检查
+ConformanceErrors|一致性错误
+ConformanceInfos|一致性信息
+ConformanceWarnings|一致性警告
+ContactEmail|联络邮件
+ContactTelephoneNumber|联络电话号码
+Containerandgeneralinformation|容器格式和一般信息
+Content|内容
+ContentType|内容类型
+CoProducer|联合制片人
+Copyright|版权
+CopyrightYear|版权年份
+CostumeDesigner|服装设计
+Count|数量
+Country|国家/地区
+Cover|封面
+Cover_Datas|封面数据
+Cover_Description|封面说明
+Cover_Mime|封面MIME
+Cover_Type|封面类型
+Cropped|裁剪信息
+Custom|自定义
+CustomDownmixTargets|自定义缩混目标
+Customize|自定义
+DarkMode|深色模式
+Date|日期
+Debug|调试
+Decimalpoint|小数点
+Default|
+Default_Setting|系统默认
+DefaultTargetDeviceConfig|默认目标设备配置
+Delay|延迟
+Delay_Source|延迟来源
+Delay_Source_Container|容器
+Delay_Source_Stream|媒体流
+Delete|删除
+Description|说明
+Details|
+dialnorm|对白归一化
+DialogueCorrected|对白校正
+DialogueEnhancement|对白增强
+DialogueNormalization|对白归一化
+Digitized_Date|数字化日期
+Dimensions|尺寸
+Director|导演
+DirectorOfPhotography|摄影指导
+Disabled|禁用
+DisplayAspectRatio|画面比例
+DisplayAspectRatio_CleanAperture|干净光圈画面比例
+DisplayAspectRatio_Original|原始画面比例
+DistributedBy|发行商
+Distributor|发行商
+Dolby_Atmos_Metadata|杜比全景声元数据
+DolbyAtmos|杜比全景声
+Donate|赞助
+DotsPerInch|DPI
+Downmix|Downmix
+Downmix_5.1.x|5.1and5.1.xdownmix
+Downmix_5to2|5.1to2.0downmix
+Downmix_5to2_90deg|
+DrcSets_Count|DRC组数
+DrcSets_Effects|DRC效果类型
+Duration|时长
+Duration_End|结束时间
+Duration_End_Command|结束时间(命令)
+Duration_Start|开始时间
+Duration_Start_Command|开始时间(命令)
+Duration_Start2End|可视内容时长
+DynamicObject|动态对象
+DynamicRangeControl|动态范围控制
+Eac3DrcProfile|E-AC-3DRC配置(Profile)
+Edit|编辑
+EditedBy|编辑者
+EditorialClassification|编辑分类
+ElementaryStream|基本流
+ElementCount|元素总数
+EMail|电子邮件
+Encoded_Application|编码程序
+Encoded_Date|编码日期
+Encoded_Library|编码函数库
+Encoded_Library_Settings|编码设置
+Encoded_Original|编码源
+EncodedBy|编码者
+EPG_Positions|EPG位置(内部)
+EpisodeTitleNumber|集数标题
+Error_File|读取文件出错
+Error_File_Write|写入文件出错
+Events_MinDuration|
+Events_PaintOn|PaintOn事件数
+Events_PopOn|PopOn事件数
+Events_RollUp|RollUp事件数
+Events_Total|事件总计数
+ExecutiveProducer|出品人
+Exit|退出
+Exit_Hint|退出程序
+Export|导出
+Export_Hint|以自定义格式导出
+Extensions|扩展名
+External_Media_NotMounted|无法读取外部储存
+Family|系列
+Fax|传真
+File|文件
+Filesize|文件大小
+File_Append|接在现有文件后面(警告:参数相同时要小心)
+File_Created_Date|文件创建日期
+File_Created_Date_Local|文件创建日期(本地)
+File_Hint|选择多媒体文件
+File_Modified_Date|最后修改日期
+File_Modified_Date_Local|最后修改日期(本地)
+FileExtension|扩展名
+FileName|文件名
+FileNameExtension|文件名和扩展名
+FileSize|文件大小
+FirstDisplay_Delay_Frames|第一个事件前的帧数
+FirstDisplay_Type|第一个事件的类型
+FirstFrameOfAction|动作的第一帧
+FlatPanelTv|平板电视
+FlowID|FlowID
+Folder|文件夹
+Folder(R)|文件夹(R)
+Folder(R)_Hint|选择一个文件夹(包含所有子文件夹)
+Folder(Recursively)|文件夹(包含子文件夹)
+Folder_Hint|选择文件夹
+FolderName|文件夹名称
+Forced|
+Format|格式
+Format_Commercial|传播名
+Format_Commercial_IfAny|传播名
+Format_Compression|压缩算法
+Format_Description|格式说明
+Format_Info|格式详情
+Format_Level|格式标准(Level)
+Format_Profile|格式配置(Profile)
+Format_Settings|格式设置
+Format_Settings_BVOP|格式设置,B帧
+Format_Settings_CABAC|格式设置,CABAC
+Format_Settings_Emphasis|频率增强(Emphasis)
+Format_Settings_Endianness|格式设置,字节顺序
+Format_Settings_Firm|格式设置,Firm
+Format_Settings_Floor|格式设置,Floor
+Format_Settings_FrameMode|帧模式
+Format_Settings_GMC|格式设置,GMC
+Format_Settings_GOP|格式设置,GOP
+Format_Settings_ITU|格式设置,ITU
+Format_Settings_Law|格式设置,Law
+Format_Settings_Matrix|格式设置,矩阵
+Format_Settings_Matrix_Custom|自定义
+Format_Settings_Matrix_Default|默认
+Format_Settings_Mode|模式
+Format_Settings_ModeExtension|模式扩展
+Format_Settings_PacketBitStream|格式设置,Packetbitstream
+Format_Settings_PictureStructure|格式设置,图像结构
+Format_Settings_PS|格式设置,PS
+Format_Settings_Pulldown|格式设置,Pulldown
+Format_Settings_QPel|格式设置,QPel
+Format_Settings_RefFrames|格式设置,参考帧
+Format_Settings_SBR|格式设置,SBR
+Format_Settings_Sign|格式设置,Sign
+Format_Settings_Wrapping|格式设置,封装模式
+Format_Tier|格式等级(Tier)
+Format_Url|格式网址
+Format_Version|格式版本
+FormatDefinition|格式定义
+FpaManufacturer|FPA制造商
+FpaPass|FPA通过
+FpaVersion|FPA版本
+FrameCount|总帧数
+FrameRate|帧率
+FrameRate_MaxFrameNumber|
+FrameRate_Maximum|最高帧率
+FrameRate_Minimum|最低帧率
+FrameRate_Mode|帧率模式
+FrameRate_Mode_CFR|恒定帧率(CFR)
+FrameRate_Mode_VFR|动态帧率(VFR)
+FrameRate_Nominal|额定帧率
+FrameRate_Original|原始帧率
+FrameRate_Real|真实帧率
+FullParsing|
+General|概览
+GeneralCompliance|
+Genre|流派
+Genre_000|Blues
+Genre_001|ClassicRock
+Genre_002|Country
+Genre_003|Dance
+Genre_004|Disco
+Genre_005|Funk
+Genre_006|Grunge
+Genre_007|Hip-Hop
+Genre_008|Jazz
+Genre_009|Metal
+Genre_010|NewAge
+Genre_011|Oldies
+Genre_012|Other
+Genre_013|Pop
+Genre_014|R&B
+Genre_015|Rap
+Genre_016|Reggae
+Genre_017|Rock
+Genre_018|Techno
+Genre_019|Industrial
+Genre_020|Alternative
+Genre_021|Ska
+Genre_022|DeathMetal
+Genre_023|Pranks
+Genre_024|Soundtrack
+Genre_025|Euro-Techno
+Genre_026|Ambient
+Genre_027|Trip-Hop
+Genre_028|Vocal
+Genre_029|Jazz+Funk
+Genre_030|Fusion
+Genre_031|Trance
+Genre_032|Classical
+Genre_033|Instrumental
+Genre_034|Acid
+Genre_035|House
+Genre_036|Game
+Genre_037|SoundClip
+Genre_038|Gospel
+Genre_039|Noise
+Genre_040|Alt.Rock
+Genre_041|Bass
+Genre_042|Soul
+Genre_043|Punk
+Genre_044|Space
+Genre_045|Meditative
+Genre_046|InstrumentalPop
+Genre_047|InstrumentalRock
+Genre_048|Ethnic
+Genre_049|Gothic
+Genre_050|Darkwave
+Genre_051|Techno-Industrial
+Genre_052|Electronic
+Genre_053|Pop-Folk
+Genre_054|Eurodance
+Genre_055|Dream
+Genre_056|SouthernRock
+Genre_057|Comedy
+Genre_058|Cult
+Genre_059|GangstaRap
+Genre_060|Top40
+Genre_061|ChristianRap
+Genre_062|Pop/Funk
+Genre_063|Jungle
+Genre_064|NativeAmerican
+Genre_065|Cabaret
+Genre_066|NewWave
+Genre_067|Psychedelic
+Genre_068|Rave
+Genre_069|Showtunes
+Genre_070|Trailer
+Genre_071|Lo-Fi
+Genre_072|Tribal
+Genre_073|AcidPunk
+Genre_074|AcidJazz
+Genre_075|Polka
+Genre_076|Retro
+Genre_077|Musical
+Genre_078|Rock&Roll
+Genre_079|HardRock
+Genre_080|Folk
+Genre_081|Folk-Rock
+Genre_082|NationalFolk
+Genre_083|Swing
+Genre_084|FastFusion
+Genre_085|Bebop
+Genre_086|Latin
+Genre_087|Revival
+Genre_088|Celtic
+Genre_089|Bluegrass
+Genre_090|Avantgarde
+Genre_091|GothicRock
+Genre_092|ProgressiveRock
+Genre_093|PsychedelicRock
+Genre_094|SymphonicRock
+Genre_095|SlowRock
+Genre_096|BigBand
+Genre_097|Chorus
+Genre_098|EasyListening
+Genre_099|Acoustic
+Genre_100|Humour
+Genre_101|Speech
+Genre_102|Chanson
+Genre_103|Opera
+Genre_104|ChamberMusic
+Genre_105|Sonata
+Genre_106|Symphony
+Genre_107|BootyBass
+Genre_108|Primus
+Genre_109|PornGroove
+Genre_110|Satire
+Genre_111|SlowJam
+Genre_112|Club
+Genre_113|Tango
+Genre_114|Samba
+Genre_115|Folklore
+Genre_116|Ballad
+Genre_117|PowerBallad
+Genre_118|RhythmicSoul
+Genre_119|Freestyle
+Genre_120|Duet
+Genre_121|PunkRock
+Genre_122|DrumSolo
+Genre_123|ACappella
+Genre_124|Euro-House
+Genre_125|DanceHall
+Genre_126|Goa
+Genre_127|Drum&Bass
+Genre_128|Club-House
+Genre_129|Hardcore
+Genre_130|Terror
+Genre_131|Indie
+Genre_132|BritPop
+Genre_133|Afro-Punk
+Genre_134|PolskPunk
+Genre_135|Beat
+Genre_136|ChristianGangstaRap
+Genre_137|HeavyMetal
+Genre_138|BlackMetal
+Genre_139|Crossover
+Genre_140|ContemporaryChristian
+Genre_141|ChristianRock
+Genre_142|Merengue
+Genre_143|Salsa
+Genre_144|ThrashMetal
+Genre_145|Anime
+Genre_146|JPop
+Genre_147|Synthpop
+Genre_148|Abstract
+Genre_149|ArtRock
+Genre_150|Baroque
+Genre_151|Bhangra
+Genre_152|BigBeat
+Genre_153|Breakbeat
+Genre_154|Chillout
+Genre_155|Downtempo
+Genre_156|Dub
+Genre_157|EBM
+Genre_158|Eclectic
+Genre_159|Electro
+Genre_160|Electroclash
+Genre_161|Emo
+Genre_162|Experimental
+Genre_163|Garage
+Genre_164|Global
+Genre_165|IDM
+Genre_166|Illbient
+Genre_167|Industro-Goth
+Genre_168|JamBand
+Genre_169|Krautrock
+Genre_170|Leftfield
+Genre_171|Lounge
+Genre_172|MathRock
+Genre_173|NewRomantic
+Genre_174|Nu-Breakz
+Genre_175|Post-Punk
+Genre_176|Post-Rock
+Genre_177|Psytrance
+Genre_178|Shoegaze
+Genre_179|SpaceRock
+Genre_180|TropRock
+Genre_181|WorldMusic
+Genre_182|Neoclassical
+Genre_183|Audiobook
+Genre_184|AudioTheatre
+Genre_185|NeueDeutscheWelle
+Genre_186|Podcast
+Genre_187|IndieRock
+Genre_188|G-Funk
+Genre_189|Dubstep
+Genre_190|GarageRock
+Genre_191|Psybient
+GotoWebSite|访问网站
+Gop_OpenClosed|GOP开闭
+Gop_OpenClosed_Closed|封闭(Closed)
+Gop_OpenClosed_FirstFrame|第一帧GOP开闭
+Gop_OpenClosed_Open|开放(Open)
+Graph|图形
+Group|分组
+Grouping|分组
+GroupPreset|分组预设(Preset)
+h|时
+HDR_Format|HDR格式
+Headerfile|创建头文件
+Height|高度
+Height_CleanAperture|干净光圈高度
+Height_Original|原始高度
+Help|帮助
+Hint|提示
+HomeTheaterAvr|家庭影院AVR
+Howmanyaudiostreams?|有多少个音频流?
+Howmanychaptersstreams?|有多少个章节流?
+Howmanytextstreams?|有多少个字幕流?
+Howmanyvideostreams?|有多少个视频流?
+HTML|网页
+ID|ID
+IdentClockStart|报时画面起始点
+IFrameInterval|I帧间隔
+Image|图像
+Imagestream(s)|图像流
+Image_Codec_List|图像解码器
+ImageCount|图像流总数
+ImmersiveStereo|沉浸式立体声
+Info|信息
+Instruments|乐器
+IntegratedLoudness|综合响度
+IntegratedLoudness_Level|综合响度(levelgated)
+IntegratedLoudness_Speech|综合响度(speechgated)
+InteractivityEnabled|启用交互
+Interlaced_BFF|底场优先(BFF)
+Interlaced_Interlaced|隔行扫描(交错)
+Interlaced_PPF|逐行扫描(连续)
+Interlaced_Progressive|逐行扫描(连续)
+Interlaced_TFF|顶场优先(TFF)
+Interlacement|扫描模式
+Interleave_Duration|交错间隔时间
+Interleave_Preload|交错预加载时间
+Interleave_VideoFrames|交错间隔帧数
+Interleaved|交错
+InternetMediaType|网络媒体类型
+IRCA|IRCA
+ISBN|ISBN
+ISRC|ISRC
+JocBalance_FrontBackListener|
+JocBalance_FrontBackOverheadFloor|
+JocTrim_Center|
+JocTrim_Height|
+JocTrim_Surround|
+JocTrimMode|
+JocTrimMode0i|
+JocTrimMode1i|
+JocTrimMode2i|
+JocTrimMode3i|
+JocTrimMode4i|
+JocTrimMode5i|
+JocTrimMode6i|
+JocTrimMode7i|
+JocTrimMode8i|
+Keywords|关键词
+Knowncodecs|已知解码器
+Knownformats|已知格式
+Knownparameters|已知参数
+Label|标签
+Language|语言
+Language_aa|阿法尔语(Afar)
+Language_ab|阿布哈兹语(Abkhazian)
+Language_ae|阿维斯陀语(Avestan)
+Language_af|南非荷兰语/阿非利卡语(Afrikaans)
+Language_ak|阿坎语(Akan)
+Language_am|阿姆哈拉语(Amharic)
+Language_an|阿拉贡语(Aragonese)
+Language_ar|阿拉伯语(Arabic)
+Language_as|阿萨姆语(Assamese)
+Language_av|阿瓦尔语(Avaric)
+Language_ay|艾马拉语(Aymara)
+Language_az|阿塞拜疆语(Azerbaijani)
+Language_ba|巴什基尔语(Bashkir)
+Language_be|白俄罗斯语(Belarusian)
+Language_bg|保加利亚语(Bulgarian)
+Language_bh|比哈尔语(Bihari)
+Language_bi|比斯拉马语(Bislama)
+Language_bm|班巴拉语(Bambara)
+Language_bn|孟加拉语(Bengali)
+Language_bo|藏语(Tibetan)
+Language_br|布列塔尼语(Breton)
+Language_bs|波斯尼亚语(Bosnian)
+Language_ca|加泰罗尼亚语(Catalan)
+Language_ce|车臣语(Chechen)
+Language_ch|查莫罗语(Chamorro)
+Language_cmn|
+Language_co|科西嘉语(Corsican)
+Language_cr|克里语(Cree)
+Language_cs|捷克语(Czech)
+Language_cu|古教会斯拉夫语(Slavonic)
+Language_cv|楚瓦什语(Chuvash)
+Language_cy|威尔士语(Welsh)
+Language_da|丹麦语(Danish)
+Language_de|德语(German)
+Language_dv|迪维希语(Divehi)
+Language_dz|不丹语(Dzongkha)
+Language_ee|埃维语(Ewe)
+Language_el|希腊语(Greek)
+Language_en|英语(English)
+Language_en-gb|英式英语(English,GreatBritain)
+Language_en-us|美式英语(English,UnitedStates)
+Language_eo|世界语(Esperanto)
+Language_es|西班牙语(Spanish)
+Language_es-419|拉美西班牙语(Spanish,LatinAmerica)
+Language_et|爱沙尼亚语(Estonian)
+Language_eu|巴斯克语(Basque)
+Language_fa|波斯语(Persian)
+Language_ff|富拉语(Fulah)
+Language_fi|芬兰语(Finnish)
+Language_fil|
+Language_fj|斐济语(Fijian)
+Language_fo|法罗语(Faroese)
+Language_fr|法语(French)
+Language_fy|弗里斯语(Frisian)
+Language_ga|爱尔兰语(Irish)
+Language_gd|苏格兰盖尔语(Gaelic)
+Language_gl|加利西亚语(Galician)
+Language_gn|瓜拉尼语(Guarani)
+Language_gu|古吉拉特语(Gujarati)
+Language_gv|曼岛语(Manx)
+Language_ha|豪萨语(Hausa)
+Language_he|希伯来语(Hebrew)
+Language_hi|印地语(Hindi)
+Language_ho|莫土语(HiriMotu)
+Language_hr|克罗地亚语(Croatian)
+Language_ht|海地语(Haitian)
+Language_hu|匈牙利语(Hungarian)
+Language_hy|亚美尼亚语(Armenian)
+Language_hy-az|阿塞拜疆亚美尼亚语(Armenian,Azerbaijani)
+Language_hz|赫雷罗语(Herero)
+Language_ia|国际语(AuxiliaryLanguageAssociation)
+Language_id|印尼语(Indonesian)
+Language_ie|西方国际语(Interlingue)
+Language_ig|伊博语(Igbo)
+Language_ii|四川彝语(SichuanYi)
+Language_ik|伊努皮克语(Inupiaq)
+Language_Info|语言信息
+Language_io|伊多语(Ido)
+Language_is|冰岛语(Icelandic)
+Language_it|意大利语(Italian)
+Language_iu|伊努特语(Inuktitut)
+Language_ja|日语(Japanese)
+Language_jv|爪哇语(Javanese)
+Language_ka|格鲁吉亚语(Georgian)
+Language_kg|刚果语(Kongo)
+Language_ki|基库尤语(Kikuyu)
+Language_kj|宽亚玛语/广雅语(Kuanyama)
+Language_kk|哈萨克语(Kazakh)
+Language_kl|格陵兰语(Kalaallisut)
+Language_km|高棉语(Khmer)
+Language_kn|卡纳达语(Kannada)
+Language_ko|韩语(Korean)
+Language_kr|卡努里语(Kanuri)
+Language_ks|克什米尔语(Kashmiri)
+Language_ku|库尔德语(Kurdish)
+Language_kv|科米语(Komi)
+Language_kw|康沃尔语(Cornish)
+Language_ky|吉尔吉斯语(Kirghiz)
+Language_la|拉丁语(Latin)
+Language_lb|卢森堡语(Luxembourgish)
+Language_lg|卢干达语(Ganda)
+Language_li|林堡语(Limburgish)
+Language_ln|林加拉语(Lingala)
+Language_lo|老挝语(Lao)
+Language_lt|立陶宛语(Lithuanian)
+Language_lu|契卢巴语(Luba-Katanga)
+Language_lv|拉脱维亚语(Latvian)
+Language_mg|马达加斯加语(Malagasy)
+Language_mh|马绍尔语(Marshallese)
+Language_mi|毛利语(Maori)
+Language_mk|马其顿语(Macedonian)
+Language_ml|马拉雅拉姆语(Malayalam)
+Language_mn|蒙古语(Mongolian)
+Language_mn-cn|中国蒙古语(Mongolian,China)
+Language_mo|摩尔多瓦语(Moldavian)
+Language_More|语言详情
+Language_mr|马拉地语(Marathi)
+Language_ms|马来语(Malay)
+Language_ms-bn|文莱马来语(Malay,Brunei)
+Language_mt|马尔他语(Maltese)
+Language_mul|多国语言
+Language_my|缅甸语(Burmese)
+Language_na|瑙鲁语(Nauru)
+Language_nb|书面挪威语(NorwegianBokmal)
+Language_nd|北恩德贝勒语(Ndebele)
+Language_ne|尼泊尔语(Nepali)
+Language_ng|恩东加语(Ndonga)
+Language_nl|荷兰语(Dutch)
+Language_nl-be|比利时荷兰语(Flemish)
+Language_nn|新挪威语(NorwegianNynorsk)
+Language_no|挪威语(Norwegian)
+Language_nr|南恩德贝勒语(Ndebele)
+Language_nv|纳瓦霍语(Navaho)
+Language_ny|奇契瓦语/尼扬贾语(Nyanja)
+Language_oc|奥克语(Occitan)
+Language_oj|奥吉布瓦语(Ojibwa)
+Language_om|奥罗莫语(Oromo)
+Language_or|奥里雅语(Oriya)
+Language_os|奥塞梯语(Ossetic)
+Language_pa|旁遮普语(Panjabi)
+Language_pi|巴利语(Pali)
+Language_pl|波兰语(Polish)
+Language_ps|普什图语(Pushto)
+Language_pt|葡萄牙语(Portuguese)
+Language_pt-br|巴西葡萄牙语(Portuguese,Brazil)
+Language_qu|克丘亚语(Quechua)
+Language_rm|罗曼什语/拉丁罗曼语(Raeto-Romance)
+Language_rn|基隆迪语(Rundi)
+Language_ro|罗马尼亚语(Romanian)
+Language_ru|俄语(Russian)
+Language_rw|卢旺达语(Kinyarwanda)
+Language_sa|梵语(Sanskrit)
+Language_sc|撒丁语(Sardinian)
+Language_sd|信德语(Sindhi)
+Language_se|北萨米语(NorthernSami)
+Language_sg|桑戈语(Sango)
+Language_si|僧伽罗语(Sinhala)
+Language_sk|斯洛伐克语(Slovak)
+Language_sl|斯洛文尼亚语(Slovenian)
+Language_sm|萨摩亚语(Samoan)
+Language_smi|萨米语(Sami)
+Language_sn|绍纳语(Shona)
+Language_so|索马里语(Somali)
+Language_sq|阿尔巴尼亚语(Albanian)
+Language_sr|塞尔维亚语(Serbian)
+Language_ss|斯威士语/斯瓦蒂语(Swati)
+Language_st|塞索托语(Sotho)
+Language_su|巽他语(Sundanese)
+Language_sv|瑞典语(Swedish)
+Language_sw|斯瓦希里语(Swahili)
+Language_ta|泰米尔语(Tamil)
+Language_te|泰卢固语(Telugu)
+Language_tg|塔吉克语(Tajik)
+Language_th|泰语(Thai)
+Language_ti|提格雷尼亚语(Tigrinya)
+Language_tk|土库曼语(Turkmen)
+Language_tl|菲律宾语/他加禄语(Tagalog)
+Language_tn|茨瓦纳语(Tswana)
+Language_to|汤加语(Tonga)
+Language_tr|土耳其语(Turkish)
+Language_ts|聪加语(Tsonga)
+Language_tt|鞑靼语(Tatar)
+Language_tw|契维语/特威语(Twi)
+Language_ty|塔希提语(Tahitian)
+Language_ug|维吾尔语(Uighur)
+Language_uk|乌克兰语(Ukrainian)
+Language_ur|乌尔都语(Urdu)
+Language_uz|乌兹别克语(Uzbek)
+Language_ve|文达语(Venda)
+Language_vi|越南语(Vietnamese)
+Language_vo|沃拉普克语(Volapuk)
+Language_wa|瓦隆语(Walloon)
+Language_wo|沃洛夫语(Wolof)
+Language_xh|科萨语(Xhosa)
+Language_yi|意第绪语(Yiddish)
+Language_yo|约鲁巴语(Yoruba)
+Language_yue|
+Language_za|壮语(Zhuang)
+Language_zh|中文(Chinese)
+Language_zh-cmn|
+Language_zh-cn|简体中文(Chinese,China)
+Language_zh-CN|
+Language_zh-Hans|
+Language_zh-Hant|
+Language_zh-tw|繁体中文(Chinese,Taiwan)
+Language_zh-TW|
+Language_zh-yue|
+Language_zu|祖鲁语(Zulu)
+LawRating|分级
+LCCN|LCCN
+LfeAttenuationKnown|LFE衰减是否已知
+LfeMixGain|LFE混音增益
+Library|混流函数库
+Lifetime_Subscribe_Button|终身订阅价格%PRICE%
+Lightness|亮度
+Lines_Count|行数
+Lines_MaxCountPerEvent|每个事件的最大行数
+LineUpStart|LineUp起始点
+LinkedTo_Bed_Pos|音床#s
+LinkedTo_ChannelFormat_Pos|声道格式#s
+LinkedTo_ComplementaryObject_Pos|互补对象#s
+LinkedTo_Content_Pos|内容#s
+LinkedTo_Group_Pos|分组#s
+LinkedTo_Object_Pos|对象#s
+LinkedTo_PackFormat_Pos|包格式#s
+LinkedTo_SignalGroup_Pos|信号组#s
+LinkedTo_StreamFormat_Pos|流格式#s
+LinkedTo_Substream_Pos|子流#s
+LinkedTo_TrackFormat_Pos|轨道格式#s
+LinkedTo_TrackUID_Pos|轨道UID#s
+List|列表
+LongTermLoudness|
+LoRoCenterMixGain|LoRo中央混音增益
+LoRoSurroundMixGain|LoRo环绕混音增益
+Loudness_Anchor|锚点响度
+Loudness_Anchor_Album|锚点响度(专辑)
+Loudness_Count|响度信息数
+Loudness_Count_Album|响度信息数(专辑)
+Loudness_MaximumMomentary|最大瞬时响度
+Loudness_MaximumMomentary_Album|最大瞬时响度(专辑)
+Loudness_MaximumOfRange|范围最大值
+Loudness_MaximumOfRange_Album|范围最大值(专辑)
+Loudness_MaximumShortTerm|最大短时响度
+Loudness_MaximumShortTerm_Album|最大短时响度(专辑)
+Loudness_ProductionMixingLevel|制作混音等级
+Loudness_ProductionMixingLevel_Album|制作混音等级(专辑)
+Loudness_Program|节目响度
+Loudness_Program_Album|节目响度(专辑)
+Loudness_Range|响度范围
+Loudness_Range_Album|响度范围(专辑)
+Loudness_RoomType|制作录音室类型
+Loudness_RoomType_Album|制作录音室类型(专辑)
+LtRtCenterMixGain|LtRt中央混音增益
+LtRtSurroundMixGain|LtRt环绕混音增益
+Lyricist|作词
+Lyrics|歌词
+Mastered_Date|母带制作日期
+MasteredBy|母带制作者
+MasteringDisplay_ColorPrimaries|制片显示器色彩原色
+MasteringDisplay_Luminance|制片显示器亮度
+Matrix_Channel(s)|矩阵编码,声道数
+Matrix_ChannelPositions|矩阵编码,声道位置
+matrix_coefficients|矩阵系数
+Matrix_Format|矩阵编码,格式
+MaxCLL|最大内容亮度(CLL)
+MaxFALL|最大帧平均亮度(FALL)
+MaxGain|最大增益
+MaximumMomentaryLoudness|最大瞬时响度
+MaxTruePeak|最大真实峰值
+MD5|
+MD5_Unencoded|
+MediaInfo_About|使用MediaInfo可以轻松读取音视频文件的编码和标签信息。\r\n本软件按BSD授权协议开源,用户可以免费使用,开发者也可以自由地学习、修改、重新发布本软件。(MacAppStore图形界面除外)
+MediaInfo_About_About|MediaInfov%MI_VERSION%,内置MediaInfoLibv%MIL_VERSION%
+Menu|菜单
+Menustream(s)|菜单流
+Menu_Codec_List|菜单解码器
+Menu_Hint|更多选择
+Menu_No|无菜单
+MenuCount|菜单流总数
+MenuID|菜单ID
+Metadata_Format|元数据格式
+Metadata_Format_Type|元数据帧类型
+Metadata_MuxingMode|元数据复用模式
+MixType|混音类型
+mn|分
+Mood|情绪
+More|更多
+Movie|电影名称
+ms|毫秒
+MSDI|MSDI
+MultipleStream|多个流
+MusicBy|音乐制作
+MuxingMode|混流模式
+MuxingMode_MoreInfo|混流模式详细信息
+MuxingMode_PackedBitstream|Packedbitstream
+Name|名称
+Nationality|国藉
+NetworkName|网络名称
+New|新建
+Newestversion|检查更新(需要连接网络)
+NewVersion_Menu|有新版本
+NewVersion_Question_Content|有新版本(v%Version%),要下载吗?
+NewVersion_Question_Title|新版本已发布!
+No|否
+Notyet|尚无
+NumberOfChannelFormats|声道格式数量
+NumberOfContents|内容数量
+NumberOfDynamicObjects|动态对象数量
+NumberOfElementaryStreams|基本流数量
+NumberOfObjects|对象数量
+NumberOfPackFormats|包格式数量
+NumberOfPresentations|形式(Presentation)数量
+NumberOfProgrammes|节目数量
+NumberOfStreamFormats|流格式数量
+NumberOfSubstreams|子流数量
+NumberOfTrackFormats|轨道格式数量
+NumberOfTrackUIDs|轨道UID数量
+NumColors|色彩数量
+Object|对象
+ObjectCount|对象数量
+OK|确定
+Oneoutputfileperinputfile|一个输入文件对应一个输出文件
+Open|打开
+OpenCaptionsLanguage|显式字幕语言
+OpenCaptionsPresent|含有显式字幕
+OpenCaptionsType|显式字幕格式
+Options|选项
+Options_Hint|设置
+Original|原始
+OriginalNetworkName|原始网络名称
+OriginalSourceForm|原始来源形式
+OriginalSourceMedium|原始来源介质
+OriginalSourceMedium_ID|原始来源介质ID
+Originator|创作者
+Other|其他
+OtherIdentifier|其他标识符
+OtherIdentifierType|其他标识符类型
+Output|输出
+Outputformat|输出格式
+OverallBitRate|总体码率
+OverallBitRate_Maximum|最高总体码率
+OverallBitRate_Minimum|最低总体码率
+OverallBitRate_Mode|总体码率模式
+OverallBitRate_Nominal|额定总体码率
+PackageName|包名
+PackFormat|包格式
+Part|分段
+Part_Count|总数
+PartNumber|分段编号
+PartTotal|分段总数
+Performer|演员
+Period|时期
+Phase90FilterInfo|90度相位过滤器
+Phase90FilterInfo2ch|90度相位过滤器(2声道)
+Phone|电话
+PictureRatio|画面比例
+PixelAspectRatio|像素宽高比
+PixelAspectRatio_CleanAperture|干净光圈像素宽高比
+PixelAspectRatio_Original|原始像素宽高比
+PlayCounter|播放计数器
+Played_Count|播放次数
+Played_First_Date|首次播放时间
+Played_Last_Date|上次播放时间
+PlayTime|播放时长
+PodcastCategory|播客类别
+PortableHeadphones|便携式耳机
+PortableSpeakers|便携式扬声器
+Position|位置
+Position_Cartesian|位置(直角坐标)
+Position_Polar|位置(极坐标)
+Position_Total|总数
+Preferences|设置
+PreferredDownmix|首选缩混
+Premium|高级版
+Premium_Summary|高级版订阅功能
+PreselectionLabel|预选标签
+Presentation|形式(Presentation)
+PresentationConfig|形式(Presentation)配置
+PresentationConfig_ContentClassifier|形式(Presentation)配置(内容类别)
+PresentationID|形式(Presentation)ID
+PreviousDownmixType5ch|之前的混缩类型(5声道)
+PreviousMixType2ch|之前的混音类型(2声道)
+PrimaryAudioLanguage|主音频语言
+Producer|制片人
+ProductionDesigner|制作设计师
+ProductionNumber|制作编号
+ProductionStudio|出品工作室
+ProductPlacement|产品布置
+Programme|节目
+ProgrammeHasText|节目含有文本
+ProgrammeTextLanguage|节目文本语言
+ProgrammeTitle|节目标题
+Publisher|发行商
+Purchased_Date|购买日期
+Quotecharacter|引号
+RadioStation|广播电台
+Rating|分级
+RealtimeLoudnessCorrected|实时响度校正
+Recorded_Date|录制日期
+Recorded_Location|录制地点
+Reel|
+Released_Date|发行日期
+RemixedBy|混音者
+RenderMode|渲染模式
+Renew|续订
+ReplayGain_Gain|播放增益
+ReplayGain_Peak|播放增益峰值
+Report|报告
+Resolution|分辨率
+RestoreLifetimeSubscription|恢复终身订阅
+s|秒
+SamplePeakLevel|采样峰值等级
+SamplePeakLevel_Album|采样峰值等级(专辑)
+SamplesPerFrame|每帧采样数
+SamplingCount|采样数
+SamplingRate|采样率
+Save|保存
+ScanOrder|扫描顺序
+ScanOrder_Original|原始扫描顺序
+ScanOrder_Stored|存储的扫描顺序
+ScanOrder_StoredDisplayedInverted|存储/显示扫描顺序相反
+ScanOrder_StoreMethod|扫描顺序存储方式
+ScanType|扫描类型
+ScanType_Original|原始扫描类型
+ScanType_StoreMethod|扫描类型储存方式
+ScreenplayBy|编剧
+ScreenToCenter|Screentocenter
+ScreenToFront|Screentofront
+Season|季期
+SecondaryAudioLanguage|第二音频语言
+seebelow|点击展开详情
+SendHeaderFile|请将头文件发送到:http://sourceforge.net/projects/mediainfo/(Bugs板块)
+Separator_Columns|列分隔符
+Separator_Lines|换行符
+SeriesTitle|系列标题
+ServiceChannel|服务频道号码
+ServiceKind|服务类别
+ServiceName|服务名称
+ServiceProvider|服务提供商
+ServiceType|服务类型
+Set|集合
+Set_Count|集合数目
+Setup|设置
+Sharpness|锐利度
+Sheet|表格
+Sheet(Complete)|表格(完整)
+Shellextension|文件资源管理器右键菜单(为多媒体文件的右键菜单添加MediaInfo选项)
+Shellextension,folder|添加到文件夹右键菜单
+ShellInfoTip|文件资源管理器工具提示(将鼠标移到文件图标上会显示相关信息)
+ShimName|Shim名称
+ShimVersion|Shim版本
+Showmenu|显示菜单
+Showtoolbar|显示工具栏
+SignalGroup|信号组
+SigningPresent|含有签名
+SignLanguage|签名语言
+Sort|排序
+SoundCategory|声音类别
+SoundEngineer|录音师
+Source|源
+Source_Duration|源,时长
+Source_FrameCount|源,帧数
+Source_SamplingCount|源,采样数
+Source_StreamSize|源,流大小
+Source_StreamSize_Encoded|源,编码后流大小
+SpokenSubtitles|语音字幕
+Standard|标准
+StoreMethod_InterleavedFields|交错场
+StoreMethod_SeparatedFields|单独场
+StoreMethod_SeparatedFields_1|单独场(每分块1场)
+StoreMethod_SeparatedFields_2|单独场(每分块2场)
+Stream|流
+Stream_MoreInfo|关于此流的更多信息
+StreamCount|此类型流的数量
+StreamFormat|流格式
+StreamID|流ID
+streamIdentifier|流标识符
+StreamKind|流类型
+StreamKindID|流标识符
+StreamKindPos|流标识符
+StreamSize|流大小
+StreamSize_Demuxed|分离后流大小
+StreamSize_Encoded|编码后流大小
+StreamSize_Proportion|流属性
+Subject|主题
+Subscribe|订阅
+Subscribe_Button|订购(%PRICE%一年)
+Subscribe_Failed|购买被取消或失败
+Subscribe_Manage|管理订阅
+Subscribe_Renew|续订?
+Subscribe_Renew_Price|续订(%PRICE%一年)
+Subscribe_Unaviable|无法检索订购详细信息。
+Subscribe_Waiting|购买等待家长批准
+Subscription_Active|订阅有效期至%DATE%
+Subscription_Android|%PRICE%/年(免费试用3天)
+Subscription_Ended|你的订购刚刚到期。
+Subscription_Expired|订购过期时间%DATE%
+Subscription_Lifetime|检测到终身订阅
+Subscription_Thanks|已检测到订阅。感谢您的支持!
+Substream|子流
+SubTrack|子轨道
+SubtstreamIdChannel|子流开始ID和声道编号
+Summary|摘要
+Supportedformats|支持格式
+Supported?|是否支持
+SupportUs|请支持我们。不仅可以解锁深色主题,未来还有更多功能。
+SurroundAttenuationKnown|环绕衰减是否已知
+SwitchGroup|切换组
+Synopsis|系统摘要
+SystemId|ID
+Tagged_Application|标记软件
+Tagged_Date|标记日期
+TargetDeviceConfig|目标设备配置
+Technician|技术员
+TermsOfUse|使用条款
+TertiaryAudioLanguage|第三音频语言
+Text|文本
+Text-Custom|文本-自定义
+Text(HTML)|文本(网页)
+Textstream(s)|字幕流
+Textstreams|字幕流
+Text_Codec_List|字幕解码器
+Text_No|无字幕
+Text1|字幕1
+Text2|字幕2
+Text3|字幕3
+TextCount|字幕流总数
+TextlessElementsExist|存在无文本元素
+ThanksTo|鸣谢
+Thousandsseparator|千位分隔符
+TimeCode|时间码
+TimeCode_FirstFrame|第一帧时间码
+TimeCode_LastFrame|最后一帧时间码
+TimeCode_MaxFrameNumber|时间码中的最大帧数
+TimeCode_MaxFrameNumber_Theory|时间码中的理论最大帧数
+TimeCode_Settings|时间码设置
+TimeCode_Source|时间码源
+TimeCode_Stripped|时间码,条带化
+TimeStamp|时间戳
+TimeZone|时区
+Title|标题
+Title_More|标题,更多信息
+Top4ToTop2|Fourfrontstotwofronts
+TopBackToFront|Topbackstofront
+TopBackToSide|Topbackstoside
+TopFrontToBack|Topfrontstoback
+TopFrontToFront|Topfrontstofront
+TopFrontToSide|Topfrontstoside
+Total|总计
+TotalNumberOfParts|分段总数
+TotalProgrammeDuration|节目时长总计
+Track|轨道名
+Track_Count|轨道数
+TrackFormat|轨道格式
+TrackIndex|轨道编号
+TrackUID|轨道UID
+transfer_characteristics|传输特性
+Translate_Reports|翻译报告
+Translator|翻译
+Transport|传输
+Tree|树状图
+Tree&Text|树状图和文本
+Trim_Center|Centertrim
+Trim_Height|Heighttrim
+Trim_Surround|Surroundtrim
+TrimMode|Trimmode
+TrimMode0i|Trimmodefor2.0
+TrimMode1i|Trimmodefor5.1
+TrimMode2i|Trimmodefor7.1
+TrimMode3i|Trimmodefor2.1.2
+TrimMode4i|Trimmodefor5.1.2
+TrimMode5i|Trimmodefor7.1.2
+TrimMode6i|Trimmodefor2.1.4
+TrimMode7i|Trimmodefor5.1.4
+TrimMode8i|Trimmodefor7.1.4
+TruePeakLevel|真实峰值等级
+TruePeakLevel_Album|真实峰值等级(专辑)
+Type|类型
+TypeDefinition|类型定义
+UniqueID|特征ID
+UniversalAdID|通用广告ID
+UniversalAdID_Registry|通用广告ID登记处
+UniversalAdID_Value|通用广告ID值
+Unknown|未知
+Url|网址
+Video|视频
+Videostream(s)|视频流
+Video_Codec_List|视频解码器
+Video_Delay|相对视频的延迟
+Video_No|无视频
+Video0_Delay|视频0延迟
+Video1|视频1
+VideoComments|视频注释
+VideoCount|视频流总数
+View|视图
+View_Hint|改变查看方式
+Views|查看
+Warning:morestreamsinthefiles|注意:此文件中还有更多的流
+Web|网页
+WebSite|网站
+WebSite_Audio|访问此音频解码器的网站
+WebSite_Audio_More|访问网站(%Url%)查找此音频解码器
+WebSite_General|访问此文件播放器的网站
+WebSite_General_More|访问此文件播放器的网站
+WebSite_Text|访问此字幕解码器的网站
+WebSite_Text_More|访问网站(%Url%)查找此字幕解码器
+WebSite_Url|http://MediaArea.net/MediaInfo
+WebSite_Video|访问此视频解码器的网站
+WebSite_Video_More|访问网站(%Url%)查找此视频解码器
+Width|宽度
+Width_CleanAperture|干净光圈宽度
+Width_Original|原始宽度
+WriteMe|给作者发邮件
+WriteToTranslator|给翻译发邮件
+Written_Date|写入日期
+Written_Location|写入地点
+WrittenBy|作者
+Yes|是
+Yoursystem|您的系统
+ZZ_Automatic_Percent|97
+ZZ_AutomaticLanguage_Percent|97
\ No newline at end of file
diff --git a/mediaInfo/Video.go b/mediaInfo/Video.go
new file mode 100644
index 0000000..b697b44
--- /dev/null
+++ b/mediaInfo/Video.go
@@ -0,0 +1,193 @@
+package mediaInfo
+
+import (
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "os/exec"
+ "runtime"
+ "strconv"
+)
+
+type VideoMedia struct {
+ CreatingLibrary struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+ Url string `json:"url"`
+ } `json:"creatingLibrary"`
+ Media struct {
+ Ref string `json:"@ref"`
+ Track []struct {
+ Type string `json:"@type"`
+ ID string `json:"ID"`
+ VideoCount string `json:"VideoCount,omitempty"`
+ AudioCount string `json:"AudioCount,omitempty"`
+ FileExtension string `json:"FileExtension,omitempty"`
+ Format string `json:"Format"`
+ FileSize string `json:"FileSize,omitempty"`
+ Duration string `json:"Duration"`
+ OverallBitRateMode string `json:"OverallBitRate_Mode,omitempty"`
+ OverallBitRate string `json:"OverallBitRate,omitempty"`
+ FrameRate string `json:"FrameRate"`
+ FrameCount string `json:"FrameCount,omitempty"`
+ FileModifiedDate string `json:"File_Modified_Date,omitempty"`
+ FileModifiedDateLocal string `json:"File_Modified_Date_Local,omitempty"`
+ Extra struct {
+ OverallBitRatePrecisionMin string `json:"OverallBitRate_Precision_Min"`
+ OverallBitRatePrecisionMax string `json:"OverallBitRate_Precision_Max"`
+ FileExtensionInvalid string `json:"FileExtension_Invalid"`
+ } `json:"extra,omitempty"`
+ StreamOrder string `json:"StreamOrder,omitempty"`
+ MenuID string `json:"MenuID,omitempty"`
+ FormatProfile string `json:"Format_Profile,omitempty"`
+ FormatLevel string `json:"Format_Level,omitempty"`
+ FormatSettingsCABAC string `json:"Format_Settings_CABAC,omitempty"`
+ FormatSettingsRefFrames string `json:"Format_Settings_RefFrames,omitempty"`
+ FormatSettingsGOP string `json:"Format_Settings_GOP,omitempty"`
+ CodecID string `json:"CodecID,omitempty"`
+ Width string `json:"Width,omitempty"`
+ Height string `json:"Height,omitempty"`
+ SampledWidth string `json:"Sampled_Width,omitempty"`
+ SampledHeight string `json:"Sampled_Height,omitempty"`
+ PixelAspectRatio string `json:"PixelAspectRatio,omitempty"`
+ DisplayAspectRatio string `json:"DisplayAspectRatio,omitempty"`
+ FrameRateNum string `json:"FrameRate_Num,omitempty"`
+ FrameRateDen string `json:"FrameRate_Den,omitempty"`
+ Standard string `json:"Standard,omitempty"`
+ ColorSpace string `json:"ColorSpace,omitempty"`
+ ChromaSubsampling string `json:"ChromaSubsampling,omitempty"`
+ BitDepth string `json:"BitDepth,omitempty"`
+ ScanType string `json:"ScanType,omitempty"`
+ Delay string `json:"Delay,omitempty"`
+ DelaySource string `json:"Delay_Source,omitempty"`
+ ColourRange string `json:"colour_range,omitempty"`
+ ColourRangeSource string `json:"colour_range_Source,omitempty"`
+ FormatVersion string `json:"Format_Version,omitempty"`
+ FormatAdditionalFeatures string `json:"Format_AdditionalFeatures,omitempty"`
+ MuxingMode string `json:"MuxingMode,omitempty"`
+ BitRateMode string `json:"BitRate_Mode,omitempty"`
+ BitRate string `json:"bit_rate,omitempty"`
+ Channels string `json:"Channels,omitempty"`
+ ChannelPositions string `json:"ChannelPositions,omitempty"`
+ ChannelLayout string `json:"ChannelLayout,omitempty"`
+ SamplesPerFrame string `json:"SamplesPerFrame,omitempty"`
+ SamplingRate string `json:"SamplingRate,omitempty"`
+ SamplingCount string `json:"SamplingCount,omitempty"`
+ CompressionMode string `json:"Compression_Mode,omitempty"`
+ VideoDelay string `json:"Video_Delay,omitempty"`
+ } `json:"track"`
+ } `json:"media"`
+}
+
+type VideoInfo struct {
+ Type string `json:"@type"` // General Audio Video
+ VideoCount string `json:"VideoCount,omitempty"` // 视频数
+ AudioCount string `json:"AudioCount,omitempty"` // 音轨数
+ FileExtension string `json:"FileExtension,omitempty"` // 扩展名
+ FileSize uint64 `json:"FileSize,omitempty"` // 文件大小
+ Duration float64 `json:"Duration,omitempty"` // 持续时间
+
+ VideoFormat string `json:"VideoFormat,omitempty"` // 视频格式 HEVC AVC
+ VideoCodecID string `json:"VideoCodecID,omitempty"` // 视频编码 hvc1 avc1
+ VideoWidth int `json:"VideoWidth,omitempty"` // 宽度 像素
+ VideoHeight int `json:"VideoHeight,omitempty"` // 高度 像素
+ VideoFrameRate float64 `json:"VideoFrameRate,omitempty"` // 视频帧率
+ VideoFrameCount int `json:"VideoFrameCount,omitempty"` // 视频帧数
+ VideoBitDepth string `json:"VideoBitDepth,omitempty"` // 视频位深
+ VideoBitRate string `json:"BitRate,omitempty"` // 视频比特率
+
+ AudioFormat string `json:"AudioFormat,omitempty"` //音频格式 AAC
+ AudioCodecID string `json:"AudioCodecID,omitempty"` //音频编码 mp4a-40-2
+ AudioFrameRate float64 `json:"AudioFrameRate,omitempty"` //音频帧率
+ AudioFrameCount int `json:"AudioFrameCount,omitempty"` // 音频帧数
+ AudioBitDepth string `json:"AudioBitDepth,omitempty"` // 音频位深
+ Channels int `json:"Channels,omitempty"` // 声道数
+
+}
+
+/*
+获取音频文件的mediainfo信息
+*/
+func GetVideoMedia(absPath string) VideoInfo {
+ var vm VideoMedia
+ cmd := exec.Command("mediainfo", absPath, "--Output=JSON")
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("C:/Program Files/MediaInfo/MediaInfo.exe", absPath, "--Output=JSON")
+ }
+ slog.Debug("生成的命令", slog.String("命令原文", fmt.Sprint(cmd)))
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ slog.Warn("运行mediainfo命令", slog.String("命令原文", fmt.Sprint(cmd)), slog.Any("产生的错误", err))
+ }
+ if err = json.Unmarshal(output, &vm); err != nil {
+ slog.Warn("解析json", slog.Any("产生的错误", err))
+ } else {
+ slog.Debug("解析json成功", slog.Any("解析后的json", vm))
+ }
+ return VideoMedia2Info(vm)
+}
+
+func VideoMedia2Info(vm VideoMedia) VideoInfo {
+ vi := new(VideoInfo)
+ vi.Type = "Video"
+ for _, kind := range vm.Media.Track {
+ if kind.Type == "General" {
+ vi.FileExtension = kind.FileExtension
+ FileSize, err := strconv.ParseUint(kind.FileSize, 10, 64)
+ if err != nil {
+ slog.Warn("文件大小转换错误", slog.String("原始数值", kind.FileSize))
+ } else {
+ vi.FileSize = FileSize
+ }
+ vi.AudioCount = kind.AudioCount
+ }
+ if kind.Type == "Video" {
+ vi.VideoCodecID = kind.CodecID
+ vi.VideoFormat = kind.Format
+ if Width, err := strconv.Atoi(kind.Width); err != nil {
+ slog.Warn("视频宽度转换错误")
+ } else {
+ vi.VideoWidth = Width
+ }
+ if Height, err := strconv.Atoi(kind.Height); err != nil {
+ slog.Warn("视频高度转换错误")
+ } else {
+ vi.VideoHeight = Height
+ }
+ vi.VideoBitDepth = kind.BitDepth
+ if VideoFrameRate, err := strconv.ParseFloat(kind.FrameRate, 64); err != nil {
+ slog.Warn("视频帧率转换错误")
+ } else {
+ vi.VideoFrameRate = VideoFrameRate
+ }
+ if VideoFrameCount, err := strconv.Atoi(kind.FrameCount); err != nil {
+ slog.Warn("视频帧数转换错误")
+ } else {
+ vi.VideoFrameCount = VideoFrameCount
+ }
+ vi.VideoBitRate = kind.BitRate
+
+ }
+ if kind.Type == "Audio" {
+ vi.AudioFormat = kind.Format
+ vi.AudioCodecID = kind.CodecID
+ if AudioFrameRate, err := strconv.ParseFloat(kind.FrameRate, 64); err != nil {
+ slog.Warn("音频帧率转换错误")
+ } else {
+ vi.AudioFrameRate = AudioFrameRate
+ }
+ if AudioFrameCount, err := strconv.Atoi(kind.FrameCount); err != nil {
+ slog.Warn("音频帧数转换错误")
+ } else {
+ vi.AudioFrameCount = AudioFrameCount
+ }
+ vi.AudioBitDepth = kind.BitDepth
+ if Channels, err := strconv.Atoi(kind.Channels); err != nil {
+ slog.Warn("视频声道数转换错误")
+ } else {
+ vi.Channels = Channels
+ }
+ }
+ }
+ return *vi
+}
diff --git a/mediaInfo/audio.json b/mediaInfo/audio.json
new file mode 100644
index 0000000..2344c18
--- /dev/null
+++ b/mediaInfo/audio.json
@@ -0,0 +1,59 @@
+{
+ "creatingLibrary": {
+ "name": "MediaInfoLib",
+ "version": "23.03",
+ "url": "https://mediaarea.net/MediaInfo"
+ },
+ "media": {
+ "@ref": "譚詠麟 - 水中花 [6LJ2mJU4BpI].m4a",
+ "track": [
+ {
+ "@type": "General",
+ "AudioCount": "1",
+ "FileExtension": "m4a",
+ "Format": "MPEG-4",
+ "Format_Profile": "Base Media",
+ "CodecID": "isom",
+ "CodecID_Compatible": "isom/iso2/mp41",
+ "FileSize": "4020073",
+ "Duration": "248.524",
+ "OverallBitRate_Mode": "CBR",
+ "OverallBitRate": "129406",
+ "StreamSize": "43703",
+ "HeaderSize": "43695",
+ "DataSize": "3976378",
+ "FooterSize": "0",
+ "IsStreamable": "Yes",
+ "File_Modified_Date": "2021-08-07 09:35:05 UTC",
+ "File_Modified_Date_Local": "2021-08-07 17:35:05",
+ "Encoded_Application": "Lavf60.3.100"
+ },
+ {
+ "@type": "Audio",
+ "StreamOrder": "0",
+ "ID": "1",
+ "Format": "AAC",
+ "Format_AdditionalFeatures": "LC",
+ "CodecID": "mp4a-40-2",
+ "Duration": "248.524",
+ "BitRate_Mode": "CBR",
+ "BitRate": "127999",
+ "Channels": "2",
+ "ChannelPositions": "Front: L R",
+ "ChannelLayout": "L R",
+ "SamplesPerFrame": "1024",
+ "SamplingRate": "44100",
+ "SamplingCount": "10959908",
+ "FrameRate": "43.066",
+ "FrameCount": "10703",
+ "Compression_Mode": "Lossy",
+ "StreamSize": "3976370",
+ "Title": "ISO Media file produced by Google Inc.",
+ "Language": "en",
+ "Default": "Yes",
+ "AlternateGroup": "1"
+ }
+ ]
+ }
+}
+
diff --git a/mediaInfo/general.json b/mediaInfo/general.json
new file mode 100644
index 0000000..58becf7
--- /dev/null
+++ b/mediaInfo/general.json
@@ -0,0 +1,21 @@
+{
+ "creatingLibrary": {
+ "name": "MediaInfoLib",
+ "version": "23.03",
+ "url": "https://mediaarea.net/MediaInfo"
+ },
+ "media": {
+ "@ref": "App_Cleaner_8_2_3_TNT.dmg",
+ "track": [
+ {
+ "@type": "General",
+ "FileExtension": "dmg",
+ "FileSize": "25705094",
+ "StreamSize": "25705094",
+ "File_Modified_Date": "2023-10-02 07:26:41 UTC",
+ "File_Modified_Date_Local": "2023-10-02 15:26:41"
+ }
+ ]
+ }
+}
+
diff --git a/mediaInfo/image.json b/mediaInfo/image.json
new file mode 100644
index 0000000..fc9bffb
--- /dev/null
+++ b/mediaInfo/image.json
@@ -0,0 +1,33 @@
+{
+ "creatingLibrary": {
+ "name": "MediaInfoLib",
+ "version": "23.03",
+ "url": "https://mediaarea.net/MediaInfo"
+ },
+ "media": {
+ "@ref": "WechatIMG30.jpg",
+ "track": [
+ {
+ "@type": "General",
+ "ImageCount": "1",
+ "FileExtension": "jpg",
+ "Format": "JPEG",
+ "FileSize": "375472",
+ "StreamSize": "0",
+ "File_Modified_Date": "2023-10-09 08:10:40 UTC",
+ "File_Modified_Date_Local": "2023-10-09 16:10:40"
+ },
+ {
+ "@type": "Image",
+ "Format": "JPEG",
+ "Width": "2160",
+ "Height": "2880",
+ "ColorSpace": "YUV",
+ "ChromaSubsampling": "4:2:0",
+ "BitDepth": "8",
+ "Compression_Mode": "Lossy",
+ "StreamSize": "375472"
+ }
+ ]
+ }
+}
diff --git a/mediaInfo/unit_test.go b/mediaInfo/unit_test.go
new file mode 100644
index 0000000..51c5d2e
--- /dev/null
+++ b/mediaInfo/unit_test.go
@@ -0,0 +1,8 @@
+package mediaInfo
+
+import "testing"
+
+func TestAudio(t *testing.T) {
+ ret := GetAudioMedia("/Users/zen/Pictures/譚詠麟 - 水中花 [6LJ2mJU4BpI].m4a")
+ t.Logf("%+v\n", ret)
+}
diff --git a/mediaInfo/video.json b/mediaInfo/video.json
new file mode 100644
index 0000000..152490d
--- /dev/null
+++ b/mediaInfo/video.json
@@ -0,0 +1,116 @@
+{
+ "creatingLibrary": {
+ "name": "MediaInfoLib",
+ "version": "23.03",
+ "url": "https://mediaarea.net/MediaInfo"
+ },
+ "media": {
+ "@ref": "out.mp4",
+ "track": [
+ {
+ "@type": "General",
+ "VideoCount": "1",
+ "AudioCount": "1",
+ "FileExtension": "mp4",
+ "Format": "MPEG-4",
+ "Format_Profile": "Base Media",
+ "CodecID": "isom",
+ "CodecID_Compatible": "isom/iso2/mp41",
+ "FileSize": "7692732",
+ "Duration": "48.344",
+ "OverallBitRate": "1272999",
+ "FrameRate": "30.000",
+ "FrameCount": "1446",
+ "StreamSize": "57858",
+ "HeaderSize": "36",
+ "DataSize": "7635225",
+ "FooterSize": "57471",
+ "IsStreamable": "No",
+ "File_Modified_Date": "2023-07-18 10:17:03 UTC",
+ "File_Modified_Date_Local": "2023-07-18 18:17:03",
+ "Encoded_Application": "Lavf60.3.100"
+ },
+ {
+ "@type": "Video",
+ "StreamOrder": "0",
+ "ID": "1",
+ "Format": "HEVC",
+ "Format_Profile": "Main",
+ "Format_Level": "4",
+ "Format_Tier": "Main",
+ "CodecID": "hvc1",
+ "Duration": "48.200",
+ "BitRate": "1138539",
+ "Width": "1080",
+ "Height": "1920",
+ "Sampled_Width": "1080",
+ "Sampled_Height": "1920",
+ "PixelAspectRatio": "1.000",
+ "DisplayAspectRatio": "0.562",
+ "Rotation": "0.000",
+ "FrameRate_Mode": "CFR",
+ "FrameRate": "30.000",
+ "FrameCount": "1446",
+ "ColorSpace": "YUV",
+ "ChromaSubsampling": "4:2:0",
+ "ChromaSubsampling_Position": "Type 0",
+ "BitDepth": "8",
+ "ScanType": "Progressive",
+ "StreamSize": "6859701",
+ "Title": "VideoHandle",
+ "Encoded_Library": "x265 - 3.5+1-f0c1022b6:[Mac OS X][clang 13.1.6][64 bit] 8bit+10bit+12bit",
+ "Encoded_Library_Name": "x265",
+ "Encoded_Library_Version": "3.5+1-f0c1022b6:[Mac OS X][clang 13.1.6][64 bit] 8bit+10bit+12bit",
+ "Encoded_Library_Settings": "cpuid=2 / frame-threads=3 / wpp / no-pmode / no-pme / no-psnr / no-ssim / log-level=2 / input-csp=1 / input-res=1080x1920 / interlace=0 / total-frames=0 / level-idc=0 / high-tier=1 / uhd-bd=0 / ref=3 / no-allow-non-conformance / no-repeat-headers / annexb / no-aud / no-hrd / info / hash=0 / no-temporal-layers / open-gop / min-keyint=25 / keyint=250 / gop-lookahead=0 / bframes=4 / b-adapt=2 / b-pyramid / bframe-bias=0 / rc-lookahead=20 / lookahead-slices=8 / scenecut=40 / hist-scenecut=0 / radl=0 / no-splice / no-intra-refresh / ctu=64 / min-cu-size=8 / no-rect / no-amp / max-tu-size=32 / tu-inter-depth=1 / tu-intra-depth=1 / limit-tu=0 / rdoq-level=0 / dynamic-rd=0.00 / no-ssim-rd / signhide / no-tskip / nr-intra=0 / nr-inter=0 / no-constrained-intra / strong-intra-smoothing / max-merge=3 / limit-refs=1 / no-limit-modes / me=1 / subme=2 / merange=57 / temporal-mvp / no-frame-dup / no-hme / weightp / no-weightb / no-analyze-src-pics / deblock=0:0 / sao / no-sao-non-deblock / rd=3 / selective-sao=4 / early-skip / rskip / no-fast-intra / no-tskip-fast / no-cu-lossless / b-intra / no-splitrd-skip / rdpenalty=0 / psy-rd=2.00 / psy-rdoq=0.00 / no-rd-refine / no-lossless / cbqpoffs=0 / crqpoffs=0 / rc=crf / crf=28.0 / qcomp=0.60 / qpstep=4 / stats-write=0 / stats-read=0 / ipratio=1.40 / pbratio=1.30 / aq-mode=2 / aq-strength=1.00 / cutree / zone-count=0 / no-strict-cbr / qg-size=32 / no-rc-grain / qpmax=69 / qpmin=0 / no-const-vbv / sar=0 / overscan=0 / videoformat=5 / range=0 / colorprim=5 / transfer=6 / colormatrix=6 / chromaloc=1 / chromaloc-top=0 / chromaloc-bottom=0 / display-window=0 / cll=0,0 / min-luma=0 / max-luma=255 / log2-max-poc-lsb=8 / vui-timing-info / vui-hrd-info / slices=1 / no-opt-qp-pps / no-opt-ref-list-length-pps / no-multi-pass-opt-rps / scenecut-bias=0.05 / hist-threshold=0.03 / no-opt-cu-delta-qp / no-aq-motion / no-hdr10 / no-hdr10-opt / no-dhdr10-opt / no-idr-recovery-sei / analysis-reuse-level=0 / analysis-save-reuse-level=0 / analysis-load-reuse-level=0 / scale-factor=0 / refine-intra=0 / refine-inter=0 / refine-mv=1 / refine-ctu-distortion=0 / no-limit-sao / ctu-info=0 / no-lowpass-dct / refine-analysis-type=0 / copy-pic=1 / max-ausize-factor=1.0 / no-dynamic-refine / no-single-sei / no-hevc-aq / no-svt / no-field / qp-adaptation-range=1.00 / scenecut-aware-qp=0conformance-window-offsets / right=0 / bottom=0 / decoder-max-rate=0 / no-vbv-live-multi-pass",
+ "Language": "en",
+ "colour_description_present": "Yes",
+ "colour_description_present_Source": "Container / Stream",
+ "colour_range": "Limited",
+ "colour_range_Source": "Container / Stream",
+ "colour_primaries": "BT.601 PAL",
+ "colour_primaries_Source": "Container / Stream",
+ "transfer_characteristics": "BT.601",
+ "transfer_characteristics_Source": "Container / Stream",
+ "matrix_coefficients": "BT.601",
+ "matrix_coefficients_Source": "Container / Stream",
+ "extra": {
+ "CodecConfigurationBox": "hvcC"
+ }
+ },
+ {
+ "@type": "Audio",
+ "StreamOrder": "1",
+ "ID": "2",
+ "Format": "AAC",
+ "Format_Settings_SBR": "No (Explicit)",
+ "Format_AdditionalFeatures": "LC",
+ "CodecID": "mp4a-40-2",
+ "Duration": "48.344",
+ "Source_Duration": "48.367",
+ "BitRate_Mode": "CBR",
+ "BitRate": "128271",
+ "Channels": "2",
+ "ChannelPositions": "Front: L R",
+ "ChannelLayout": "L R",
+ "SamplesPerFrame": "1024",
+ "SamplingRate": "44100",
+ "SamplingCount": "2131970",
+ "FrameRate": "43.066",
+ "FrameCount": "2082",
+ "Source_FrameCount": "2083",
+ "Compression_Mode": "Lossy",
+ "StreamSize": "775173",
+ "Source_StreamSize": "775516",
+ "Title": "SoundHandle",
+ "Language": "en",
+ "Default": "Yes",
+ "AlternateGroup": "1",
+ "extra": {
+ "Source_Delay": "-23",
+ "Source_Delay_Source": "Container"
+ }
+ }
+ ]
+ }
+}
+
diff --git a/merge/extractAAC.go b/merge/extractAAC.go
new file mode 100644
index 0000000..da0cd71
--- /dev/null
+++ b/merge/extractAAC.go
@@ -0,0 +1,61 @@
+package merge
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/mediaInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strings"
+)
+
+func ExtractAAC(rootPath string) {
+ roots := getall(rootPath)
+ slog.Debug("根目录", slog.Any("roots", roots))
+ for _, root := range roots {
+ slog.Info("1", slog.String("1", root))
+ secs := getall(root)
+ for _, sec := range secs {
+ slog.Info("2", slog.String("2", sec))
+ entry := strings.Join([]string{sec, "entry.json"}, string(os.PathSeparator))
+ name := getName(entry)
+ name = replace.ForFileName(name)
+ name = CutName(name)
+ slog.Info("entry", slog.String("获取到的文件名", name))
+ thirds := getall(sec)
+ for _, third := range thirds {
+ slog.Info("3", slog.String("3", third))
+ video := strings.Join([]string{third, "video.m4s"}, string(os.PathSeparator))
+ audio := strings.Join([]string{third, "audio.m4s"}, string(os.PathSeparator))
+ fname := strings.Join([]string{name, "aac"}, ".")
+ if isExist("/sdcard/Movies") {
+ os.Mkdir("/sdcard/Movies/bili", 0777)
+ fname = strings.Join([]string{"/sdcard/Movies/bili", fname}, string(os.PathSeparator))
+ } else {
+ fname = strings.Join([]string{rootPath, fname}, string(os.PathSeparator))
+ }
+ slog.Info("最终名称", slog.String("文件名", fname), slog.String("音频", audio))
+ vInfo := GetFileInfo.GetFileInfo(video)
+ mi, ok := vInfo.MediaInfo.(mediaInfo.VideoInfo)
+ if ok {
+ slog.Debug("断言视频mediainfo结构体成功", slog.Any("MediainfoVideo结构体", mi))
+ } else {
+ slog.Warn("断言视频mediainfo结构体失败")
+ }
+ slog.Info("WARNING", slog.String("vTAG", mi.VideoCodecID))
+ cmd := exec.Command("ffmpeg", "-i", audio, "-c:a", "copy", "-ac", "1", fname)
+ if mi.VideoCodecID == "avc1" {
+ cmd = exec.Command("ffmpeg", "-i", video, "-i", audio, "-c:v", "copy", "-c:a", "copy", "-ac", "1", fname)
+ }
+ err := util.ExecCommand(cmd)
+ if err != nil {
+ slog.Warn("哔哩哔哩合成出错", slog.Any("错误原文", err), slog.Any("命令原文", fmt.Sprint(cmd)))
+ continue
+ }
+ }
+ }
+ }
+}
diff --git a/merge/merge.go b/merge/merge.go
new file mode 100644
index 0000000..079d06c
--- /dev/null
+++ b/merge/merge.go
@@ -0,0 +1,234 @@
+package merge
+
+import (
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/mediaInfo"
+ "processAll/replace"
+ "processAll/util"
+ "regexp"
+ "strings"
+ "time"
+)
+
+type Entry struct {
+ MediaType int `json:"media_type"`
+ HasDashAudio bool `json:"has_dash_audio"`
+ IsCompleted bool `json:"is_completed"`
+ TotalBytes int `json:"total_bytes"`
+ DownloadedBytes int `json:"downloaded_bytes"`
+ Title string `json:"title"`
+ TypeTag string `json:"type_tag"`
+ Cover string `json:"cover"`
+ VideoQuality int `json:"video_quality"`
+ PreferedVideoQuality int `json:"prefered_video_quality"`
+ GuessedTotalBytes int `json:"guessed_total_bytes"`
+ TotalTimeMilli int `json:"total_time_milli"`
+ DanmakuCount int `json:"danmaku_count"`
+ TimeUpdateStamp int64 `json:"time_update_stamp"`
+ TimeCreateStamp int64 `json:"time_create_stamp"`
+ CanPlayInAdvance bool `json:"can_play_in_advance"`
+ InterruptTransformTempFile bool `json:"interrupt_transform_temp_file"`
+ QualityPithyDescription string `json:"quality_pithy_description"`
+ QualitySuperscript string `json:"quality_superscript"`
+ CacheVersionCode int `json:"cache_version_code"`
+ PreferredAudioQuality int `json:"preferred_audio_quality"`
+ AudioQuality int `json:"audio_quality"`
+ Avid int `json:"avid"`
+ Spid int `json:"spid"`
+ SeasionId int `json:"seasion_id"`
+ Bvid string `json:"bvid"`
+ OwnerId int `json:"owner_id"`
+ OwnerName string `json:"owner_name"`
+ OwnerAvatar string `json:"owner_avatar"`
+ PageData struct {
+ Cid int `json:"cid"`
+ Page int `json:"page"`
+ From string `json:"from"`
+ Part string `json:"part"`
+ Link string `json:"link"`
+ RichVid string `json:"rich_vid"`
+ Vid string `json:"vid"`
+ HasAlias bool `json:"has_alias"`
+ Weblink string `json:"weblink"`
+ Offsite string `json:"offsite"`
+ Tid int `json:"tid"`
+ Width int `json:"width"`
+ Height int `json:"height"`
+ Rotate int `json:"rotate"`
+ DownloadTitle string `json:"download_title"`
+ DownloadSubtitle string `json:"download_subtitle"`
+ } `json:"page_data"`
+}
+
+func Merge(rootPath string) {
+ roots := getall(rootPath)
+ slog.Debug("根目录", slog.Any("roots", roots))
+ for _, root := range roots {
+ slog.Info("1", slog.String("1", root))
+ secs := getall(root)
+ for _, sec := range secs {
+ slog.Info("2", slog.String("2", sec))
+ entry := strings.Join([]string{sec, "entry.json"}, string(os.PathSeparator))
+ name := getName(entry)
+ name = replace.ForFileName(name)
+ name = CutName(name)
+ slog.Info("entry", slog.String("获取到的文件名", name))
+ thirds := getall(sec)
+ for _, third := range thirds {
+ slog.Info("3", slog.String("3", third))
+ video := strings.Join([]string{third, "video.m4s"}, string(os.PathSeparator))
+ audio := strings.Join([]string{third, "audio.m4s"}, string(os.PathSeparator))
+ fname := strings.Join([]string{name, "mp4"}, ".")
+ if isExist("/sdcard/Movies") {
+ os.Mkdir("/sdcard/Movies/bili", 0777)
+ fname = strings.Join([]string{"/sdcard/Movies/bili", fname}, string(os.PathSeparator))
+ } else {
+ fname = strings.Join([]string{"/sdcard/Movies/bili", fname}, string(os.PathSeparator))
+ }
+ if isFileExist(fname) {
+ perfix := strings.Replace(fname, ".mp4", "", 1)
+ middle := strings.Join([]string{perfix, time.Now().Format("20060102")}, "-")
+ fname = strings.Join([]string{middle, "mp4"}, ".")
+ }
+ slog.Info("最终名称", slog.String("文件名", fname), slog.String("视频", video), slog.String("音频", audio))
+ vInfo := GetFileInfo.GetFileInfo(video)
+ mi, ok := vInfo.MediaInfo.(mediaInfo.VideoInfo)
+ if ok {
+ slog.Debug("断言视频mediainfo结构体成功", slog.Any("MediainfoVideo结构体", mi))
+ } else {
+ slog.Warn("断言视频mediainfo结构体失败")
+ }
+ slog.Info("WARNING", slog.String("vTAG", mi.VideoCodecID))
+ cmd := exec.Command("ffmpeg", "-i", video, "-i", audio, "-c:v", "copy", "-c:a", "copy", "-ac", "1", "-tag:v", "hvc1", fname)
+ if mi.VideoCodecID == "avc1" {
+ cmd = exec.Command("ffmpeg", "-i", video, "-i", audio, "-c:v", "copy", "-c:a", "copy", "-ac", "1", fname)
+ }
+ err := util.ExecCommand(cmd)
+ if err != nil {
+ slog.Warn("哔哩哔哩合成出错", slog.Any("错误原文", err), slog.Any("命令原文", fmt.Sprint(cmd)))
+ continue
+ }
+ if err = os.RemoveAll(sec); err != nil {
+ slog.Debug("删除失败", slog.String("目录名", sec), slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("删除成功", slog.String("目录名", sec))
+ }
+ }
+ }
+ }
+}
+
+func isDir(path string) bool {
+ fileInfo, _ := os.Stat(path)
+ if fileInfo.IsDir() {
+ return true
+ } else {
+ return false
+ }
+}
+
+func getall(rootPath string) (realFolders []string) {
+ folders, _ := os.ReadDir(rootPath)
+ for _, folder := range folders {
+ folderPath := strings.Join([]string{rootPath, folder.Name()}, string(os.PathSeparator))
+ if isDir(folderPath) {
+ realFolders = append(realFolders, folderPath)
+ }
+ }
+ return realFolders
+}
+
+/*
+解析并返回文件名和entry原始文件
+*/
+func getName(jackson string) (name string) {
+ var entry Entry
+ file, err := os.ReadFile(jackson)
+ if err != nil {
+ return
+ }
+ err = json.Unmarshal(file, &entry)
+ if err != nil {
+ return
+ }
+ if entry.PageData.DownloadSubtitle != "" {
+ //name = strings.Join([]string{entry.Title, entry.PageData.DownloadSubtitle}, "-")
+ name = entry.PageData.DownloadSubtitle
+ } else {
+ name = entry.Title
+ }
+ name = replace.ForFileName(name)
+ return name
+}
+
+// func ForFileName(name string) string {
+// nStr := ""
+// for _, v := range name {
+// if Effective(string(v)) {
+// // fmt.Printf("%d\t有效%v\n", i, string(v))
+// nStr = strings.Join([]string{nStr, string(v)}, "")
+// }
+// }
+// slog.Debug("正则表达式匹配数字字母汉字", slog.String("文件名", nStr))
+// return nStr
+// }
+func Effective(s string) bool {
+ num := regexp.MustCompile(`\d`) // 匹配任意一个数字
+ letter := regexp.MustCompile(`[a-zA-Z]`) // 匹配任意一个字母
+ char := regexp.MustCompile(`[\p{Han}]`) // 匹配任意一个汉字
+ if num.MatchString(s) || letter.MatchString(s) || char.MatchString(s) {
+ return true
+ }
+ return false
+}
+
+/*
+判断路径是否存在
+*/
+func isExist(path string) bool {
+ if _, err := os.Stat(path); err == nil {
+ fmt.Println("路径存在")
+ return true
+ } else if os.IsNotExist(err) {
+ fmt.Println("路径不存在")
+ return false
+ } else {
+ fmt.Println("发生错误:", err)
+ return false
+ }
+}
+
+/*
+判断文件是否存在
+*/
+func isFileExist(fp string) bool {
+ if _, err := os.Stat(fp); os.IsNotExist(err) {
+ return false
+ } else {
+ return true
+ }
+}
+
+/*
+截取合理长度的标题
+*/
+func CutName(before string) (after string) {
+ for i, char := range before {
+ slog.Debug(fmt.Sprintf("第%d个字符:%v\n", i+1, string(char)))
+ if i >= 124 {
+ slog.Debug("截取124之前的完整字符")
+ break
+ } else {
+ before = strings.Join([]string{before, string(char)}, "")
+ }
+ }
+ slog.Debug(fmt.Sprintf("截取的完整字符:%before\n", after))
+ slog.Debug("截取后", slog.String("字符串", after))
+ return after
+}
diff --git a/merge/merge_test.go b/merge/merge_test.go
new file mode 100644
index 0000000..f6d0fb2
--- /dev/null
+++ b/merge/merge_test.go
@@ -0,0 +1,7 @@
+package merge
+
+import "testing"
+
+func TestMerge(t *testing.T) {
+ Merge("/media/zen/53F4-E16B/bilibili")
+}
diff --git a/model/audio.go b/model/audio.go
new file mode 100644
index 0000000..bec06d0
--- /dev/null
+++ b/model/audio.go
@@ -0,0 +1,74 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Audio struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT" json:"id"`
+ TaskId int64 `xorm:"not null comment('任务id') INT" json:"TaskId"`
+ SrcName string `xorm:"comment('源文件名') VARCHAR(255)" json:"SrcName"`
+ DstName string `xorm:"comment('目标文件名') VARCHAR(255)" json:"DstName"`
+ SrcType string `xorm:"comment('源文件类型') VARCHAR(255)" json:"SrcType"`
+ DstType string `xorm:"comment('目标文件类型') VARCHAR(255)" json:"DstTypeType"`
+ SrcSize int64 `xorm:"comment('源文件大小') BIGINT" json:"SrcSize"`
+ DstSize int64 `xorm:"comment('目标文件大小') BIGINT" json:"DstSize"`
+ SrcMD5 string `xorm:"comment('源文件MD5') VARCHAR(255)" json:"SrcMD5"`
+ DstMD5 string `xorm:"comment('目标文件MD5') VARCHAR(255)" json:"DstMD5"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncAudio() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Audio))
+ if err != nil {
+ slog.Error("同步音频数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步音频数据表成功")
+ }
+ }
+}
+
+func (a *Audio) GetAll() (audios []Audio, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(audios)
+ if err != nil {
+ return nil, err
+ }
+ return audios, nil
+ }
+ return nil, nil
+}
+
+func (a *Audio) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(a)
+ }
+ return 0, nil
+}
+
+func (a *Audio) FindById() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(a.Id).Get(a)
+ }
+ return false, nil
+}
+
+func (a *Audio) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(a.Id).Update(a)
+ }
+ return 0, nil
+}
+func (a *Audio) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(a)
+ }
+ return 0, nil
+}
diff --git a/model/av.go b/model/av.go
new file mode 100644
index 0000000..8e42e70
--- /dev/null
+++ b/model/av.go
@@ -0,0 +1,78 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type AV struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT" json:"id"`
+ TaskId int64 `xorm:"comment('任务id') INT" json:"TaskId"`
+ Entry []byte `xorm:"comment('原始entry文件') Text" json:"Entry"`
+ FinalName string `xorm:"comment('最终生成视频的全路径') VARCHAR(255)" json:"FinalName"`
+ Video string `xorm:"comment('视频文件的绝对路径') VARCHAR(255)" json:"VideoName"`
+ Audio string `xorm:"comment('音频文件的绝对路径') VARCHAR(255)" json:"AudioName"`
+ FileName string `xorm:"comment('最终文件名') VARCHAR(255)" json:"FileName"`
+ DelName string `xorm:"comment('标记删除的目录') VARCHAR(255)" json:"DelName"`
+ FileSize float64 `xorm:"comment('视频文件大小') Double" json:"FileSize"`
+ MD5 string `xorm:"comment('视频MD5') VARCHAR(255)" json:"MD5"`
+ Code string `xorm:"comment('视频编码') VARCHAR(255)" json:"Code"`
+ Width int `xorm:"comment('视频宽度') INT" json:"Width"`
+ Height int `xorm:"comment('视频高度') INT" json:"Height"`
+ VTag string `xorm:"comment('视频标签') VARCHAR(255)" json:"VTag"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncAV() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(AV))
+ if err != nil {
+ slog.Error("同步哔哩哔哩数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步哔哩哔哩数据表成功")
+ }
+ }
+}
+
+func (av *AV) GetAll() (AVs []AV, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(AVs)
+ if err != nil {
+ return nil, err
+ }
+ return AVs, nil
+ }
+ return nil, nil
+}
+
+func (av *AV) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(av)
+ }
+ return 0, nil
+}
+
+func (av *AV) FindById() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(av.Id).Get(av)
+ }
+ return false, nil
+}
+
+func (av *AV) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(av.Id).Update(av)
+ }
+ return 0, nil
+}
+func (av *AV) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(av)
+ }
+ return 0, nil
+}
diff --git a/model/err.go b/model/err.go
new file mode 100644
index 0000000..bff57b2
--- /dev/null
+++ b/model/err.go
@@ -0,0 +1,55 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Err struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT(11)" json:"id"`
+ FullPath string `xorm:"comment('出错文件的全路径') VARCHAR(255)" json:"FullPath"`
+ Reason string `xorm:"comment('错误文本') VARCHAR(255)" json:"Reason"`
+ TTY string `xorm:"comment('控制台内容') TEXT" json:"tty"`
+ Warn string `xorm:"comment('日志内容') TEXT" json:"content"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncErr() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Err))
+ if err != nil {
+ slog.Error("同步错误数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步错误数据表成功")
+ }
+ }
+}
+
+func (e *Err) GetAll() (es []Err, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(es)
+ if err != nil {
+ return nil, err
+ }
+ return es, nil
+ }
+ return nil, nil
+}
+
+func (e *Err) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(e)
+ }
+ return 0, nil
+}
+func (e *Err) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(e)
+ }
+ return 0, nil
+}
diff --git a/model/file.go b/model/file.go
new file mode 100644
index 0000000..c08c3c1
--- /dev/null
+++ b/model/file.go
@@ -0,0 +1,82 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type File struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT(11)" json:"id"`
+ Name string `xorm:"comment('文件名') VARCHAR(255)" json:"name"`
+ Type string `xorm:"comment('文件类型') VARCHAR(255)" json:"type"`
+ Size int64 `xorm:"comment('文件大小') BIGINT" json:"size"`
+ MD5 string `xorm:"comment('文件MD5') VARCHAR(255)" json:"MD5"`
+ IsVideo bool `xorm:"comment('是否为视频文件') BOOL" json:"isvideo"`
+ Frame string `xorm:"comment('文件大致帧数') VARCHAR(255)" json:"frame"`
+ Code string `xorm:"comment('编码') VARCHAR(255)" json:"code"`
+ Width int `xorm:"comment('宽度') INT" json:"width"`
+ Height int `xorm:"comment('高度') INT" json:"height"`
+ VTag string `xorm:"comment('视频标签') VARCHAR(255)" json:"vtag"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncFile() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(File))
+ if err != nil {
+ slog.Error("同步文件数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步文件数据表成功")
+ }
+ }
+}
+
+func (f *File) GetAll() (files []File, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(files)
+ if err != nil {
+ return nil, err
+ }
+ return files, nil
+ }
+ return nil, nil
+}
+
+func (f *File) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(f)
+ }
+ return 0, nil
+}
+
+func (f *File) FindById() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(f.Id).Get(f)
+ }
+ return false, nil
+}
+
+func (f *File) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(f.Id).Update(f)
+ }
+ return 0, nil
+}
+
+func (f *File) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(f)
+ }
+ return 0, nil
+}
+func (f *File) InsertAll(fs []*File) (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(&fs)
+ }
+ return 0, nil
+}
diff --git a/model/image.go b/model/image.go
new file mode 100644
index 0000000..f622392
--- /dev/null
+++ b/model/image.go
@@ -0,0 +1,76 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Image struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT(11)" json:"id"`
+ TaskId int64 `xorm:"not null pk comment('任务id') INT" json:"TaskId"`
+ ToType bool `xorm:"comment('静态0fales|动态1true')BOOL" json:"ToType"`
+ SrcName string `xorm:"comment('源文件名') VARCHAR(255)" json:"SrcName"`
+ DstName string `xorm:"comment('目标文件名') VARCHAR(255)" json:"DstName"`
+ SrcType string `xorm:"comment('源文件类型') VARCHAR(255)" json:"SrcType"`
+ DstType string `xorm:"comment('目标文件类型') VARCHAR(255)" json:"DstTypeType"`
+ SrcSize int64 `xorm:"comment('源文件大小') BIGINT" json:"SrcSize"`
+ DstSize int64 `xorm:"comment('目标文件大小') BIGINT" json:"DstSize"`
+ SavedSize float64 `xorm:"comment('节省的磁盘空间(MB)') Float" json:"SavedSize"`
+ SrcMD5 string `xorm:"comment('源文件MD5') VARCHAR(255)" json:"SrcMD5"`
+ DstMD5 string `xorm:"comment('目标文件MD5') VARCHAR(255)" json:"DstMD5"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncImage() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Image))
+ if err != nil {
+ slog.Error("同步图片数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步图片数据表成功")
+ }
+ }
+}
+
+func (i *Image) GetAll() (images []Image, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(images)
+ if err != nil {
+ return nil, err
+ }
+ return images, nil
+ }
+ return nil, nil
+}
+
+func (i *Image) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(i)
+ }
+ return 0, nil
+}
+
+func (i *Image) FindById() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(i.Id).Get(i)
+ }
+ return false, nil
+}
+
+func (i *Image) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(i.Id).Update(i)
+ }
+ return 0, nil
+}
+func (i *Image) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(i)
+ }
+ return 0, nil
+}
diff --git a/model/louder.go b/model/louder.go
new file mode 100644
index 0000000..beb6fd3
--- /dev/null
+++ b/model/louder.go
@@ -0,0 +1,76 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Louder struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT" json:"id"`
+ TaskId int64 `xorm:"not null comment('任务id') INT" json:"TaskId"`
+ SrcName string `xorm:"comment('增大电平前文件名') VARCHAR(255)" json:"SrcName"`
+ DstName string `xorm:"comment('增大电平后文件名') VARCHAR(255)" json:"DstName"`
+ SrcType string `xorm:"comment('增大电平前文件类型') VARCHAR(255)" json:"SrcType"`
+ DstType string `xorm:"comment('增大电平后文件类型') VARCHAR(255)" json:"DstTypeType"`
+ SrcSize int64 `xorm:"comment('增大电平前文件大小') BIGINT" json:"SrcSize"`
+ DstSize int64 `xorm:"comment('增大电平后文件大小') BIGINT" json:"DstSize"`
+ SrcMD5 string `xorm:"comment('增大电平前文件MD5') VARCHAR(255)" json:"SrcMD5"`
+ DstMD5 string `xorm:"comment('增大电平后文件MD5') VARCHAR(255)" json:"DstMD5"`
+ Multiple string `xorm:"comment('增大电平倍数') VARCHAR(255)" json:"Multiple"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncLouder() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Louder))
+ if err != nil {
+ slog.Error("同步音频放大数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步音频放大数据表成功")
+ }
+ }
+}
+
+func (l *Louder) GetAll() (louders []Louder, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(louders)
+ if err != nil {
+ return nil, err
+ }
+ return louders, nil
+ }
+ return nil, nil
+}
+
+func (l *Louder) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(l)
+ }
+ return 0, nil
+}
+
+func (l *Louder) FindByTaskId() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Where("taskid = ?", l.TaskId).Get(l)
+ }
+ return false, nil
+}
+
+func (l *Louder) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(l.Id).Update(l)
+ }
+ return 0, nil
+}
+
+func (l *Louder) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(l)
+ }
+ return 0, nil
+}
diff --git a/model/mytest.go b/model/mytest.go
new file mode 100644
index 0000000..5bd0a97
--- /dev/null
+++ b/model/mytest.go
@@ -0,0 +1,41 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Custom struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT(11)" json:"id"`
+ FileName string `xorm:"comment('文件名') VARCHAR(255)" json:"SrcName"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncCustom() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Custom))
+ if err != nil {
+ slog.Error("同步测试数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步测试数据表成功")
+ }
+ }
+}
+
+func (c *Custom) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(c)
+ }
+ return 0, nil
+}
+func (c *Custom) InsertAll(cs []*Custom) (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(&cs)
+ }
+ return 0, nil
+}
diff --git a/model/save.go b/model/save.go
new file mode 100644
index 0000000..e2740f3
--- /dev/null
+++ b/model/save.go
@@ -0,0 +1,64 @@
+package model
+
+import (
+ "fmt"
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Save struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT(11)" json:"id"`
+ SrcSize uint64 `xorm:"comment('源文件大小 字节') Double" json:"src_size"`
+ SrcName string `xorm:"comment('源文件名') Varchar(255)" json:"src_name"`
+ DstSize uint64 `xorm:"comment('目标文件大小 字节') Double" json:"dst_size"`
+ DstName string `xorm:"comment('目标文件名') Varchar(255)" json:"dst_name"`
+ Size float64 `xorm:"comment('节省的空间 GB') Double" json:"save"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+// select sum(size)from Save;
+func SyncSave() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Save))
+ if err != nil {
+ slog.Error("同步节省空间数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步节省空间数据表成功")
+ }
+ }
+}
+
+func (s *Save) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(s)
+ }
+ return 0, nil
+}
+func (s *Save) InsertAll(ss []*Save) (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(&ss)
+ }
+ return 0, nil
+}
+
+func (s *Save) SumSaveAll() (string, error) {
+ var sum string
+ sql := "SELECT SUM(size) as sum FROM save;"
+ if util.GetVal("mysql", "switch") == "on" {
+ if results, err := mysql.GetSession().Query(sql); err != nil {
+ fmt.Println("获取总节省空间有错误产生")
+ return "", err
+ } else {
+ //fmt.Printf("sum: %+v\n", string(results[0]["sum"]))
+ sum = string(results[0]["sum"])
+ return sum, nil
+ }
+ }
+ //slog.Debug("求和命令", slog.Any("result", results), slog.Any("err", err))
+ return "", nil
+}
diff --git a/model/speedUp.go b/model/speedUp.go
new file mode 100644
index 0000000..3dbb7d8
--- /dev/null
+++ b/model/speedUp.go
@@ -0,0 +1,76 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Speed struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT" json:"id"`
+ TaskId int64 `xorm:"not null comment('任务id') INT" json:"TaskId"`
+ SrcName string `xorm:"comment('加速前文件名') VARCHAR(255)" json:"SrcName"`
+ DstName string `xorm:"comment('加速后文件名') VARCHAR(255)" json:"DstName"`
+ SrcType string `xorm:"comment('加速前文件类型') VARCHAR(255)" json:"SrcType"`
+ DstType string `xorm:"comment('加速后文件类型') VARCHAR(255)" json:"DstTypeType"`
+ SrcSize int64 `xorm:"comment('加速前文件大小') BIGINT" json:"SrcSize"`
+ DstSize int64 `xorm:"comment('加速后文件大小') BIGINT" json:"DstSize"`
+ SrcMD5 string `xorm:"comment('加速前文件MD5') VARCHAR(255)" json:"SrcMD5"`
+ DstMD5 string `xorm:"comment('加速后文件MD5') VARCHAR(255)" json:"DstMD5"`
+ Multiple string `xorm:"comment('加速倍数') VARCHAR(255)" json:"Multiple"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncSpeed() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Speed))
+ if err != nil {
+ slog.Error("同步音频加速数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步音频加速数据表成功")
+ }
+ }
+}
+
+func (s *Speed) GetAll() (speeds []Speed, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(speeds)
+ if err != nil {
+ return nil, err
+ }
+ return speeds, nil
+ }
+ return nil, nil
+}
+
+func (s *Speed) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(s)
+ }
+ return 0, nil
+}
+
+func (s *Speed) FindByTaskId() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Where("taskid = ?", s.TaskId).Get(s)
+ }
+ return false, nil
+}
+
+func (s *Speed) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(s.Id).Update(s)
+ }
+ return 0, nil
+}
+
+func (s *Speed) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(s)
+ }
+ return 0, nil
+}
diff --git a/model/task.go b/model/task.go
new file mode 100644
index 0000000..690050f
--- /dev/null
+++ b/model/task.go
@@ -0,0 +1,39 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/information"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+/*
+主任务表
+*/
+
+type Task struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT" json:"id"`
+ TaskType string `xorm:"comment('任务类型') VARCHAR(255)" json:"TaskType"`
+ MachineType string `xorm:"comment('执行的机器类型') VARCHAR(255)" json:"MachineType"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncTask() {
+ err := mysql.GetSession().Sync2(new(Task))
+ if err != nil {
+ slog.Error("同步主任务数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步主任务数据表成功")
+ }
+}
+func (t *Task) CreateOne() (int64, error) {
+ t.MachineType = information.GetMachineInfo()
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(t)
+ }
+ return 0, nil
+}
diff --git a/model/telegram.go b/model/telegram.go
new file mode 100644
index 0000000..c58761e
--- /dev/null
+++ b/model/telegram.go
@@ -0,0 +1,69 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Telegraph struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT(11)" json:"id"`
+ Name string `xorm:"comment('文件名') VARCHAR(255)" json:"name"`
+ Shell string `xorm:"comment('下载命令') VARCHAR(255)" json:"shell"`
+ Url string `xorm:"comment('url') VARCHAR(255)" json:"url"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncTelegraph() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Telegraph))
+ if err != nil {
+ slog.Error("同步电报图片数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步电报图片数据表成功")
+ }
+ }
+}
+
+func (t *Telegraph) GetAll() (telegraphs []Telegraph, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(telegraphs)
+ if err != nil {
+ return nil, err
+ }
+ return telegraphs, nil
+ }
+ return nil, nil
+}
+
+func (t *Telegraph) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(t)
+ }
+ return 0, nil
+}
+
+func (t *Telegraph) FindById() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(t.Id).Get(t)
+ }
+ return false, nil
+}
+
+func (t *Telegraph) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(t.Id).Update(t)
+ }
+ return 0, nil
+}
+
+func (t *Telegraph) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(t)
+ }
+ return 0, nil
+}
diff --git a/model/text.go b/model/text.go
new file mode 100644
index 0000000..9238124
--- /dev/null
+++ b/model/text.go
@@ -0,0 +1,64 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Text struct {
+ Id int64 `xorm:"pk autoincr comment('主键id') INT(11)" json:"id"`
+ SrcName string `xorm:"comment('源文件名') VARCHAR(255)" json:"SrcName"`
+ DstName string `xorm:"comment('目标文件名') VARCHAR(255)" json:"DstName"`
+ SrcType string `xorm:"comment('源文件类型') VARCHAR(255)" json:"SrcType"`
+ DstType string `xorm:"comment('目标文件类型') VARCHAR(255)" json:"DstTypeType"`
+ SrcSize int64 `xorm:"comment('源文件大小') BIGINT" json:"SrcSize"`
+ DstSize int64 `xorm:"comment('目标文件大小') BIGINT" json:"DstSize"`
+ SrcMD5 string `xorm:"comment('源文件MD5') VARCHAR(255)" json:"SrcMD5"`
+ DstMD5 string `xorm:"comment('目标文件MD5') VARCHAR(255)" json:"DstMD5"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncText() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Text))
+ if err != nil {
+ slog.Error("同步文字编码转换数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步文字编码转换数据表成功")
+ }
+ }
+}
+
+func (t *Text) GetAll() (texts []Text, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(texts)
+ if err != nil {
+ return nil, err
+ }
+ return texts, nil
+ }
+ return nil, nil
+}
+func (t *Text) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(t)
+ }
+ return 0, nil
+}
+func (t *Text) FindById() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(t.Id).Get(t)
+ }
+ return false, nil
+}
+func (t *Text) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(t.Id).Update(t)
+ }
+ return 0, nil
+}
diff --git a/model/unit_test.go b/model/unit_test.go
new file mode 100644
index 0000000..800c714
--- /dev/null
+++ b/model/unit_test.go
@@ -0,0 +1,22 @@
+package model
+
+import (
+ "fmt"
+ "processAll/storage/mysql"
+ "testing"
+)
+
+func init() {
+ mysql.SetEngine()
+ SyncSave()
+ SyncYtdlp()
+}
+func TestMysql(t *testing.T) {
+ s := new(Save)
+ all, err := s.SumSaveAll()
+ if err != nil {
+ return
+ } else {
+ fmt.Printf("节省的空间:%v GB\n", all)
+ }
+}
diff --git a/model/video.go b/model/video.go
new file mode 100644
index 0000000..9356607
--- /dev/null
+++ b/model/video.go
@@ -0,0 +1,87 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Video struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT(11)" json:"id"`
+ TaskId int64 `xorm:"not null comment('任务id') INT" json:"TaskId"`
+ Frame string `xorm:"comment('预计文件帧数') VARCHAR(255)" json:"frame"`
+ SrcName string `xorm:"comment('源文件名') VARCHAR(255)" json:"SrcName"`
+ DstName string `xorm:"comment('目标文件名') VARCHAR(255)" json:"DstName"`
+ SrcType string `xorm:"comment('源文件类型') VARCHAR(255)" json:"SrcType"`
+ DstType string `xorm:"comment('目标文件类型') VARCHAR(255)" json:"DstTypeType"`
+ SrcSize int64 `xorm:"comment('源文件大小') BIGINT" json:"SrcSize"`
+ DstSize int64 `xorm:"comment('目标文件大小') BIGINT" json:"DstSize"`
+ SavedSize float64 `xorm:"comment('节省的磁盘空间(MB)') Float" json:"SavedSize"`
+ SrcMD5 string `xorm:"comment('源文件MD5') VARCHAR(255)" json:"SrcMD5"`
+ DstMD5 string `xorm:"comment('目标文件MD5') VARCHAR(255)" json:"DstMD5"`
+ SrcCode string `xorm:"comment('源编码') VARCHAR(255)" json:"SrcCode"`
+ DstCode string `xorm:"comment('目标编码') VARCHAR(255)" json:"DstCode"`
+ SrcWidth int `xorm:"comment('源宽度') INT" json:"SrcWidth"`
+ DstWidth int `xorm:"comment('目标宽度') INT" json:"DstWidth"`
+ SrcHeight int `xorm:"comment('源高度') INT" json:"SrcHeight"`
+ DstHeight int `xorm:"comment('目标高度') INT" json:"DstHeight"`
+ SrcVTag string `xorm:"comment('源视频标签') VARCHAR(255)" json:"SrcVTag"`
+ DstVTag string `xorm:"comment('目标视频标签') VARCHAR(255)" json:"DstVTag"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncVideo() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Video))
+ if err != nil {
+ slog.Error("同步视频数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步视频数据表成功")
+ }
+ }
+}
+
+func (v *Video) GetAll() (videos []Video, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(videos)
+ if err != nil {
+ return nil, err
+ }
+ return videos, nil
+ }
+ return nil, nil
+}
+func (v *Video) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(v)
+ }
+ return 0, nil
+}
+func (v *Video) FindById() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(v.Id).Get(v)
+ }
+ return false, nil
+}
+func (v *Video) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(v.Id).Update(v)
+ }
+ return 0, nil
+}
+func (v *Video) UpdateByTaskId() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Where("task_id = ?", v.TaskId).Update(v)
+ }
+ return 0, nil
+}
+func (v *Video) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(v)
+ }
+ return 0, nil
+}
diff --git a/model/ytdlp.go b/model/ytdlp.go
new file mode 100644
index 0000000..4785e50
--- /dev/null
+++ b/model/ytdlp.go
@@ -0,0 +1,65 @@
+package model
+
+import (
+ "log/slog"
+ "processAll/storage/mysql"
+ "processAll/util"
+ "time"
+)
+
+type Ytdlp struct {
+ Id int64 `xorm:"not null pk autoincr comment('主键id') INT(11)" json:"id"`
+ URL string `xorm:"comment('下载链接') VARCHAR(255)" json:"url"`
+ Status string `xorm:"comment('下载状态') VARCHAR(255)" json:"Status"`
+ UpdateTime time.Time `xorm:"updated comment('更新时间') DateTime" json:"update_time"`
+ CreateTime time.Time `xorm:"created comment('创建时间') DateTime" json:"create_time"`
+ DeleteTime time.Time `xorm:"deleted comment('删除时间') DateTime" json:"delete_time"`
+}
+
+func SyncYtdlp() {
+ if util.GetVal("mysql", "switch") == "on" {
+ err := mysql.GetSession().Sync2(new(Ytdlp))
+ if err != nil {
+ slog.Error("同步数据表出错", slog.Any("错误原文", err))
+ return
+ } else {
+ slog.Debug("同步数据表成功")
+ }
+ }
+}
+
+func (y *Ytdlp) GetAll() (ytdlps []Ytdlp, err error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ err = mysql.GetSession().Find(ytdlps)
+ if err != nil {
+ return nil, err
+ }
+ return ytdlps, nil
+ }
+ return nil, nil
+}
+func (y *Ytdlp) InsertOne() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Insert(y)
+ }
+ return 0, nil
+}
+func (y *Ytdlp) FindById() (bool, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(y.Id).Get(y)
+ }
+ return false, nil
+}
+func (y *Ytdlp) UpdateById() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().ID(y.Id).Update(y)
+ }
+ return 0, nil
+}
+
+func (y *Ytdlp) Sum() (int64, error) {
+ if util.GetVal("mysql", "switch") == "on" {
+ return mysql.GetSession().Count(y)
+ }
+ return 0, nil
+}
diff --git a/offsite/offset.go b/offsite/offset.go
new file mode 100644
index 0000000..d414e8c
--- /dev/null
+++ b/offsite/offset.go
@@ -0,0 +1,43 @@
+package offsite
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "processAll/GetFileInfo"
+ "strconv"
+ "strings"
+)
+
+func offset(dir string, off int) {
+ names := GetFileInfo.GetAllFiles(dir)
+ for _, name := range names {
+ suffix := path.Ext(name)
+ prefix := strings.Trim(name, suffix)
+ old, _ := strconv.Atoi(prefix)
+ newer := strconv.Itoa(old + off)
+ nfname := strings.Join([]string{newer, suffix}, "")
+ before := strings.Join([]string{dir, name}, string(os.PathSeparator))
+ after := strings.Join([]string{dir, nfname}, string(os.PathSeparator))
+ fmt.Printf("旧文件名:%s\t新文件名:%s\n", before, after)
+ os.Rename(before, after)
+ }
+}
+func addZero(dir string) {
+ names := GetFileInfo.GetAllFiles(dir)
+ for _, name := range names {
+ //fmt.Println(name)
+ suffix := path.Ext(name)
+ prefix := strings.Trim(name, suffix)
+ if len(prefix) == 3 {
+ fmt.Printf("三位数%v\n", name)
+ newName := strings.Join([]string{"0", prefix}, "")
+ fmt.Printf("三位数补0后%v\n", newName)
+ newName = strings.Join([]string{newName, suffix}, "")
+ before := strings.Join([]string{dir, name}, string(os.PathSeparator))
+ after := strings.Join([]string{dir, newName}, string(os.PathSeparator))
+ fmt.Printf("源文件名%s\t新文件名%s\n", before, after)
+ os.Rename(before, after)
+ }
+ }
+}
diff --git a/offsite/unit_test.go b/offsite/unit_test.go
new file mode 100644
index 0000000..26265a2
--- /dev/null
+++ b/offsite/unit_test.go
@@ -0,0 +1,10 @@
+package offsite
+
+import "testing"
+
+func TestOffset(t *testing.T) {
+ offset("/Users/zen/Downloads/BaiduNetdisk", 2244)
+}
+func TestAddZero(t *testing.T) {
+ addZero("/Users/zen/Downloads/BaiduNetdisk")
+}
diff --git a/processAudio/aac.go b/processAudio/aac.go
new file mode 100644
index 0000000..f1b8ec6
--- /dev/null
+++ b/processAudio/aac.go
@@ -0,0 +1,41 @@
+package processAudio
+
+import (
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strings"
+)
+
+func Audio2AAC(in GetFileInfo.BasicInfo) {
+ // 执行转换
+ fname := replace.ForFileName(in.PurgeName)
+ out := strings.Join([]string{in.PurgePath, fname, ".aac"}, "")
+ cmd := exec.Command("ffmpeg", "-i", in.FullPath, "-ac", "1", out)
+ err := util.ExecCommand(cmd)
+
+ if err == nil {
+ if err = os.RemoveAll(in.FullPath); err != nil {
+ slog.Warn("删除失败", slog.String("源文件", in.FullPath), slog.Any("错误", err))
+ } else {
+ slog.Debug("删除成功", slog.String("源文件", in.FullPath))
+ }
+ }
+}
+
+func Audios2AAC(dir, pattern string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, in := range infos {
+ Audio2AAC(in)
+ }
+}
+
+func AllAudios2AAC(root, pattern string) {
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ for _, in := range infos {
+ Audio2AAC(in)
+ }
+}
diff --git a/processAudio/louder.go b/processAudio/louder.go
new file mode 100644
index 0000000..82401ca
--- /dev/null
+++ b/processAudio/louder.go
@@ -0,0 +1,63 @@
+package processAudio
+
+import (
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strings"
+)
+
+/*
+执行文件夹和子文件夹中音频增大电平
+*/
+func LouderAllAudios(root, pattern string) {
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ for _, in := range infos {
+ LouderAudio(in)
+ }
+}
+
+/*
+执行一个文件夹中音频增大电平
+*/
+func LouderAudios(dir, pattern string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, in := range infos {
+ LouderAudio(in)
+ }
+}
+
+/*
+单个音频增大电平的完整函数
+*/
+func LouderAudio(in GetFileInfo.BasicInfo) {
+ src := in.PurgePath //原文件目录 带有最后一个 /
+ dst := strings.Join([]string{src, "Louder"}, "") //目标文件目录
+ os.Mkdir(dst, 0777)
+ fname := replace.ForFileName(in.PurgeName)
+ fname = strings.Join([]string{fname, "aac"}, ".")
+ out := strings.Join([]string{dst, fname}, string(os.PathSeparator))
+ slog.Debug("io", slog.String("输入文件", in.FullPath), slog.String("输出文件", out))
+ //跳过已经增大电平的文件夹
+ if strings.Contains(in.FullPath, "Louder") {
+ return
+ }
+ Louder(in.FullPath, out)
+
+}
+
+/*
+仅使用输入输出和增大电平
+*/
+func Louder(in, out string) {
+ cmd := exec.Command("ffmpeg", "-i", in, "-filter:a", "volume=3.0", out)
+ util.ExecCommand(cmd)
+ if err := os.RemoveAll(in); err != nil {
+ slog.Warn("删除失败", slog.String("源文件", in), slog.Any("错误内容", err))
+ } else {
+ slog.Debug("删除成功", slog.String("源文件", in))
+ }
+}
diff --git a/processAudio/ogg.go b/processAudio/ogg.go
new file mode 100644
index 0000000..4e691a6
--- /dev/null
+++ b/processAudio/ogg.go
@@ -0,0 +1,39 @@
+package processAudio
+
+import (
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strings"
+)
+
+func Audio2OGG(in GetFileInfo.BasicInfo) {
+ // 执行转换
+ fname := replace.ForFileName(in.PurgeName)
+ //fname=r
+ out := strings.Join([]string{in.PurgePath, fname, ".ogg"}, "")
+ cmd := exec.Command("ffmpeg", "-i", in.FullPath, out)
+ _ = util.ExecCommand(cmd)
+ //if err == nil {
+ // if err = os.RemoveAll(in.FullPath); err != nil {
+ // slog.Warn("删除失败", slog.String("源文件", in.FullPath), slog.Any("错误", err))
+ // } else {
+ // slog.Debug("删除成功", slog.String("源文件", in.FullPath))
+ // }
+ //}
+}
+
+func Audios2OGG(dir, pattern string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, in := range infos {
+ Audio2OGG(in)
+ }
+}
+
+func AllAudios2OGG(root, pattern string) {
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ for _, in := range infos {
+ Audio2OGG(in)
+ }
+}
diff --git a/processAudio/speedUp.go b/processAudio/speedUp.go
new file mode 100644
index 0000000..ded50a9
--- /dev/null
+++ b/processAudio/speedUp.go
@@ -0,0 +1,88 @@
+package processAudio
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strconv"
+ "strings"
+)
+
+const (
+ //AudioBook = "1.54" //等效audition的65%
+ AudioBook = "1.43" //等效audition的70%
+
+)
+
+/*
+执行文件夹和子文件夹中音频加速
+*/
+func SpeedUpAllAudios(root, pattern string, speed string) {
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ for _, in := range infos {
+ SpeedupAudio(in, speed)
+ }
+}
+
+/*
+执行一个文件夹中音频加速
+*/
+func SpeedUpAudios(dir, pattern string, speed string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, in := range infos {
+ SpeedupAudio(in, speed)
+ }
+}
+
+/*
+加速单个音频的完整函数
+*/
+func SpeedupAudio(in GetFileInfo.BasicInfo, speed string) {
+ dst := strings.Join([]string{in.PurgePath, "speed"}, "") //目标文件目录
+ os.Mkdir(dst, 0777)
+ fname := replace.ForFileName(in.PurgeName)
+ fname = strings.Join([]string{fname, "aac"}, ".")
+ slog.Debug("补全后的 fname", slog.String("fname", fname))
+ out := strings.Join([]string{dst, fname}, string(os.PathSeparator))
+ slog.Debug("io", slog.String("输入文件", in.FullPath), slog.String("输出文件", out))
+ //跳过已经加速的文件夹
+ if strings.Contains(in.FullPath, "speed") {
+ return
+ }
+ speedUp(in.FullPath, out, speed)
+}
+
+/*
+仅使用输入输出和加速参数执行命令
+*/
+func speedUp(in, out string, speed string) {
+ ff := audition2ffmpeg(speed)
+ atempo := strings.Join([]string{"atempo", ff}, "=")
+ cmd := exec.Command("ffmpeg", "-i", in, "-filter:a", atempo, "-vn", "-ac", "1", out)
+ util.ExecCommand(cmd)
+ if err := os.RemoveAll(in); err != nil {
+ slog.Warn("删除失败", slog.String("源文件", in), slog.Any("错误内容", err))
+ } else {
+ slog.Debug("删除成功", slog.String("源文件", in))
+ }
+}
+
+/*
+获取一个等效adobe audition 的 混缩
+*/
+func audition2ffmpeg(speed string) string {
+ audition, err := strconv.ParseFloat(speed, 64)
+ if err != nil {
+ slog.Warn("解析加速参数错误,退出程序", slog.String("错误原文", fmt.Sprint(err)))
+ os.Exit(1)
+ }
+ param := 100 / audition
+ slog.Debug("转换后的原始参数", slog.Float64("param", param))
+ final := fmt.Sprintf("%.2f", param)
+ slog.Debug("保留两位小数的原始参数", slog.String("final", final))
+ return final
+}
diff --git a/processImage/avif.go b/processImage/avif.go
new file mode 100644
index 0000000..833f62a
--- /dev/null
+++ b/processImage/avif.go
@@ -0,0 +1,60 @@
+package processImage
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/mediaInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strings"
+)
+
+/*
+转换所有子文件夹下的图片为AVIF
+*/
+func ProcessAllImages(root, pattern, threads string) {
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ for _, in := range infos {
+ ProcessImage(in, threads)
+ }
+}
+
+/*
+转换一个文件夹下的图片为AVIF
+*/
+func ProcessImages(dir, pattern, threads string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, in := range infos {
+ ProcessImage(in, threads)
+ }
+}
+
+/*
+转换一张图片为AVIF
+*/
+func ProcessImage(in GetFileInfo.BasicInfo, threads string) {
+ mi, ok := in.MediaInfo.(mediaInfo.ImageInfo)
+ if ok {
+ slog.Debug("断言图片mediainfo结构体成功", slog.Any("MediainfoVideo结构体", mi))
+ } else {
+ slog.Warn("断言图片mediainfo结构体失败")
+ }
+
+ cleanName := replace.ForFileName(in.PurgeName)
+ out := strings.Join([]string{in.PurgePath, cleanName, ".avif"}, "")
+
+ cmd := exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-c:v", "libaom-av1", "-still-picture", "1", "-threads", threads, out)
+ slog.Debug("ffmpeg", slog.Any("生成的命令", fmt.Sprint(cmd)))
+ err := util.ExecCommand(cmd)
+
+ if err == nil {
+ if err = os.RemoveAll(in.FullPath); err != nil {
+ slog.Warn("删除失败", slog.Any("源文件", in.FullPath), slog.Any("错误", err))
+ } else {
+ slog.Debug("删除成功", slog.Any("源文件", in.FullPath))
+ }
+ }
+}
diff --git a/processImage/unit_test.go b/processImage/unit_test.go
new file mode 100644
index 0000000..e66f66f
--- /dev/null
+++ b/processImage/unit_test.go
@@ -0,0 +1,26 @@
+package processImage
+
+import (
+ "log/slog"
+ "os"
+ "processAll/GetFileInfo"
+ "testing"
+)
+
+func init() {
+ opt := slog.HandlerOptions{ // 自定义option
+ AddSource: true,
+ Level: slog.LevelDebug, // slog 默认日志级别是 info
+ }
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, &opt))
+ slog.SetDefault(logger)
+}
+func TestOne(t *testing.T) {
+ abs := "/Users/zen/Downloads/telegram/cache/Images/4965298626347773683_121.jpg"
+ info := GetFileInfo.GetFileInfo(abs)
+ ProcessImage(info, "3")
+}
+func TestAll(t *testing.T) {
+ folder := "/Users/zen/Downloads/图片助手(ImageAssistant)_批量图片下载器/girlygirlpic.com"
+ ProcessAllImages(folder, "jpg;png", "3")
+}
diff --git a/processVideo/PAL.go b/processVideo/PAL.go
new file mode 100644
index 0000000..93e981f
--- /dev/null
+++ b/processVideo/PAL.go
@@ -0,0 +1,55 @@
+package processVideo
+
+import (
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/alert"
+ "processAll/replace"
+ "processAll/util"
+ "strings"
+)
+
+func FixAll4x3s(root, pattern, threads string) {
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ for _, in := range infos {
+ Fix4x3(in, threads)
+ }
+}
+
+func Fix4x3s(src, pattern, threads string) {
+ infos := GetFileInfo.GetAllFileInfo(src, pattern)
+ for _, in := range infos {
+ slog.Debug("横屏视频", slog.Any("视频信息", in))
+ Fix4x3(in, threads)
+ }
+}
+
+func Fix4x3(in GetFileInfo.BasicInfo, threads string) {
+ defer func() {
+ if err := recover(); err != nil {
+ slog.Warn("错误", slog.String("文件信息", in.FullPath))
+ alert.Customize("failed", alert.Ava)
+ }
+ }()
+
+ dst := in.PurgePath //原始目录
+ dst = strings.Join([]string{dst, "resolution"}, "") //二级目录
+ fname := in.PurgeName //仅文件名
+ fname = replace.ForFileName(fname)
+ mp4 := strings.Join([]string{fname, "mp4"}, ".")
+ os.Mkdir(dst, 0777)
+ slog.Debug("新建文件夹", slog.String("全名", dst))
+ out := strings.Join([]string{dst, mp4}, string(os.PathSeparator))
+ slog.Debug("io", slog.String("源文件:", in.FullPath), slog.String("输出文件:", out))
+ var cmd *exec.Cmd = exec.Command("ffmpeg", "-i", in.FullPath, "-aspect", "4:3", "-c:v", "libx265", "-c:a", "aac", "-ac", "1", "-tag:v", "hvc1", "-threads", threads, out)
+ err := util.ExecCommand(cmd)
+ if err == nil {
+ if err = os.Remove(in.FullPath); err != nil {
+ slog.Warn("删除失败", slog.String("源文件", in.FullPath), slog.Any("错误文本", err))
+ } else {
+ slog.Debug("删除成功", slog.String("源文件", in.FullPath))
+ }
+ }
+}
diff --git a/processVideo/README.md b/processVideo/README.md
new file mode 100644
index 0000000..26e905f
--- /dev/null
+++ b/processVideo/README.md
@@ -0,0 +1,5 @@
+# ffmpeg
+
+```shell
+ffmpeg -i input.mp4 -ss 00:30:31.827 -to 00:44:58.194 -c:v copy -c:a copy -filter:a volume=3.0 -map_chapters -1 output.mp4
+```
\ No newline at end of file
diff --git a/processVideo/Resize.go b/processVideo/Resize.go
new file mode 100644
index 0000000..c26f67a
--- /dev/null
+++ b/processVideo/Resize.go
@@ -0,0 +1,106 @@
+package processVideo
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/alert"
+ "processAll/mediaInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strings"
+)
+
+func ResizeAllVideos(root, pattern, threads string) {
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ for _, in := range infos {
+ ResizeVideo(in, threads)
+ }
+}
+func ResizeVideos(dir, pattern, threads string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, in := range infos {
+ ResizeVideo(in, threads)
+ }
+}
+func ResizeVideo(in GetFileInfo.BasicInfo, threads string) {
+
+ mi, ok := in.MediaInfo.(mediaInfo.VideoInfo)
+ if ok {
+ slog.Debug("断言视频mediainfo结构体成功", slog.Any("MediainfoVideo结构体", mi))
+ } else {
+ slog.Warn("断言视频mediainfo结构体失败")
+ }
+ if mi.VideoWidth <= 1920 || mi.VideoHeight <= 1920 {
+ slog.Debug("跳过", slog.String("正常尺寸的视频", in.FullPath))
+ return
+ }
+ if mi.VideoWidth > mi.VideoHeight {
+ slog.Debug("横屏视频", slog.Any("视频信息", in))
+ Resize(in, threads, "1920x1080")
+ } else if mi.VideoWidth < mi.VideoHeight {
+ slog.Debug("竖屏视频", slog.Any("视频信息", in))
+ Resize(in, threads, "1080x1920")
+ } else {
+ slog.Debug("正方形视频", slog.Any("视频信息", in))
+ Resize(in, threads, "1920x1920")
+ }
+}
+func Resize(in GetFileInfo.BasicInfo, threads string, p string) {
+ defer func() {
+ if err := recover(); err != nil {
+ slog.Warn("错误", slog.String("文件信息", in.FullPath))
+ alert.Customize("failed", alert.Ava)
+ }
+ }()
+ mi, ok := in.MediaInfo.(mediaInfo.VideoInfo)
+ if ok {
+ slog.Debug("断言视频mediainfo结构体成功", slog.Any("MediainfoVideo结构体", mi))
+ } else {
+ slog.Warn("断言视频mediainfo结构体失败")
+ }
+ dst := in.PurgePath // 文件所在路径 包含最后一个路径分隔符
+ if strings.Contains(in.PurgePath, "resize") {
+ return
+ }
+ dst = strings.Join([]string{dst, "resize"}, "") //二级目录
+ fname := replace.ForFileName(in.PurgeName) //仅文件名
+ fname = strings.Join([]string{fname, "mp4"}, ".")
+ os.Mkdir(dst, 0777)
+ slog.Debug("新建文件夹", slog.String("全名", dst))
+ out := strings.Join([]string{dst, fname}, string(os.PathSeparator))
+ slog.Debug("io", slog.String("源文件:", in.FullPath), slog.String("输出文件:", out))
+ var cmd *exec.Cmd
+ switch p {
+ case "1920x1080":
+ cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-strict", "-2", "-vf", "scale=-1:1080", "-c:v", "copy", "-tag:v", "hvc1", "-c:a", "copy", "-ac", "1", "-threads", threads, out)
+ if mi.VideoCodecID != "hvc1" {
+ cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-strict", "-2", "-vf", "scale=-1:1080", "-c:v", "libx265", "-tag:v", "hvc1", "-c:a", "aac", "-ac", "1", "-threads", threads, out)
+ }
+ case "1080x1920":
+ cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-strict", "-2", "-vf", "scale=-1:1920", "-c:v", "copy", "-tag:v", "hvc1", "hvc1", "-c:a", "copy", "-ac", "1", "-threads", threads, out)
+ if mi.VideoCodecID != "hvc1" {
+ cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-strict", "-2", "-vf", "scale=-1:1920", "-c:v", "libx265", "-tag:v", "hvc1", "-c:a", "aac", "-ac", "1", "-threads", threads, out)
+ }
+ case "1920x1920":
+ cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-strict", "-2", "-vf", "scale=1920:1920", "-c:v", "copy", "-tag:v", "hvc1", "-c:a", "copy", "-ac", "1", "-threads", threads, out)
+ if mi.VideoCodecID != "hvc1" {
+ cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-strict", "-2", "-vf", "scale=1920:1920", "-c:v", "libx254", "-tag:v", "hvc1", "hvc1", "-c:a", "aac", "-ac", "1", "-threads", threads, out)
+ }
+ default:
+ slog.Warn("不正常的视频源", slog.Any("视频信息", in.FullPath))
+ }
+ slog.Debug("ffmpeg", slog.String("生成的命令", fmt.Sprintf("生成的命令是:%s", cmd)))
+ if err := util.ExecCommand(cmd); err != nil {
+ slog.Warn("resize发生错误", slog.String("命令原文", fmt.Sprint(cmd)), slog.String("错误原文", fmt.Sprint(err)), slog.String("源文件", in.FullPath))
+ return
+ }
+
+ if err := os.Remove(in.FullPath); err != nil {
+ slog.Warn("删除失败", slog.String("源文件", in.FullPath), slog.Any("错误文本", err))
+ } else {
+ slog.Warn("删除成功", slog.String("源文件", in.FullPath))
+ }
+}
diff --git a/processVideo/Rotate.go b/processVideo/Rotate.go
new file mode 100644
index 0000000..afab7f5
--- /dev/null
+++ b/processVideo/Rotate.go
@@ -0,0 +1,54 @@
+package processVideo
+
+import (
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strings"
+)
+
+func RotateAllVideos(dir, pattern, direction, threads string) {
+ infos := GetFileInfo.GetAllFilesInfo(dir, pattern)
+ for _, in := range infos {
+ RotateVideo(in, direction, threads)
+ }
+}
+func RotateVideos(dir, pattern, direction, threads string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, in := range infos {
+ RotateVideo(in, direction, threads)
+ }
+}
+func RotateVideo(in GetFileInfo.BasicInfo, direction, threads string) {
+ if strings.Contains(in.PurgePath, "rotate") {
+ return
+ }
+ dst := strings.Join([]string{in.PurgePath, "rotate"}, "")
+ os.Mkdir(dst, os.ModePerm)
+ fname := in.PurgeName
+ fname = replace.ForFileName(fname)
+ fname = strings.Join([]string{fname, "mp4"}, ".")
+ out := strings.Join([]string{dst, fname}, string(os.PathSeparator))
+ var cmd *exec.Cmd
+ var transport string
+ switch direction {
+ case "ToRight":
+ transport = "transpose=1"
+ case "ToLeft":
+ transport = "transpose=2"
+ default:
+ return
+ }
+ cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-vf", transport, "-c:v", "libx265", "-c:a", "aac", "-tag:v", "hvc1", "-threads", threads, out)
+ err := util.ExecCommand(cmd)
+ if err == nil {
+ if err = os.RemoveAll(in.FullPath); err != nil {
+ slog.Warn("删除失败", slog.Any("源文件", in.FullPath), slog.Any("错误", err))
+ } else {
+ slog.Debug("删除成功", slog.Any("源文件", in.FullPath))
+ }
+ }
+}
diff --git a/processVideo/extractAudio.go b/processVideo/extractAudio.go
new file mode 100644
index 0000000..7fd8fe3
--- /dev/null
+++ b/processVideo/extractAudio.go
@@ -0,0 +1,40 @@
+package processVideo
+
+import (
+ "fmt"
+ "log/slog"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/util"
+ "strings"
+)
+
+func AllVideos2Audio(root, pattern, threads string) {
+ files := GetFileInfo.GetAllFileInfo(root, pattern)
+ for _, file := range files {
+ Video2Audio(file, threads)
+ }
+}
+
+func Videos2Audio(dir, pattern, threads string) {
+ files := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, file := range files {
+ Video2Audio(file, threads)
+ }
+}
+func Video2Audio(in GetFileInfo.BasicInfo, threads string) {
+ out := strings.Replace(in.FullPath, in.PurgeExt, "aac", 1)
+ cmd := exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-vn", "-ac", "1", out)
+ slog.Info("生成的命令", slog.String("command", fmt.Sprint(cmd)))
+ if err := util.ExecCommand(cmd); err != nil {
+ slog.Warn("命令执行中出现错误")
+ }
+ slog.Debug("视频提取音频运行完成")
+ //if err == nil {
+ // if err = os.RemoveAll(in.FullPath); err != nil {
+ // slog.Warn("删除失败", slog.Any("源文件", in.FullPath), slog.Any("错误", err))
+ // } else {
+ // slog.Info("删除成功", slog.Any("源文件", in.FullName))
+ // }
+ //}
+}
diff --git a/processVideo/h265.go b/processVideo/h265.go
new file mode 100644
index 0000000..6f8e20c
--- /dev/null
+++ b/processVideo/h265.go
@@ -0,0 +1,168 @@
+package processVideo
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetAllFolder"
+ "processAll/GetFileInfo"
+ "processAll/mediaInfo"
+ "processAll/model"
+ "processAll/replace"
+ "processAll/util"
+ "runtime"
+ "strings"
+)
+
+func ProcessAllVideos2H265(root, pattern, threads string) {
+ folders := GetAllFolder.List(root)
+ folders = append(folders, root)
+ for i, folder := range folders {
+ slog.Debug(fmt.Sprintf("获取全部子文件夹,正在处理第个 %d/%d 文件夹", i+1, len(folders)))
+ ProcessVideos2H265(folder, pattern, threads)
+ runtime.GC()
+ }
+}
+
+func ProcessVideos2H265(dir, pattern, threads string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for i, info := range infos {
+ slog.Debug(fmt.Sprintf("获取全部文件,正在处理第个 %d/%d 文件", i+1, len(dir)))
+ ProcessVideo2H265(info, threads)
+ runtime.GC()
+ }
+}
+
+func ProcessVideo2H265(in GetFileInfo.BasicInfo, threads string) {
+ mi, ok := in.MediaInfo.(mediaInfo.VideoInfo)
+ if ok {
+ slog.Debug("断言视频mediainfo结构体成功", slog.Any("MediainfoVideo结构体", mi))
+ } else {
+ slog.Warn("断言视频mediainfo结构体失败")
+ }
+ slog.Info("获取帧数", slog.Int("当前视频帧数", mi.VideoFrameCount))
+ //slog.Debug("文件信息", slog.Any("info", in))
+ if strings.Contains(in.FullPath, "h265") {
+ slog.Debug("跳过当前已经在h265目录中的文件", slog.String("文件名", in.FullPath))
+ return
+ }
+ prefix := in.PurgePath // 输入文件的纯路径
+ slog.Debug("perfix", slog.String("perfix", prefix))
+
+ slog.Debug("fullname", slog.String("fullname", in.FullName))
+ middle := "h265"
+ if err := os.Mkdir(strings.Join([]string{prefix, middle}, string(os.PathSeparator)), 0777); err != nil {
+ if strings.Contains(err.Error(), "file exists") {
+ slog.Debug("输出文件夹已存在")
+ }
+ } else {
+ slog.Debug("创建输出文件夹")
+ }
+ dstPurgeName := replace.ForFileName(in.PurgeName) // 输入文件格式化后的新文件名
+ out := strings.Join([]string{in.PurgePath, middle, string(os.PathSeparator), dstPurgeName, ".mp4"}, "")
+ defer func() {
+ if err := recover(); err != nil {
+ slog.Warn("出现错误", slog.String("输入文件", in.FullPath), slog.String("输出文件", out))
+ }
+ }()
+
+ slog.Debug("", slog.String("out", out), slog.String("extName", in.PurgeExt))
+ mp4 := strings.Replace(out, in.PurgeExt, "mp4", -1)
+ slog.Debug("调试", slog.String("输入文件", in.FullPath), slog.String("输出文件", mp4))
+ cmd := exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-c:v", "libx265", "-c:a", "aac", "-ac", "1", "-tag:v", "hvc1", "-map_chapters", "-1", "-threads", threads, mp4)
+ // info := GetFileInfo.GetVideoFileInfo(in.FullPath)
+
+ // if fmt.Sprintf("%v", cpuid.CPU.VendorID) == "Intel" {
+ // bitRate, err := GetFileInfo.GetBitRate(in.FullPath)
+ // slog.Debug("bitrate in h265", slog.String("bitrate", bitRate))
+ // if err != nil {
+ // goto AGAIN
+ // } else {
+ // b := strings.Join([]string{bitRate, "k"}, "")
+ // slog.Debug("获取的比特率", slog.String("bitrate", b))
+ // if b == "" || b == "0" || b == "k" {
+ // slog.Warn("获取比特率失败", slog.String("bitrate", b))
+ // goto AGAIN
+ // } else {
+ // //b := strings.Join([]string{mi.VideoBitRate, "k"}, "")
+ // //ffmpeg -i in.mp4 -c:v hevc_qsv -c:a aac -ac 1 -b:v 100k,
+ // cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-c:v", "hevc_qsv", "-b:v", b, "-c:a", "aac", "-ac", "1", "-tag:v", "hvc1", "-map_chapters", "-1", "-threads", threads, mp4)
+ // slog.Info("检测到Intel系统", slog.String("命令改变为", fmt.Sprint(cmd)))
+ // }
+ // }
+ // }
+ //AGAIN:
+ bitRate, _ := GetFileInfo.GetBitRate(in.FullPath)
+ slog.Debug("bitrate in h265", slog.String("bitrate", bitRate))
+
+ b := strings.Join([]string{bitRate, "k"}, "")
+ slog.Debug("获取的比特率", slog.String("bitrate", b))
+ cmd = exec.Command("ffmpeg", "-threads", threads, "-i", in.FullPath, "-c:v", "libx265", "-b:v", b, "-c:a", "aac", "-ac", "1", "-tag:v", "hvc1", "-map_chapters", "-1", "-threads", threads, mp4)
+
+ if mi.VideoWidth > 1920 && mi.VideoHeight > 1920 {
+ slog.Warn("视频大于1080P需要使用其他程序先处理视频尺寸", slog.Any("原视频", in))
+ ResizeVideo(in, threads)
+ return
+ } else if mi.VideoFormat == "HEVC" {
+ if mi.VideoCodecID == "hvc1" {
+ slog.Debug(fmt.Sprintf("跳过hevc/hvc1文件"), slog.String("文件名", in.FullPath))
+ return
+ } else {
+ addTag(in)
+ slog.Debug("添加标签", slog.String("文件名", in.FullPath))
+ }
+ }
+ slog.Info("生成的命令", slog.String("command", fmt.Sprint(cmd)))
+ slog.Info("视频信息", slog.Int("视频帧数", mi.VideoFrameCount), slog.String("比特率", mi.VideoBitRate))
+ err := util.ExecCommand(cmd)
+ if err != nil {
+ return
+ }
+ slog.Debug("视频编码运行完成")
+ if s_size, d_size, diff, err := util.GetDiffFileSize(in.FullPath, mp4); err != nil {
+ slog.Warn("文件优化大小计算出错")
+ } else {
+ save := new(model.Save)
+ save.SrcName = in.FullPath
+ save.DstName = mp4
+ save.SrcSize = s_size
+ save.DstSize = d_size
+ save.Size = diff
+ _, err := save.InsertOne()
+ if err != nil {
+ fmt.Println("节省的空间 记录插入失败")
+ }
+ }
+ if err == nil {
+ if err = os.RemoveAll(in.FullPath); err != nil {
+ slog.Warn("删除失败", slog.Any("源文件", in.FullPath), slog.Any("错误", err))
+ } else {
+ slog.Debug("删除成功", slog.Any("源文件", in.FullName))
+ }
+ }
+ s := new(model.Save)
+ all, err := s.SumSaveAll()
+ if err != nil {
+ return
+ } else {
+ //fmt.Printf("节省的空间:%v GB\n", all)
+ slog.Info("转码总共节省的空间", slog.String("GB", all))
+ }
+ slog.Debug("本次转码完成")
+}
+func addTag(in GetFileInfo.BasicInfo) {
+ prefix := strings.Trim(in.FullPath, in.FullName) // 带 /
+ dst := strings.Join([]string{prefix, "tag"}, "")
+ os.Mkdir(dst, 0777)
+ target := strings.Join([]string{dst, in.FullName}, string(os.PathSeparator))
+ cmd := exec.Command("ffmpeg", "-i", in.FullPath, "-c:v", "copy", "-c:a", "copy", "-ac", "1", "-c:s", "copy", "-tag:v", "hvc1", "-map_chapters", "-1", target)
+ err := util.ExecCommand(cmd)
+ if err == nil {
+ if err = os.RemoveAll(in.FullPath); err != nil {
+ slog.Warn("删除失败", slog.Any("源文件", in.FullPath), slog.Any("错误", err))
+ } else {
+ slog.Debug("删除成功", slog.Any("源文件", in.FullName))
+ }
+ }
+}
diff --git a/processVideo/report.go b/processVideo/report.go
new file mode 100644
index 0000000..b42cdb6
--- /dev/null
+++ b/processVideo/report.go
@@ -0,0 +1,57 @@
+package processVideo
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "processAll/GetFileInfo"
+ "processAll/mediaInfo"
+ "runtime"
+)
+
+func GetOutOfH265(root, pattern string) {
+ report, err := os.OpenFile("report.md", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0777)
+ if err != nil {
+ return
+ }
+ h264, err := os.OpenFile("h264.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0777)
+ if err != nil {
+ return
+ }
+ h265, err := os.OpenFile("h265.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0777)
+ if err != nil {
+ return
+ }
+ defer report.Close()
+ report.WriteString(fmt.Sprintf("|文件名|h264|未打标签的h265|\n"))
+ report.WriteString(fmt.Sprintf("|:---:|:---:|:---:|\n"))
+
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ length := len(infos)
+ slog.Info("", slog.Int("总文件数", length))
+ for i, in := range infos {
+ fmt.Printf("正在处理第个%d /%d文件\n", i+1, length)
+ mi, ok := in.MediaInfo.(mediaInfo.VideoInfo)
+ if ok {
+ slog.Debug("断言视频mediainfo结构体成功", slog.Any("MediainfoVideo结构体", mi))
+ } else {
+ slog.Warn("断言视频mediainfo结构体失败")
+ }
+ if mi.VideoCodecID == "hvc1" {
+ // 一定是h265视频
+ } else if mi.VideoFormat == "HEVC" {
+ // 一定是没有打标签的h265视频
+ fmt.Printf("%s\t是h265视频但没有打标签\n", in.FullPath)
+ report.WriteString(fmt.Sprintf("|%v||\u2713|\n", in.FullPath))
+ h265.WriteString(fmt.Sprintf("%s\n", in.FullPath))
+ } else {
+ // 一定不是h265视频
+ fmt.Printf("%s\t不是h265视频\n", in.FullPath)
+ report.WriteString(fmt.Sprintf("|%v|\u2713||\n", in.FullPath))
+ h264.WriteString(fmt.Sprintf("%s\n", in.FullPath))
+ }
+ report.Sync()
+ runtime.GC()
+
+ }
+}
diff --git a/processVideo/speedUp.go b/processVideo/speedUp.go
new file mode 100644
index 0000000..6b6a8b6
--- /dev/null
+++ b/processVideo/speedUp.go
@@ -0,0 +1,84 @@
+package processVideo
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "os/exec"
+ "processAll/GetFileInfo"
+ "processAll/replace"
+ "processAll/util"
+ "strconv"
+ "strings"
+)
+
+/*
+执行文件夹和子文件夹中音视频加速
+*/
+func SpeedUpAllVideos(root, pattern string, speed string) {
+ infos := GetFileInfo.GetAllFilesInfo(root, pattern)
+ for _, in := range infos {
+ SpeedupVideo(in, speed)
+ }
+}
+
+/*
+执行一个文件夹中音视频加速
+*/
+func SpeedUpVideos(dir, pattern string, speed string) {
+ infos := GetFileInfo.GetAllFileInfo(dir, pattern)
+ for _, in := range infos {
+ SpeedupVideo(in, speed)
+ }
+}
+
+/*
+加速单个音视频的完整函数
+*/
+func SpeedupVideo(in GetFileInfo.BasicInfo, speed string) {
+ dst := strings.Join([]string{in.PurgePath, "speed"}, "") //目标文件目录
+ os.Mkdir(dst, 0777)
+ fname := replace.ForFileName(in.PurgeName)
+ fname = strings.Join([]string{fname, "mp4"}, ".")
+ slog.Debug("补全后的 fname", slog.String("fname", fname))
+ out := strings.Join([]string{dst, fname}, string(os.PathSeparator))
+ slog.Debug("io", slog.String("输入文件", in.FullPath), slog.String("输出文件", out))
+ //跳过已经加速的文件夹
+ if strings.Contains(in.FullPath, "speed") {
+ return
+ }
+ speedUp(in.FullPath, out, speed)
+}
+
+/*
+仅使用输入输出和加速参数执行命令
+*/
+func speedUp(in, out string, speed string) {
+ //ffmpeg -i 5_6253787118179453662.mp4 -y -vf "setpts=0.8*PTS" -filter:a "atempo=1.25" -c:v libx265 -c:a aac -ac 1 -tag:v hvc1 6253787118179453662.mp4
+ ff := audio2video(speed)
+ pts := strings.Join([]string{"setpts=", ff, "*PTS"}, "")
+ atempo := strings.Join([]string{"atempo", ff}, "=")
+ cmd := exec.Command("ffmpeg", "-i", in, "-filter:a", atempo, "-vf", pts, "-c:v", "libx265", "-c:a", "aac", "-ac", "1", "-tag:v", "hvc1", out)
+ util.ExecCommand(cmd)
+ if err := os.RemoveAll(in); err != nil {
+ slog.Warn("删除失败", slog.String("源文件", in), slog.Any("错误内容", err))
+ } else {
+ slog.Debug("删除成功", slog.String("源文件", in))
+ }
+}
+
+/*
+获取一个等效ffmpeg音视频参数
+*/
+func audio2video(speed string) string {
+ audio, err := strconv.ParseFloat(speed, 64)
+ if err != nil {
+ slog.Warn("解析加速参数错误,退出程序", slog.String("错误原文", fmt.Sprint(err)))
+ os.Exit(1)
+ }
+ video := 1 / audio
+ slog.Debug("转换后的原始参数", slog.Float64("Video", video))
+ final := fmt.Sprintf("%.2f", video)
+ slog.Debug("保留两位小数的原始参数", slog.String("final", final))
+ return final
+}
diff --git a/processVideo/unit_test.go b/processVideo/unit_test.go
new file mode 100644
index 0000000..1d3f561
--- /dev/null
+++ b/processVideo/unit_test.go
@@ -0,0 +1,34 @@
+package processVideo
+
+import (
+ "fmt"
+ "path"
+ "testing"
+)
+
+func TestDir(t *testing.T) {
+ fp := "/Users/zen/Downloads/Telegram Desktop/水岛津实/33.mp4"
+ ret := path.Dir(fp)
+ t.Log(ret)
+}
+
+func TestProcessAllH265(t *testing.T) {
+ root := "/Users/zen/Downloads/telegram/cache"
+ pattern := "mp4"
+ ProcessAllVideos2H265(root, pattern, "3")
+}
+func TestPanic(t *testing.T) {
+ if err := recover(); err != nil {
+ fmt.Println("有panic")
+ }
+ go func() {
+ panic("panic!")
+ }()
+ for i := 0; i < 10; i++ {
+ fmt.Println(i)
+ }
+}
+
+func TestGetOutOfH265(t *testing.T) {
+ GetOutOfH265("/Volumes/volume/未整理", "mp4")
+}
diff --git a/rename/README.md b/rename/README.md
new file mode 100644
index 0000000..958cf0a
--- /dev/null
+++ b/rename/README.md
@@ -0,0 +1,6 @@
+# 运行一个单元测试
+```bash
+go test -v -run <测试函数名> <目录>
+# example
+go test -v -run TestRename ./
+```
\ No newline at end of file
diff --git a/rename/customRename.go b/rename/customRename.go
new file mode 100644
index 0000000..0dd4d00
--- /dev/null
+++ b/rename/customRename.go
@@ -0,0 +1,48 @@
+package rename
+
+import (
+ "fmt"
+ "os"
+ "processAll/GetAllFolder"
+ "processAll/GetFileInfo"
+ "processAll/replace"
+ "runtime"
+ "strings"
+)
+
+/*
+get all name in folders and replace clean
+*/
+func cleanName(root, pattern string) {
+ folders := GetAllFolder.List(root)
+ folders = append(folders, root)
+ for _, folder := range folders {
+ files := GetFileInfo.GetAllFilesInfo(folder, pattern)
+ for _, file := range files {
+ fmt.Printf("%+v\n", file)
+ clean(file)
+ }
+ runtime.GC()
+ }
+}
+func clean(info GetFileInfo.BasicInfo) {
+ file, err := os.OpenFile("report.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0777)
+ if err != nil {
+ return
+ }
+ defer file.Close()
+ oldPurgeName := info.PurgeName
+ newPurgeName := replace.ForFileName(oldPurgeName)
+ if oldPurgeName == newPurgeName {
+ file.WriteString(fmt.Sprintf("跳过已经处理的文件:%v\n", info.FullPath))
+ return
+ }
+ newFileName := strings.Join([]string{newPurgeName, info.PurgeExt}, ".")
+ newFullPath := strings.Join([]string{info.PurgePath, newFileName}, "")
+ file.WriteString(fmt.Sprintf("旧文件名:%s\t新文件名:%s\n", info.FullPath, newFullPath))
+ err = os.Rename(info.FullPath, newFullPath)
+ if err != nil {
+ file.WriteString(fmt.Sprintf("重命名出错的文件filename:%v\n", info.FullPath))
+ }
+ file.Sync()
+}
diff --git a/rename/rename.go b/rename/rename.go
new file mode 100644
index 0000000..4a1ca03
--- /dev/null
+++ b/rename/rename.go
@@ -0,0 +1,84 @@
+package rename
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "path/filepath"
+ "processAll/replace"
+ "regexp"
+ "strings"
+)
+
+func rename(root string) {
+ //folders = append(folders, root)
+ err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ //fmt.Println(info.Name())
+ oldname := info.Name()
+ oldPath := strings.Join([]string{root, oldname}, string(os.PathSeparator))
+ //newName := strings.Replace(oldname, "-", "", -1)
+ newName, newPath := old2new(oldname, root)
+ fmt.Printf("oldName:%v\noldPath:%v\nnewName:%v\nnewPath:%v\n", oldname, oldPath, newName, newPath)
+ //err = os.Rename(oldPath, newPath)
+ //if err != nil {
+ // fmt.Printf("重命名出错\n")
+ // return err
+ //}
+ }
+ //fmt.Printf("Path: %s, Size: %d bytes\n", path, info.Size())
+ return nil
+ })
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+func old2new(oldName, oldPath string) (newName, newPath string) {
+
+ /****************重命名逻辑*******************/
+ //tme = strings.Replace(oldname, "AI换脸", "", -1)
+
+ newName = replace.ForFileName(oldName)
+ match, _ := regexp.MatchString(`[A-Z]\d{3}`, newName)
+ if match {
+ //slog.Info("matched!", slog.String("匹配到的字符串", match))
+ //fmt.Println(match)
+ re := regexp.MustCompile(`[A-Z]\d{3,4}`)
+ matches := re.FindStringSubmatch(newName)
+ for _, bingo := range matches {
+ slog.Info("matched!", slog.String("匹配到的字符串", bingo))
+ newName = strings.Replace(newName, bingo, "", 1)
+ }
+ }
+ /****************重命名逻辑*******************/
+
+ newPath = strings.Join([]string{oldPath, newName}, string(os.PathSeparator))
+
+ return newName, newPath
+}
+
+/*
+重命名文件删除重复内容
+*/
+func RenameForDup(dir, dup string) {
+ readDir, err := os.ReadDir(dir)
+ if err != nil {
+ return
+ }
+ for _, file := range readDir {
+ //t.Logf("%+v\n", file)
+ oldName := file.Name()
+ oldPath := strings.Join([]string{dir, oldName}, string(os.PathSeparator))
+ newName := strings.Replace(oldName, dup, "", 1)
+ newPath := strings.Join([]string{dir, newName}, string(os.PathSeparator))
+ slog.Info("summary", slog.String("旧文件名", oldName), slog.String("新文件名", newPath))
+ err := os.Rename(oldPath, newName)
+ if err != nil {
+ //t.Log(err)
+ return
+ }
+ }
+}
diff --git a/rename/unit_test.go b/rename/unit_test.go
new file mode 100644
index 0000000..3a742a9
--- /dev/null
+++ b/rename/unit_test.go
@@ -0,0 +1,12 @@
+package rename
+
+import "testing"
+
+// go test -v -run TestRename ./
+
+func TestRenameForDup(t *testing.T) {
+ RenameForDup("/sdcard/Movies/bili", "蔡依林")
+}
+func TestClearName(t *testing.T) {
+ cleanName("/home/zen/storage/AllBackup/20231121_093359/Music", "aac;WAV;ogg")
+}
diff --git a/replace.py b/replace.py
new file mode 100644
index 0000000..58ac55c
--- /dev/null
+++ b/replace.py
@@ -0,0 +1,37 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+from glob import glob
+
+
+def zh_ch2en_us(files):
+ for file in files:
+ print(file)
+ f = open(file, 'r+', encoding='utf-8')
+ all_the_lines = f.readlines()
+ f.seek(0)
+ f.truncate()
+ for line in all_the_lines:
+ line = line.replace(',', ',')
+ line = line.replace('。', '.')
+ line = line.replace('(', '(')
+ line = line.replace(')', ')')
+ line = line.replace('“', '\"')
+ line = line.replace('”', '\"')
+ line = line.replace(':', ':')
+ line = line.replace(';', ';')
+ line = line.replace('?', '?')
+ line = line.replace('!', '!')
+ line = line.replace('《', '<')
+ line = line.replace('》', '>')
+ line = line.replace('【', '[')
+ line = line.replace('】', ']')
+ line = line.replace('、', '\\')
+ line = line.replace('~', '~')
+ f.write(line)
+ f.close()
+
+
+if __name__ == '__main__':
+ path = glob('*.md')
+ print(path)
+ zh_ch2en_us(path)
diff --git a/replace/filename.go b/replace/filename.go
new file mode 100644
index 0000000..8650f6f
--- /dev/null
+++ b/replace/filename.go
@@ -0,0 +1,74 @@
+package replace
+
+import (
+ "log/slog"
+ "regexp"
+ "strings"
+)
+
+//func ForFileName(str string) string {
+// str = strings.Replace(str, "。", ".", -1)
+// str = strings.Replace(str, ",", ",", -1)
+// str = strings.Replace(str, "《", "(", -1)
+// str = strings.Replace(str, "》", ")", -1)
+// str = strings.Replace(str, "【", "(", -1)
+// str = strings.Replace(str, "】", ")", -1)
+// str = strings.Replace(str, "(", "(", -1)
+// str = strings.Replace(str, ")", ")", -1)
+// str = strings.Replace(str, "「", "(", -1)
+// str = strings.Replace(str, "」", ")", -1)
+// str = strings.Replace(str, "+", "_", -1)
+// str = strings.Replace(str, "`", "", -1)
+// str = strings.Replace(str, " ", "", -1)
+// str = strings.Replace(str, "\u00A0", "", -1)
+// str = strings.Replace(str, "\u0000", "", -1)
+// str = strings.Replace(str, "·", "", -1)
+// str = strings.Replace(str, "\uE000", "", -1)
+// str = strings.Replace(str, "\u000D", "", -1)
+// str = strings.Replace(str, "、", "", -1)
+// //str = strings.Replace(str, "/", "", -1)
+// str = strings.Replace(str, "!", "", -1)
+// str = strings.Replace(str, "|", "", -1)
+// str = strings.Replace(str, "|", "", -1)
+// str = strings.Replace(str, ":", "", -1)
+// str = strings.Replace(str, " ", "", -1)
+// str = strings.Replace(str, "&", "", -1)
+// str = strings.Replace(str, "?", "", -1)
+// str = strings.Replace(str, "(", "", -1)
+// str = strings.Replace(str, ")", "", -1)
+// str = strings.Replace(str, "-", "", -1)
+// str = strings.Replace(str, " ", "", -1)
+// str = strings.Replace(str, "“", "", -1)
+// str = strings.Replace(str, "”", "", -1)
+// str = strings.Replace(str, "--", "", -1)
+// str = strings.Replace(str, "_", "", -1)
+// str = strings.Replace(str, ":", "", -1)
+// return str
+//}
+
+/*
+仅保留文件名中的 数字 字母 和 中文
+*/
+func ForFileName(name string) string {
+ nStr := ""
+ for _, v := range name {
+ if Effective(string(v)) {
+ // fmt.Printf("%d\t有效%v\n", i, string(v))
+ nStr = strings.Join([]string{nStr, string(v)}, "")
+ }
+ }
+ slog.Debug("正则表达式匹配数字字母汉字", slog.String("文件名", nStr))
+ return nStr
+}
+func Effective(s string) bool {
+ if s == " " {
+ return true
+ }
+ num := regexp.MustCompile(`\d`) // 匹配任意一个数字
+ letter := regexp.MustCompile(`[a-zA-Z]`) // 匹配任意一个字母
+ char := regexp.MustCompile(`[\p{Han}]`) // 匹配任意一个汉字
+ if num.MatchString(s) || letter.MatchString(s) || char.MatchString(s) {
+ return true
+ }
+ return false
+}
diff --git a/replace/tty.go b/replace/tty.go
new file mode 100644
index 0000000..5ef2fbe
--- /dev/null
+++ b/replace/tty.go
@@ -0,0 +1,13 @@
+package replace
+
+import "strings"
+
+/*
+终端输出内容转换为可以保存到mysql的字段
+*/
+func TTY2Mysql(tty string) string {
+ tty = strings.Replace(tty, " ", " ", -1)
+ tty = strings.Replace(tty, "\u0000", " ", -1)
+ tty = strings.Replace(tty, "\n", " ", -1)
+ return tty
+}
diff --git a/replace/unit_test.go b/replace/unit_test.go
new file mode 100644
index 0000000..7daa5cb
--- /dev/null
+++ b/replace/unit_test.go
@@ -0,0 +1,11 @@
+package replace
+
+import (
+ "testing"
+)
+
+func TestForFileName(t *testing.T) {
+ str := "Hello, 世界!123abc!@#$%^&*()_+"
+ ret := ForFileName(str)
+ t.Log(ret)
+}
diff --git a/sim/similar.go b/sim/similar.go
new file mode 100644
index 0000000..9983862
--- /dev/null
+++ b/sim/similar.go
@@ -0,0 +1,61 @@
+package sim
+
+import (
+ "fmt"
+ "image"
+ _ "image/jpeg"
+ _ "image/png"
+ "math"
+ "os"
+)
+
+func Similar(p1, p2 string) {
+ img1, err := loadImage(p1)
+ if err != nil {
+ fmt.Println("Failed to load image1:", err)
+ return
+ }
+
+ img2, err := loadImage(p2)
+ if err != nil {
+ fmt.Println("Failed to load image2:", err)
+ return
+ }
+
+ similarity := calculateSimilarity(img1, img2)
+ fmt.Println("Similarity:", similarity)
+}
+
+func loadImage(filename string) (image.Image, error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+
+ return img, nil
+}
+
+func calculateSimilarity(img1, img2 image.Image) float64 {
+ bounds := img1.Bounds()
+ totalPixels := bounds.Dx() * bounds.Dy()
+ diffPixels := 0
+
+ for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ r1, g1, b1, _ := img1.At(x, y).RGBA()
+ r2, g2, b2, _ := img2.At(x, y).RGBA()
+
+ diffPixels += int(math.Abs(float64(r1-r2)/0xffff*255)) +
+ int(math.Abs(float64(g1-g2)/0xffff*255)) +
+ int(math.Abs(float64(b1-b2)/0xffff*255))
+ }
+ }
+
+ return 1 - float64(diffPixels)/(3*255*float64(totalPixels))
+}
diff --git a/sim/unit_test.go b/sim/unit_test.go
new file mode 100644
index 0000000..1bb89fe
--- /dev/null
+++ b/sim/unit_test.go
@@ -0,0 +1,22 @@
+package sim
+
+import "testing"
+
+func TestSimilar(t *testing.T) {
+ p1 := "/Users/zen/github/processAVIWithXorm/sim/10003.jpg"
+ p2 := "/Users/zen/github/processAVIWithXorm/sim/10003.jpg"
+ Similar(p1, p2)
+ //-16544.503349298768 相似
+ //-678.9719753749642 不相似
+ //1 相等
+}
+
+func TestLoad(t *testing.T) {
+ p := "/Users/zen/github/processAVIWithXorm/sim/10003.jpg"
+ image, err := loadImage(p)
+ if err != nil {
+ t.Log("panic:", err)
+ return
+ }
+ t.Log(image)
+}
diff --git a/soup/soup.go b/soup/soup.go
new file mode 100644
index 0000000..8044c60
--- /dev/null
+++ b/soup/soup.go
@@ -0,0 +1,581 @@
+/* soup package implements a simple web scraper for Go,
+keeping it as similar as possible to BeautifulSoup
+*/
+
+package soup
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ netURL "net/url"
+ "regexp"
+ "strings"
+
+ "golang.org/x/net/html"
+ "golang.org/x/net/html/charset"
+)
+
+// ErrorType defines types of errors that are possible from soup
+type ErrorType int
+
+const (
+ // ErrUnableToParse will be returned when the HTML could not be parsed
+ ErrUnableToParse ErrorType = iota
+ // ErrElementNotFound will be returned when element was not found
+ ErrElementNotFound
+ // ErrNoNextSibling will be returned when no next sibling can be found
+ ErrNoNextSibling
+ // ErrNoPreviousSibling will be returned when no previous sibling can be found
+ ErrNoPreviousSibling
+ // ErrNoNextElementSibling will be returned when no next element sibling can be found
+ ErrNoNextElementSibling
+ // ErrNoPreviousElementSibling will be returned when no previous element sibling can be found
+ ErrNoPreviousElementSibling
+ // ErrCreatingGetRequest will be returned when the get request couldn't be created
+ ErrCreatingGetRequest
+ // ErrInGetRequest will be returned when there was an error during the get request
+ ErrInGetRequest
+ // ErrCreatingPostRequest will be returned when the post request couldn't be created
+ ErrCreatingPostRequest
+ // ErrMarshallingPostRequest will be returned when the body of a post request couldn't be serialized
+ ErrMarshallingPostRequest
+ // ErrReadingResponse will be returned if there was an error reading the response to our get request
+ ErrReadingResponse
+)
+
+// Error allows easier introspection on the type of error returned.
+// If you know you have a Error, you can compare the Type to one of the exported types
+// from this package to see what kind of error it is, then further inspect the Error() method
+// to see if it has more specific details for you, like in the case of a ErrElementNotFound
+// type of error.
+type Error struct {
+ Type ErrorType
+ msg string
+}
+
+func (se Error) Error() string {
+ return se.msg
+}
+
+func newError(t ErrorType, msg string) Error {
+ return Error{Type: t, msg: msg}
+}
+
+// Root is a structure containing a pointer to an html node, the node value, and an error variable to return an error if one occurred
+type Root struct {
+ Pointer *html.Node
+ NodeValue string
+ Error error
+}
+
+// Init a new HTTP client for use when the client doesn't want to use their own.
+var (
+ defaultClient = &http.Client{}
+
+ debug = false
+
+ // Headers contains all HTTP headers to send
+ Headers = make(map[string]string)
+
+ // Cookies contains all HTTP cookies to send
+ Cookies = make(map[string]string)
+)
+
+// SetDebug sets the debug status
+// Setting this to true causes the panics to be thrown and logged onto the console.
+// Setting this to false causes the errors to be saved in the Error field in the returned struct.
+func SetDebug(d bool) {
+ debug = d
+}
+
+// Header sets a new HTTP header
+func Header(n string, v string) {
+ Headers[n] = v
+}
+
+// Cookie sets a cookie for http requests
+func Cookie(n string, v string) {
+ Cookies[n] = v
+}
+
+// GetWithClient returns the HTML returned by the url using a provided HTTP client
+func GetWithClient(url string, client *http.Client) (string, error) {
+ req, err := http.NewRequest("GET", url, nil)
+ req.Header.Set("User-Agent", "Apifox/1.0.0 (https://www.apifox.cn)")
+ if err != nil {
+ if debug {
+ panic("Couldn't create GET request to " + url)
+ }
+ return "", newError(ErrCreatingGetRequest, "error creating get request to "+url)
+ }
+
+ setHeadersAndCookies(req)
+
+ // Perform request
+ resp, err := client.Do(req)
+ if err != nil {
+ if debug {
+ panic("Couldn't perform GET request to " + url)
+ }
+ return "", newError(ErrInGetRequest, "couldn't perform GET request to "+url)
+ }
+ defer resp.Body.Close()
+ utf8Body, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
+ if err != nil {
+ return "", err
+ }
+ bytes, err := ioutil.ReadAll(utf8Body)
+ if err != nil {
+ if debug {
+ panic("Unable to read the response body")
+ }
+ return "", newError(ErrReadingResponse, "unable to read the response body")
+ }
+ return string(bytes), nil
+}
+
+// setHeadersAndCookies helps build a request
+func setHeadersAndCookies(req *http.Request) {
+ // Set headers
+ for hName, hValue := range Headers {
+ req.Header.Set(hName, hValue)
+ }
+ // Set cookies
+ for cName, cValue := range Cookies {
+ req.AddCookie(&http.Cookie{
+ Name: cName,
+ Value: cValue,
+ })
+ }
+}
+
+// getBodyReader serializes the body for a network request. See the test file for examples
+func getBodyReader(rawBody interface{}) (io.Reader, error) {
+ var bodyReader io.Reader
+
+ if rawBody != nil {
+ switch body := rawBody.(type) {
+ case map[string]string:
+ jsonBody, err := json.Marshal(body)
+ if err != nil {
+ if debug {
+ panic("Unable to read the response body")
+ }
+ return nil, newError(ErrMarshallingPostRequest, "couldn't serialize map of strings to JSON.")
+ }
+ bodyReader = bytes.NewBuffer(jsonBody)
+ case netURL.Values:
+ bodyReader = strings.NewReader(body.Encode())
+ case []byte: //expects JSON format
+ bodyReader = bytes.NewBuffer(body)
+ case string: //expects JSON format
+ bodyReader = strings.NewReader(body)
+ default:
+ return nil, newError(ErrMarshallingPostRequest, fmt.Sprintf("Cannot handle body type %T", rawBody))
+ }
+ }
+
+ return bodyReader, nil
+}
+
+// PostWithClient returns the HTML returned by the url using a provided HTTP client
+// The type of the body must conform to one of the types listed in func getBodyReader()
+func PostWithClient(url string, bodyType string, body interface{}, client *http.Client) (string, error) {
+ bodyReader, err := getBodyReader(body)
+ if err != nil {
+ return "todo:", err
+ }
+
+ req, _ := http.NewRequest("POST", url, bodyReader)
+ Header("Content-Type", bodyType)
+ setHeadersAndCookies(req)
+
+ if debug {
+ // Save a copy of this request for debugging.
+ requestDump, err := httputil.DumpRequest(req, true)
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(string(requestDump))
+ }
+
+ // Perform request
+ resp, err := client.Do(req)
+
+ if err != nil {
+ if debug {
+ panic("Couldn't perform POST request to " + url)
+ }
+ return "", newError(ErrCreatingPostRequest, "couldn't perform POST request to "+url)
+ }
+ defer resp.Body.Close()
+ bytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ if debug {
+ panic("Unable to read the response body")
+ }
+ return "", newError(ErrReadingResponse, "unable to read the response body")
+ }
+ return string(bytes), nil
+}
+
+// Post returns the HTML returned by the url as a string using the default HTTP client
+func Post(url string, bodyType string, body interface{}) (string, error) {
+ return PostWithClient(url, bodyType, body, defaultClient)
+}
+
+// PostForm is a convenience method for POST requests that
+func PostForm(url string, data url.Values) (string, error) {
+ return PostWithClient(url, "application/x-www-form-urlencoded", data, defaultClient)
+}
+
+// Get returns the HTML returned by the url as a string using the default HTTP client
+func Get(url string) (string, error) {
+ return GetWithClient(url, defaultClient)
+}
+
+// Get returns the HTML returned by the url as a string using the proxy HTTP client
+func GetWithProxy(url, proxy string) (string, error) {
+ httpProxy, _ := netURL.Parse(proxy)
+ client := &http.Client{
+ Transport: &http.Transport{Proxy: http.ProxyURL(httpProxy)},
+ }
+ return GetWithClient(url, client)
+}
+
+// HTMLParse parses the HTML returning a start pointer to the DOM
+func HTMLParse(s string) Root {
+ r, err := html.Parse(strings.NewReader(s))
+ if err != nil {
+ if debug {
+ panic("Unable to parse the HTML")
+ }
+ return Root{Error: newError(ErrUnableToParse, "unable to parse the HTML")}
+ }
+ for r.Type != html.ElementNode {
+ switch r.Type {
+ case html.DocumentNode:
+ r = r.FirstChild
+ case html.DoctypeNode:
+ r = r.NextSibling
+ case html.CommentNode:
+ r = r.NextSibling
+ }
+ }
+ return Root{Pointer: r, NodeValue: r.Data}
+}
+
+// Find finds the first occurrence of the given tag name,
+// with or without attribute key and value specified,
+// and returns a struct with a pointer to it
+func (r Root) Find(args ...string) Root {
+ temp, ok := findOnce(r.Pointer, args, false, false)
+ if ok == false {
+ if debug {
+ panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
+ }
+ return Root{Error: newError(ErrElementNotFound, fmt.Sprintf("element `%s` with attributes `%s` not found", args[0], strings.Join(args[1:], " ")))}
+ }
+ return Root{Pointer: temp, NodeValue: temp.Data}
+}
+
+// FindAll finds all occurrences of the given tag name,
+// with or without key and value specified,
+// and returns an array of structs, each having
+// the respective pointers
+func (r Root) FindAll(args ...string) []Root {
+ temp := findAllofem(r.Pointer, args, false)
+ if len(temp) == 0 {
+ if debug {
+ panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
+ }
+ return []Root{}
+ }
+ pointers := make([]Root, 0, len(temp))
+ for i := 0; i < len(temp); i++ {
+ pointers = append(pointers, Root{Pointer: temp[i], NodeValue: temp[i].Data})
+ }
+ return pointers
+}
+
+// FindStrict finds the first occurrence of the given tag name
+// only if all the values of the provided attribute are an exact match
+func (r Root) FindStrict(args ...string) Root {
+ temp, ok := findOnce(r.Pointer, args, false, true)
+ if ok == false {
+ if debug {
+ panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
+ }
+ return Root{nil, "", newError(ErrElementNotFound, fmt.Sprintf("element `%s` with attributes `%s` not found", args[0], strings.Join(args[1:], " ")))}
+ }
+ return Root{Pointer: temp, NodeValue: temp.Data}
+}
+
+// FindAllStrict finds all occurrences of the given tag name
+// only if all the values of the provided attribute are an exact match
+func (r Root) FindAllStrict(args ...string) []Root {
+ temp := findAllofem(r.Pointer, args, true)
+ if len(temp) == 0 {
+ if debug {
+ panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
+ }
+ return []Root{}
+ }
+ pointers := make([]Root, 0, len(temp))
+ for i := 0; i < len(temp); i++ {
+ pointers = append(pointers, Root{Pointer: temp[i], NodeValue: temp[i].Data})
+ }
+ return pointers
+}
+
+// FindNextSibling finds the next sibling of the pointer in the DOM
+// returning a struct with a pointer to it
+func (r Root) FindNextSibling() Root {
+ nextSibling := r.Pointer.NextSibling
+ if nextSibling == nil {
+ if debug {
+ panic("No next sibling found")
+ }
+ return Root{Error: newError(ErrNoNextSibling, "no next sibling found")}
+ }
+ return Root{Pointer: nextSibling, NodeValue: nextSibling.Data}
+}
+
+// FindPrevSibling finds the previous sibling of the pointer in the DOM
+// returning a struct with a pointer to it
+func (r Root) FindPrevSibling() Root {
+ prevSibling := r.Pointer.PrevSibling
+ if prevSibling == nil {
+ if debug {
+ panic("No previous sibling found")
+ }
+
+ return Root{Error: newError(ErrNoPreviousSibling, "no previous sibling found")}
+ }
+ return Root{Pointer: prevSibling, NodeValue: prevSibling.Data}
+}
+
+// FindNextElementSibling finds the next element sibling of the pointer in the DOM
+// returning a struct with a pointer to it
+func (r Root) FindNextElementSibling() Root {
+ nextSibling := r.Pointer.NextSibling
+ if nextSibling == nil {
+ if debug {
+ panic("No next element sibling found")
+ }
+ return Root{Error: newError(ErrNoNextElementSibling, "no next element sibling found")}
+ }
+ if nextSibling.Type == html.ElementNode {
+ return Root{Pointer: nextSibling, NodeValue: nextSibling.Data}
+ }
+ p := Root{Pointer: nextSibling, NodeValue: nextSibling.Data}
+ return p.FindNextElementSibling()
+}
+
+// FindPrevElementSibling finds the previous element sibling of the pointer in the DOM
+// returning a struct with a pointer to it
+func (r Root) FindPrevElementSibling() Root {
+ prevSibling := r.Pointer.PrevSibling
+ if prevSibling == nil {
+ if debug {
+ panic("No previous element sibling found")
+ }
+ return Root{Error: newError(ErrNoPreviousElementSibling, "no previous element sibling found")}
+ }
+ if prevSibling.Type == html.ElementNode {
+ return Root{Pointer: prevSibling, NodeValue: prevSibling.Data}
+ }
+ p := Root{Pointer: prevSibling, NodeValue: prevSibling.Data}
+ return p.FindPrevElementSibling()
+}
+
+// Children returns all direct children of this DOME element.
+func (r Root) Children() []Root {
+ child := r.Pointer.FirstChild
+ var children []Root
+ for child != nil {
+ children = append(children, Root{Pointer: child, NodeValue: child.Data})
+ child = child.NextSibling
+ }
+ return children
+}
+
+// Attrs returns a map containing all attributes
+func (r Root) Attrs() map[string]string {
+ if r.Pointer.Type != html.ElementNode {
+ if debug {
+ panic("Not an ElementNode")
+ }
+ return nil
+ }
+ if len(r.Pointer.Attr) == 0 {
+ return nil
+ }
+ return getKeyValue(r.Pointer.Attr)
+}
+
+// Text returns the string inside a non-nested element
+func (r Root) Text() string {
+ k := r.Pointer.FirstChild
+checkNode:
+ if k != nil && k.Type != html.TextNode {
+ k = k.NextSibling
+ if k == nil {
+ if debug {
+ panic("No text node found")
+ }
+ return ""
+ }
+ goto checkNode
+ }
+ if k != nil {
+ r, _ := regexp.Compile(`^\s+$`)
+ if ok := r.MatchString(k.Data); ok {
+ k = k.NextSibling
+ if k == nil {
+ if debug {
+ panic("No text node found")
+ }
+ return ""
+ }
+ goto checkNode
+ }
+ return k.Data
+ }
+ return ""
+}
+
+// HTML returns the HTML code for the specific element
+func (r Root) HTML() string {
+ var buf bytes.Buffer
+ if err := html.Render(&buf, r.Pointer); err != nil {
+ return ""
+ }
+ return buf.String()
+}
+
+// FullText returns the string inside even a nested element
+func (r Root) FullText() string {
+ var buf bytes.Buffer
+
+ var f func(*html.Node)
+ f = func(n *html.Node) {
+ if n == nil {
+ return
+ }
+ if n.Type == html.TextNode {
+ buf.WriteString(n.Data)
+ }
+ if n.Type == html.ElementNode {
+ f(n.FirstChild)
+ }
+ if n.NextSibling != nil {
+ f(n.NextSibling)
+ }
+ }
+
+ f(r.Pointer.FirstChild)
+
+ return buf.String()
+}
+
+func matchElementName(n *html.Node, name string) bool {
+ return name == "" || name == n.Data
+}
+
+// Using depth first search to find the first occurrence and return
+func findOnce(n *html.Node, args []string, uni bool, strict bool) (*html.Node, bool) {
+ if uni == true {
+ if n.Type == html.ElementNode && matchElementName(n, args[0]) {
+ if len(args) > 1 && len(args) < 4 {
+ for i := 0; i < len(n.Attr); i++ {
+ attr := n.Attr[i]
+ searchAttrName := args[1]
+ searchAttrVal := args[2]
+ if (strict && attributeAndValueEquals(attr, searchAttrName, searchAttrVal)) ||
+ (!strict && attributeContainsValue(attr, searchAttrName, searchAttrVal)) {
+ return n, true
+ }
+ }
+ } else if len(args) == 1 {
+ return n, true
+ }
+ }
+ }
+ uni = true
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ p, q := findOnce(c, args, true, strict)
+ if q != false {
+ return p, q
+ }
+ }
+ return nil, false
+}
+
+// Using depth first search to find all occurrences and return
+func findAllofem(n *html.Node, args []string, strict bool) []*html.Node {
+ var nodeLinks = make([]*html.Node, 0, 10)
+ var f func(*html.Node, []string, bool)
+ f = func(n *html.Node, args []string, uni bool) {
+ if uni == true {
+ if n.Type == html.ElementNode && matchElementName(n, args[0]) {
+ if len(args) > 1 && len(args) < 4 {
+ for i := 0; i < len(n.Attr); i++ {
+ attr := n.Attr[i]
+ searchAttrName := args[1]
+ searchAttrVal := args[2]
+ if (strict && attributeAndValueEquals(attr, searchAttrName, searchAttrVal)) ||
+ (!strict && attributeContainsValue(attr, searchAttrName, searchAttrVal)) {
+ nodeLinks = append(nodeLinks, n)
+ }
+ }
+ } else if len(args) == 1 {
+ nodeLinks = append(nodeLinks, n)
+ }
+ }
+ }
+ uni = true
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ f(c, args, true)
+ }
+ }
+ f(n, args, false)
+ return nodeLinks
+}
+
+// attributeAndValueEquals reports when the html.Attribute attr has the same attribute name and value as from
+// provided arguments
+func attributeAndValueEquals(attr html.Attribute, attribute, value string) bool {
+ return attr.Key == attribute && attr.Val == value
+}
+
+// attributeContainsValue reports when the html.Attribute attr has the same attribute name as from provided
+// attribute argument and compares if it has the same value in its values parameter
+func attributeContainsValue(attr html.Attribute, attribute, value string) bool {
+ if attr.Key == attribute {
+ for _, attrVal := range strings.Fields(attr.Val) {
+ if attrVal == value {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// Returns a key pair value (like a dictionary) for each attribute
+func getKeyValue(attributes []html.Attribute) map[string]string {
+ var keyvalues = make(map[string]string)
+ for i := 0; i < len(attributes); i++ {
+ _, exists := keyvalues[attributes[i].Key]
+ if exists == false {
+ keyvalues[attributes[i].Key] = attributes[i].Val
+ }
+ }
+ return keyvalues
+}
diff --git a/soup/soup_test.go b/soup/soup_test.go
new file mode 100644
index 0000000..2ec3b05
--- /dev/null
+++ b/soup/soup_test.go
@@ -0,0 +1,330 @@
+package soup
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const testHTML = `
+
+
+
+
+
+
+
+
+
+
+
+ Sample "Hello, World" Application
+