From 46b314dc18c23713e75d1969983ca3ec6c401b47 Mon Sep 17 00:00:00 2001 From: mi wsl Date: Thu, 14 Dec 2023 11:43:47 +0800 Subject: [PATCH] clean --- .github/RELEASE-TEMPLATE.md | 0 .github/workflows/basic.yml | 44 ++ .github/workflows/release.yml | 41 + .gitignore | 23 + GBK2UTF8/GBK2UTF8.go | 97 +++ GetAllFolder/GetAllFolder.go | 34 + GetAllFolder/unit_test.go | 13 + GetFileInfo/basic.go | 186 +++++ GetFileInfo/h264.json | 94 +++ GetFileInfo/preload.go | 184 +++++ GetFileInfo/unit_test.go | 82 ++ LICENSE | 73 ++ README.md | 310 ++++++++ alert/README.md | 16 + alert/constant.go | 55 ++ alert/email.go | 84 ++ alert/voice.go | 67 ++ asmrgay/decoder.go | 24 + asmrgay/exam.html | 0 asmrgay/exam.txt | 0 asmrgay/fromFile.go | 139 ++++ asmrgay/readme.md | 1 + asmrgay/unit_test.go | 23 + build.sh | 31 + chang/Monitor.go | 24 + chang/example.go | 76 ++ chang/unit_test.go | 14 + conf.ini | 74 ++ console.sql | 14 + constant/constant.go | 20 + duplicate/dup.go | 22 + duplicate/unit_test.go | 7 + ffmpeg.sh | 22 + go.mod | 36 + go.sum | 346 +++++++++ help.sql | 9 + information/machine.go | 10 + lines.txt | 92 +++ main.go | 363 +++++++++ mediaInfo/Audio.go | 111 +++ mediaInfo/General.go | 73 ++ mediaInfo/Image.go | 96 +++ mediaInfo/README.md | 1367 +++++++++++++++++++++++++++++++++ mediaInfo/Video.go | 193 +++++ mediaInfo/audio.json | 59 ++ mediaInfo/general.json | 21 + mediaInfo/image.json | 33 + mediaInfo/unit_test.go | 8 + mediaInfo/video.json | 116 +++ merge/extractAAC.go | 61 ++ merge/merge.go | 234 ++++++ merge/merge_test.go | 7 + model/audio.go | 74 ++ model/av.go | 78 ++ model/err.go | 55 ++ model/file.go | 82 ++ model/image.go | 76 ++ model/louder.go | 76 ++ model/mytest.go | 41 + model/save.go | 64 ++ model/speedUp.go | 76 ++ model/task.go | 39 + model/telegram.go | 69 ++ model/text.go | 64 ++ model/unit_test.go | 22 + model/video.go | 87 +++ model/ytdlp.go | 65 ++ offsite/offset.go | 43 ++ offsite/unit_test.go | 10 + processAudio/aac.go | 41 + processAudio/louder.go | 63 ++ processAudio/ogg.go | 39 + processAudio/speedUp.go | 88 +++ processImage/avif.go | 60 ++ processImage/unit_test.go | 26 + processVideo/PAL.go | 55 ++ processVideo/README.md | 5 + processVideo/Resize.go | 106 +++ processVideo/Rotate.go | 54 ++ processVideo/extractAudio.go | 40 + processVideo/h265.go | 168 ++++ processVideo/report.go | 57 ++ processVideo/speedUp.go | 84 ++ processVideo/unit_test.go | 34 + rename/README.md | 6 + rename/customRename.go | 48 ++ rename/rename.go | 84 ++ rename/unit_test.go | 12 + replace.py | 37 + replace/filename.go | 74 ++ replace/tty.go | 13 + replace/unit_test.go | 11 + sim/similar.go | 61 ++ sim/unit_test.go | 22 + soup/soup.go | 581 ++++++++++++++ soup/soup_test.go | 330 ++++++++ splicing/aac.go | 18 + splicing/unit_test.go | 31 + splicing/work.txt | 2 + sql/lite.go | 38 + sql/unit_test.go | 8 + sql/ytdlp.go | 27 + storage/mysql/mysql.go | 58 ++ telegraph/README.md | 6 + telegraph/exam.html | 2 + telegraph/fromFile.go | 132 ++++ telegraph/fromHtml.go | 23 + telegraph/fromWeb.go | 63 ++ telegraph/replace.go | 25 + telegraph/unit_test.go | 47 ++ unit_test.go | 18 + util/Gracefully.go | 41 + util/cmd.go | 58 ++ util/conf.go | 50 ++ util/curl.go | 243 ++++++ util/duplicate.go | 16 + util/goini/conf.go | 154 ++++ util/io.go | 47 ++ util/randon.go | 19 + util/root.go | 17 + util/rwLock.go | 70 ++ util/save.go | 58 ++ util/threads.go | 1 + util/timeout.go | 20 + util/unit_test.go | 104 +++ xhamster/file2shell.go | 17 + xhamster/split.go | 18 + xhamster/unit_test.go | 15 + ytdlp/yt-dlp.go | 65 ++ 129 files changed, 9860 insertions(+) create mode 100644 .github/RELEASE-TEMPLATE.md create mode 100644 .github/workflows/basic.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 GBK2UTF8/GBK2UTF8.go create mode 100644 GetAllFolder/GetAllFolder.go create mode 100644 GetAllFolder/unit_test.go create mode 100644 GetFileInfo/basic.go create mode 100644 GetFileInfo/h264.json create mode 100644 GetFileInfo/preload.go create mode 100644 GetFileInfo/unit_test.go create mode 100644 LICENSE create mode 100644 README.md create mode 100644 alert/README.md create mode 100644 alert/constant.go create mode 100644 alert/email.go create mode 100644 alert/voice.go create mode 100644 asmrgay/decoder.go create mode 100644 asmrgay/exam.html create mode 100644 asmrgay/exam.txt create mode 100644 asmrgay/fromFile.go create mode 100644 asmrgay/readme.md create mode 100644 asmrgay/unit_test.go create mode 100644 build.sh create mode 100644 chang/Monitor.go create mode 100644 chang/example.go create mode 100644 chang/unit_test.go create mode 100644 conf.ini create mode 100644 console.sql create mode 100644 constant/constant.go create mode 100644 duplicate/dup.go create mode 100644 duplicate/unit_test.go create mode 100644 ffmpeg.sh create mode 100644 go.mod create mode 100644 go.sum create mode 100644 help.sql create mode 100644 information/machine.go create mode 100644 lines.txt create mode 100644 main.go create mode 100644 mediaInfo/Audio.go create mode 100644 mediaInfo/General.go create mode 100644 mediaInfo/Image.go create mode 100644 mediaInfo/README.md create mode 100644 mediaInfo/Video.go create mode 100644 mediaInfo/audio.json create mode 100644 mediaInfo/general.json create mode 100644 mediaInfo/image.json create mode 100644 mediaInfo/unit_test.go create mode 100644 mediaInfo/video.json create mode 100644 merge/extractAAC.go create mode 100644 merge/merge.go create mode 100644 merge/merge_test.go create mode 100644 model/audio.go create mode 100644 model/av.go create mode 100644 model/err.go create mode 100644 model/file.go create mode 100644 model/image.go create mode 100644 model/louder.go create mode 100644 model/mytest.go create mode 100644 model/save.go create mode 100644 model/speedUp.go create mode 100644 model/task.go create mode 100644 model/telegram.go create mode 100644 model/text.go create mode 100644 model/unit_test.go create mode 100644 model/video.go create mode 100644 model/ytdlp.go create mode 100644 offsite/offset.go create mode 100644 offsite/unit_test.go create mode 100644 processAudio/aac.go create mode 100644 processAudio/louder.go create mode 100644 processAudio/ogg.go create mode 100644 processAudio/speedUp.go create mode 100644 processImage/avif.go create mode 100644 processImage/unit_test.go create mode 100644 processVideo/PAL.go create mode 100644 processVideo/README.md create mode 100644 processVideo/Resize.go create mode 100644 processVideo/Rotate.go create mode 100644 processVideo/extractAudio.go create mode 100644 processVideo/h265.go create mode 100644 processVideo/report.go create mode 100644 processVideo/speedUp.go create mode 100644 processVideo/unit_test.go create mode 100644 rename/README.md create mode 100644 rename/customRename.go create mode 100644 rename/rename.go create mode 100644 rename/unit_test.go create mode 100644 replace.py create mode 100644 replace/filename.go create mode 100644 replace/tty.go create mode 100644 replace/unit_test.go create mode 100644 sim/similar.go create mode 100644 sim/unit_test.go create mode 100644 soup/soup.go create mode 100644 soup/soup_test.go create mode 100644 splicing/aac.go create mode 100644 splicing/unit_test.go create mode 100644 splicing/work.txt create mode 100644 sql/lite.go create mode 100644 sql/unit_test.go create mode 100644 sql/ytdlp.go create mode 100644 storage/mysql/mysql.go create mode 100644 telegraph/README.md create mode 100644 telegraph/exam.html create mode 100644 telegraph/fromFile.go create mode 100644 telegraph/fromHtml.go create mode 100644 telegraph/fromWeb.go create mode 100644 telegraph/replace.go create mode 100644 telegraph/unit_test.go create mode 100644 unit_test.go create mode 100644 util/Gracefully.go create mode 100644 util/cmd.go create mode 100644 util/conf.go create mode 100644 util/curl.go create mode 100644 util/duplicate.go create mode 100644 util/goini/conf.go create mode 100644 util/io.go create mode 100644 util/randon.go create mode 100644 util/root.go create mode 100644 util/rwLock.go create mode 100644 util/save.go create mode 100644 util/threads.go create mode 100644 util/timeout.go create mode 100644 util/unit_test.go create mode 100644 xhamster/file2shell.go create mode 100644 xhamster/split.go create mode 100644 xhamster/unit_test.go create mode 100644 ytdlp/yt-dlp.go diff --git a/.github/RELEASE-TEMPLATE.md b/.github/RELEASE-TEMPLATE.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml new file mode 100644 index 0000000..215cda6 --- /dev/null +++ b/.github/workflows/basic.yml @@ -0,0 +1,44 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: go + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + - name: root + run: pwd + - name: ls1 + run: ls -R /home/runner/work/processAll + - name: Save Build + run: mkdir -p /home/runner/work/processAll/exec + - name: Build for linux/amd64 + run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o /home/runner/work/processAll/exec/process4LinuxAmd64 /home/runner/work/processAll/processAll/main.go + - name: Build for linux/arm64 + run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -v -o /home/runner/work/processAll/exec/process4LinuxAmd64 /home/runner/work/processAll/processAll/main.go + - name: Build for windows/amd64 + run: CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -v -o /home/runner/work/processAll/exec/process4Win64.exe /home/runner/work/processAll/processAll/main.go + - name: Build for darwin/amd64 + run: CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -v -o /home/runner/work/processAll/exec/process4Mac /home/runner/work/processAll/processAll/main.go + - name: Build for darwin/arm64 + run: CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -v -o /home/runner/work/processAll/exec/process4M1 /home/runner/work/processAll/processAll/main.go + - name: Build for android/arm64 + run: CGO_ENABLED=0 GOOS=android GOARCH=arm64 go build -v -o /home/runner/work/processAll/exec/process4android /home/runner/work/processAll/processAll/main.go + - name: show build file + run: ls -alhtSF /home/runner/work/processAll/exec +# - name: Test +# run: go test -v ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e6fda61 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 +#on: +# push: +# branches: [ "master" ] +# pull_request: +# branches: [ "master" ] +name: Latest Release + +jobs: + build: + name: GoReleaser build + runs-on: ubuntu-latest + + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + with: + fetch-depth: 0 # See: https://goreleaser.com/ci/actions/ + + - name: Set up Go 1.21.4 + uses: actions/setup-go@v2 + with: + go-version: 1.21.4 + id: go + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + version: latest + args: release --clean +# env: +# GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6804dd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +*.exe +*.exe~ +*.dll +*.so +*.dylib +vendor +.DS_Store +.idea +*.log +process4Linux32 +process4Linux64 +process4Raspi +process4Raspi64 +process4Win32.exe +process4Win64.exe +process4Mac +process4M1 +process4Android +/soup/exam.txt +/trans.db +lines.txt +*.db +lines.txt \ No newline at end of file diff --git a/GBK2UTF8/GBK2UTF8.go b/GBK2UTF8/GBK2UTF8.go new file mode 100644 index 0000000..03c694e --- /dev/null +++ b/GBK2UTF8/GBK2UTF8.go @@ -0,0 +1,97 @@ +package GBK2UTF8 + +import ( + "github.com/zhangyiming748/mahonia" + "log/slog" + "os" + "path" + "processAll/GetFileInfo" + "strings" + "unicode/utf8" +) + +func AllGBKs2UTF8(root, pattern string) { + infos := GetFileInfo.GetAllFilesInfo(root, pattern) + for _, in := range infos { + GBK2UTF8(in) + } +} +func GBKs2UTF8(dir, pattern string) { + infos := GetFileInfo.GetAllFileInfo(dir, pattern) + for _, in := range infos { + GBK2UTF8(in) + } +} + +func GBK2UTF8(info GetFileInfo.BasicInfo) { + fp := info.FullPath + prefix := path.Dir(fp) + newFp := strings.Join([]string{prefix, "utf8", info.FullName}, string(os.PathSeparator)) + base := path.Dir(newFp) + os.MkdirAll(base, 0777) + slog.Debug("执行前的文件基本信息", slog.String("输入", fp), slog.String("输出", newFp), slog.String("前缀", prefix), slog.String("新前缀", base)) + if isUTF8(fp) { + writeUTF8(newFp, readUTF8(fp)) + slog.Debug("skip", slog.String("编码已经是UTF8,直接复制", info.FullName)) + } else { + u8 := readGB18030(fp) + nums := writeUTF8(newFp, u8) + slog.Debug("文件写入", slog.String("文件名", newFp), slog.Int("字符数", nums)) + } + if err := os.Remove(fp); err != nil { + slog.Error("删除源文件出错", slog.Any("错误文本", err), slog.String("文件名", fp)) + } else { + slog.Debug("删除源文件", slog.String("文件名", fp)) + } +} + +func isUTF8(src string) bool { + defer func() { + if err := recover(); err != nil { + slog.Error("查询是否为UTF8时产生错误", slog.Any("错误文本", err)) + } + }() + file, err := os.ReadFile(src) + if err != nil { + panic(err) + } + return utf8.Valid(file) +} + +func readGB18030(src string) string { + file, err := os.ReadFile(src) + if err != nil { + panic(err) + } + decoder := mahonia.NewDecoder("gb18030") + if decoder == nil { + slog.Error("编码不存在", slog.Any("错误文本", err)) + } + return decoder.ConvertString(string(file)) +} + +func readUTF8(src string) string { + file, err := os.ReadFile(src) + if err != nil { + slog.Error("读取utf8产生错误", slog.Any("错误文本", err)) + } + return string(file) +} + +func writeUTF8(dst, s string) int { + defer func() { + if err := recover(); err != nil { + slog.Error("转写utf8产生错误", slog.Any("错误文本", err)) + } + }() + f, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777) + if err != nil { + slog.Error("打开目标文件产生错误", slog.Any("错误文本", err)) + } + + writeString, err := f.WriteString(s) + if err != nil { + slog.Error("写文件产生错误", slog.Any("错误文本", err)) + } + return writeString +} diff --git a/GetAllFolder/GetAllFolder.go b/GetAllFolder/GetAllFolder.go new file mode 100644 index 0000000..932d209 --- /dev/null +++ b/GetAllFolder/GetAllFolder.go @@ -0,0 +1,34 @@ +package GetAllFolder + +import ( + "log/slog" + "os" + "strings" +) + +var ( + all []string +) + +/* +递归获取文件夹和全部子文件夹 +不包括文件夹本身 +*/ +func List(dirname string) []string { + fileInfos, _ := os.ReadDir(dirname) + var folders []string + for _, fi := range fileInfos { + filename := strings.Join([]string{dirname, fi.Name()}, string(os.PathSeparator)) //拼写当前文件夹中所有的文件地址 + if fi.IsDir() { //判断是否是文件夹 如果是继续调用把自己的地址作为参数继续调用 + if strings.Contains(filename, "/.") { + slog.Debug("跳过隐藏文件夹", slog.Any("文件夹名", fi.Name())) + continue + } + slog.Debug("获取到文件夹", slog.String("文件夹名", filename)) + all = append(all, filename) + folders = append(folders, filename) + List(filename) //递归调用 + } + } + return all +} diff --git a/GetAllFolder/unit_test.go b/GetAllFolder/unit_test.go new file mode 100644 index 0000000..18adf9f --- /dev/null +++ b/GetAllFolder/unit_test.go @@ -0,0 +1,13 @@ +package GetAllFolder + +import "testing" + +/* +go test -run TestListFolders -v +*/ +func TestListFolders(t *testing.T) { + ret := List("/home/zen/Downloads") + for _, d := range ret { + t.Log(d) + } +} diff --git a/GetFileInfo/basic.go b/GetFileInfo/basic.go new file mode 100644 index 0000000..8be8a2e --- /dev/null +++ b/GetFileInfo/basic.go @@ -0,0 +1,186 @@ +package GetFileInfo + +// todo 重新改写为 一个-一个文件夹-全部文件夹 +import ( + "fmt" + "log/slog" + "os" + "path" + "path/filepath" + "processAll/GetAllFolder" + "processAll/mediaInfo" + "processAll/model" + "strings" + "sync" +) + +// note 重新改写为具有全部类型文件的通用结构体 + +type BasicInfo struct { + FullPath string `json:"full_path,omitempty"` // 文件的绝对路径 + FullName string `json:"full_name,omitempty"` // 文件名 + PurgeName string `json:"purge_name,omitempty"` // 单纯文件名 + PurgeExt string `json:"purge_ext,omitempty"` // 单纯扩展名 + PurgePath string `json:"purge_path,omitempty"` // 文件所在路径 包含最后一个路径分隔符 + MD5 string `json:"md_5,omitempty"` // 文件MD5 + MediaInfo interface{} `json:"media_info"` // 文件类型对应的mediainfo结构体 +} + +func (i *BasicInfo) SetAudioMediaInfo(info mediaInfo.AudioInfo) { + i.MediaInfo = info +} +func (i *BasicInfo) SetImageMediaInfo(info mediaInfo.ImageInfo) { + i.MediaInfo = info +} +func (i *BasicInfo) SetGeneralMediaInfo(info mediaInfo.GeneralInfo) { + i.MediaInfo = info +} +func (i *BasicInfo) SetVideoMediaInfo(info mediaInfo.VideoInfo) { + i.MediaInfo = info +} + +/* +获取单个文件基础信息 +*/ + +func GetFileInfo(absPath string) BasicInfo { + ext := path.Ext(absPath) + dir, file := filepath.Split(absPath) + i := BasicInfo{ + FullPath: absPath, + FullName: file, + PurgeName: strings.Replace(file, ext, "", 1), + PurgeExt: strings.Replace(ext, ".", "", 1), + PurgePath: dir, + MD5: GetMD5(absPath), + } + // todo 测试使用文件头判断文件类型 + //t := SelectType(strings.Replace(ext, ".", "", 1)) + t := SelectTypeByHead(absPath) + switch t { + case "Audio": + i.SetAudioMediaInfo(mediaInfo.GetAudioMedia(absPath)) + case "Video": + i.SetVideoMediaInfo(mediaInfo.GetVideoMedia(absPath)) + case "Image": + i.SetImageMediaInfo(mediaInfo.GetImageMedia(absPath)) + case "General": + i.SetGeneralMediaInfo(mediaInfo.GetGeneralMedia(absPath)) + } + return i +} + +/* +获取目录下符合条件的所有文件基础信息 +*/ +func GetAllFileInfo(dir, pattern string) []BasicInfo { + var aim []BasicInfo + files, err := os.ReadDir(dir) + if err != nil { + slog.Warn("出错", slog.Any("读取文件夹下内容", err)) + return nil + } + for _, file := range files { + if strings.HasPrefix(file.Name(), ".") { + slog.Debug("获取文件信息", slog.String("跳过隐藏文件", file.Name())) + continue + } + if file.IsDir() { + slog.Debug("获取文件信息", slog.String("跳过文件夹", file.Name())) + continue + } + absPath := strings.Join([]string{dir, file.Name()}, string(os.PathSeparator)) + ext := strings.Replace(path.Ext(absPath), ".", "", 1) + if In(ext, strings.Split(pattern, ";")) { + bi := GetFileInfo(absPath) + aim = append(aim, bi) + slog.Debug("获取到的单个文件全部信息", slog.Any("", bi)) + } else { + slog.Info("跳过非目标文件") + } + } + return aim +} + +/* +获取全部目录下符合条件的所有文件基础信息 +*/ +func GetAllFilesInfo(dir, pattern string) []BasicInfo { + var aims []BasicInfo + folders := GetAllFolder.List(dir) + //包括根目录 + folders = append(folders, dir) + for _, folder := range folders { + aim := GetAllFileInfo(folder, pattern) + aims = append(aims, aim...) + } + return aims +} + +/* +通过channel获取目录下符合条件的所有文件基础信息 +*/ +func GetAllFileInfoByChan(dir, pattern string, limit chan struct{}, msg chan []BasicInfo, wg *sync.WaitGroup) { + defer wg.Done() + msg <- GetAllFileInfo(dir, pattern) + <-limit +} + +/* +通过channel获取全部目录下符合条件的所有文件基础信息 +*/ +func GetAllFilesInfoByChan(dir, pattern string) []BasicInfo { + var aims []BasicInfo + + var wg sync.WaitGroup + limit := make(chan struct{}, 12) + msg := make(chan []BasicInfo, 1) + + folders := GetAllFolder.List(dir) + //包括根目录 + folders = append(folders, dir) + + for _, folder := range folders { + limit <- struct{}{} + wg.Add(1) + go GetAllFileInfoByChan(folder, pattern, limit, msg, &wg) + } + go func() { + wg.Wait() + close(msg) + }() + + for data := range msg { + slog.Debug("msg", slog.Any("通道中获取", data)) + aims = append(aims, data...) + } + return aims +} + +//func (i *BasicInfo) setMD5(md5 string) { +// i.MD5 = md5 +//} + +/* +获取全部文件的文件名,批量插入数据库 +*/ +func GetAllFiles(dir string) (names []string) { + files, err := os.ReadDir(dir) + if err != nil { + slog.Warn("读取目录下文件出错", slog.String("错误文本", fmt.Sprint(err))) + } + for _, file := range files { + if strings.HasPrefix(file.Name(), ".") { + continue + } + names = append(names, file.Name()) + } + var cs []*model.Custom + for _, name := range names { + c := new(model.Custom) + c.FileName = name + cs = append(cs, c) + } + go new(model.Custom).InsertAll(cs) + return names +} diff --git a/GetFileInfo/h264.json b/GetFileInfo/h264.json new file mode 100644 index 0000000..f34b099 --- /dev/null +++ b/GetFileInfo/h264.json @@ -0,0 +1,94 @@ +{ + "creatingLibrary": { + "name": "MediaInfoLib", + "version": "22.12", + "url": "https://mediaarea.net/MediaInfo" + }, + "media": { + "@ref": "愛錯 - LeeHom 節省錢 翻唱.mp4", + "track": [ + { + "@type": "General", + "VideoCount": "1", + "AudioCount": "1", + "FileExtension": "mp4", + "Format": "MPEG-4", + "Format_Profile": "Base Media", + "CodecID": "isom", + "CodecID_Compatible": "isom/iso2/avc1/mp41", + "FileSize": "83331213", + "Duration": "326.798", + "OverallBitRate": "2039944", + "FrameRate": "23.976", + "FrameCount": "7834", + "StreamSize": "279819", + "HeaderSize": "40", + "DataSize": "83051402", + "FooterSize": "279771", + "IsStreamable": "No", + "File_Modified_Date": "UTC 2023-02-18 05:52:01", + "File_Modified_Date_Local": "2023-02-18 13:52:01", + "Encoded_Application": "Lavf59.30.100" + }, + { + "@type": "Video", + "StreamOrder": "0", + "ID": "1", + "Format": "AVC", + "Format_Profile": "High", + "Format_Level": "4", + "Format_Settings_CABAC": "Yes", + "Format_Settings_RefFrames": "3", + "CodecID": "avc1", + "Duration": "326.744", + "BitRate": "1907825", + "Width": "1920", + "Height": "1080", + "Stored_Height": "1088", + "Sampled_Width": "1920", + "Sampled_Height": "1080", + "PixelAspectRatio": "1.000", + "DisplayAspectRatio": "1.778", + "Rotation": "0.000", + "FrameRate_Mode": "VFR", + "FrameRate": "23.976", + "FrameRate_Minimum": "23.974", + "FrameRate_Maximum": "23.981", + "FrameCount": "7834", + "ColorSpace": "YUV", + "ChromaSubsampling": "4:2:0", + "BitDepth": "8", + "ScanType": "Progressive", + "StreamSize": "77921095", + "extra": { + "CodecConfigurationBox": "avcC" + } + }, + { + "@type": "Audio", + "StreamOrder": "1", + "ID": "2", + "Format": "AAC", + "Format_AdditionalFeatures": "LC", + "CodecID": "mp4a-40-2", + "Duration": "326.798", + "BitRate_Mode": "CBR", + "BitRate": "125589", + "Channels": "2", + "ChannelPositions": "Front: L R", + "ChannelLayout": "L R", + "SamplesPerFrame": "1024", + "SamplingRate": "44100", + "SamplingCount": "14411792", + "FrameRate": "43.066", + "FrameCount": "14074", + "Compression_Mode": "Lossy", + "StreamSize": "5130299", + "StreamSize_Proportion": "0.06157", + "Default": "Yes", + "AlternateGroup": "1" + } + ] + } +} + diff --git a/GetFileInfo/preload.go b/GetFileInfo/preload.go new file mode 100644 index 0000000..b4e1e68 --- /dev/null +++ b/GetFileInfo/preload.go @@ -0,0 +1,184 @@ +package GetFileInfo + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "github.com/zhangyiming748/filetype" + "io" + "log/slog" + "os" + "os/exec" + "regexp" +) + +/* +判断当前文件扩展名是否在选定的扩展名列表中 +*/ +func In(target string, str_array []string) bool { + for _, element := range str_array { + if target == element { + return true + } + } + return false +} + +/* +通过文件头判断文件类型 +选择合适的结构体 +*/ +func SelectTypeByHead(fp string) string { + file, _ := os.Open(fp) + // We only have to pass the file header = first 261 bytes + head := make([]byte, 261) + file.Read(head) + if filetype.IsVideo(head) { + slog.Debug("File is a video") + return "Video" + } else if filetype.IsAudio(head) { + slog.Debug("File is a audio") + return "Audio" + } else if filetype.IsImage(head) { + slog.Debug("File is a image") + return "Image" + } else { + slog.Debug("File is a general") + return "General" + } +} + +/* +获取文件MD5 +*/ +func GetMD5(fp string) string { + pFile, err := os.Open(fp) + if err != nil { + slog.Warn("获取md5打开文件出错", slog.String("文件名", fp), slog.Any("错误文本", err)) + return "" + } + defer pFile.Close() + md5h := md5.New() + io.Copy(md5h, pFile) + return hex.EncodeToString(md5h.Sum(nil)) +} + +/* +通过扩展名判断文件类型 +选择合适的结构体 +*/ +func SelectType(ext string) string { + switch ext { + case "jpeg": + return "Image" + case "JPEG": + return "Image" + case "jpg": + return "Image" + case "JPG": + return "Image" + case "png": + return "Image" + case "PNG": + return "Image" + case "webp": + return "Image" + case "WEBP": + return "Image" + case "tif": + return "Image" + case "TIF": + return "Image" + + case "mp3": + return "Audio" + case "MP3": + return "Audio" + case "aac": + return "Audio" + case "AAC": + return "Audio" + case "m4a": + return "Audio" + case "M4A": + return "Audio" + case "flac": + return "Audio" + case "FLAC": + return "Audio" + case "wma": + return "Audio" + case "WMA": + return "Audio" + case "wav": + return "Audio" + case "WAV": + return "Audio" + case "ogg": + return "Audio" + case "OGG": + return "Audio" + + case "webm": + return "Video" + case "WEBM": + return "Video" + case "mkv": + return "Video" + case "MKV": + return "Video" + case "m4v": + return "Video" + case "M4V": + return "Video" + case "mp4": + return "Video" + case "MP4": + return "Video" + case "mov": + return "Video" + case "MOV": + return "Video" + case "avi": + return "Video" + case "AVI": + return "Video" + case "wmv": + return "Video" + case "WMV": + return "Video" + case "ts": + return "Video" + case "TS": + return "Video" + case "rmvb": + return "Video" + case "RMVB": + return "Video" + case "flv": + return "Video" + case "FLV": + return "Video" + case "vob": + return "Video" + case "VOB": + return "Video" + default: + return "General" + } +} + +func GetBitRate(fp string) (string, error) { + ff, _ := exec.Command("ffmpeg", "-i", fp).CombinedOutput() + re := regexp.MustCompile(`bitrate:\s*(\d+)\s*kb/s`) + matches := re.FindAllStringSubmatch(string(ff), -1) + if len(matches) > 0 { + str := fmt.Sprintf("比特率字段:%s\n", matches) + fmt.Println(str) + num := fmt.Sprint(matches[0][1]) + slog.Debug(num) + return num, nil + } + + return "", nil +} diff --git a/GetFileInfo/unit_test.go b/GetFileInfo/unit_test.go new file mode 100644 index 0000000..cc3752d --- /dev/null +++ b/GetFileInfo/unit_test.go @@ -0,0 +1,82 @@ +package GetFileInfo + +import ( + "fmt" + "log/slog" + "os" + "processAll/chang" + "processAll/mediaInfo" + "testing" +) + +func init() { + go chang.RunNumGoroutineMonitor() + opt := slog.HandlerOptions{ // 自定义option + AddSource: true, + Level: slog.LevelDebug, // slog 默认日志级别是 info + } + logger := slog.New(slog.NewJSONHandler(os.Stdout, &opt)) + slog.SetDefault(logger) +} + +func TestGetOneInfo(t *testing.T) { + ret := GetFileInfo("/Users/zen/Pictures/譚詠麟 - 水中花 [6LJ2mJU4BpI].m4a") + mi := ret.MediaInfo + if v, ok := mi.(mediaInfo.AudioInfo); ok { + t.Logf("mi is %+v\n", v) + } else { + fmt.Println("不ok") + } + t.Logf("%+v\n", ret) +} +func TestGetAllInfo(t *testing.T) { + ret := GetAllFileInfo("/Users/zen/Pictures", "jpg;dmg;mp4") + t.Logf("%+v\n", ret) +} + +func TestGetAllFilesInfo(t *testing.T) { + ret := GetAllFilesInfo("/mnt/e/BaiduNetdiskDownload/pikpak", "jpg;dmg;mp4;aac") + t.Logf("%+v\n", ret) +} +func TestUnit(t *testing.T) { + +} + +// 四个协程28.463秒 +// 十个协程23.26秒 +func TestGetAllFilesInfoByChan(t *testing.T) { + root := "/Users/zen/Downloads" + ret := GetAllFilesInfoByChan(root, "mp4;dmg;jpg;png;avif") + file, _ := os.OpenFile("summary.txt", 2|8|512, 0777) + defer file.Close() + for _, v := range ret { + file.WriteString(fmt.Sprintf("%+v\n", v)) + } + t.Log(ret) +} +func TestIsNotH265(t *testing.T) { + root := "/Volumes/volume/未整理" + ret := GetAllFilesInfoByChan(root, "webm;mkv;m4v;mp4;mov;avi;wmv;ts;rmvb;wma;avi;flv;rmvb") + file, _ := os.OpenFile("report.txt", 2|8|512, 0777) + for _, one := range ret { + if mi, ok := one.MediaInfo.(mediaInfo.VideoInfo); ok { + if mi.VideoFormat != "HEVC" { + file.WriteString(fmt.Sprintf("不是H265的视频:%s\n", one.FullPath)) + } else if mi.VideoCodecID != "hvc1" { + file.WriteString(fmt.Sprintf("H265的视频,没有正确的标签:%s\n", one.FullPath)) + } + } + } +} + +// go test -v -run TestGetVideoFrameFast ./ +// go test -v -run TestGetAllFileInfos ./ +// go test -v -run TestGetBitRate ./ +func TestGetBitRate(t *testing.T) { + + rate, err := GetBitRate("/media/zen/Disk2/video/再見我的愛人.mp4") + if err != nil { + return + } + t.Log(rate) +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d53bebc --- /dev/null +++ b/LICENSE @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright 2023 root + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..405c04a --- /dev/null +++ b/README.md @@ -0,0 +1,310 @@ +本地gitea服务器突然坏了,抢救出来的代码 +---- + +# processAVIWithXorm + +processA(audio)V(video)I(image)With~~Xorm~~ + +# 升级go版本 + +```shell +go install golang.org/dl/go1.21.0@latest +GOPATH/bin/go1.21.0 download +echo "PATH=/Users/zen/sdk/go1.21.0/bin:$PATH" >> ~/.bash_profile +``` + +# git http 记住密码 + +```shell +git config --global credential.helper store +git config credential.helper store +``` + +# 运行一个单元测试 +```bash +go test -v -run <测试函数名> <目录> +# example +go test -v -run TestRename ./ +``` + +# 转换为本地gitea托管前的提交 +```shell +git log --pretty=format:"%ai , %an: %s" --since="100 day ago" >> ./commit.log +``` +```text2023-10-29 12:55:00 +0800 , zen: Merge branch 'bilibili' +2023-10-29 12:53:56 +0800 , zen: save +2023-10-29 12:53:36 +0800 , zen: Merge branch 'media' +2023-10-29 12:48:49 +0800 , zen: channel模型 +2023-10-29 12:47:42 +0800 , zen: save +2023-10-29 11:24:32 +0800 , zen: save +2023-10-28 18:24:43 +0800 , zen: 更新merge方法 +2023-10-28 18:20:40 +0800 , zen: 提取音频方法 +2023-10-28 18:06:35 +0800 , zen: 视频和图片基本完成 +2023-10-28 01:57:47 +0800 , zen: 设置lfs +2023-10-26 23:17:23 +0800 , zen: 使用大佬的协程 +2023-10-26 15:19:07 +0800 , zen: 解决goto +2023-10-26 10:13:28 +0800 , zen: 经过大佬指点的协程方案 +2023-10-24 01:53:06 +0800 , zen: 模拟了一个可以正确控制并发的channel +2023-10-24 00:05:13 +0800 , zen: 删除其他逻辑 +2023-10-23 23:30:49 +0800 , zen: 暂时删除其他逻辑 +2023-10-23 10:41:10 +0800 , 张zen: 添加说明 +2023-10-23 10:37:06 +0800 , 张zen: Merge remote-tracking branch 'origin/bilibili' into bilibili +2023-10-23 10:33:45 +0800 , 张zen: 测试最大允许的长度 +2023-10-23 10:33:45 +0800 , 张zen: 测试最大允许的长度 +2023-10-23 10:26:30 +0800 , 张zen: 每个汉字三个字符长度 +2023-10-23 10:16:59 +0800 , 张zen: 截取过长文件名 +2023-10-23 10:10:00 +0800 , 张zen: init +2023-10-23 10:08:14 +0800 , 张zen: init +2023-10-23 10:04:58 +0800 , 张zen: init +2023-10-23 18:02:42 +0800 , root: Initial commit +# 清空了历史提交记录 +2023-10-06 11:12:41 +0800 , 张zen: 静态动态图片使用同一个表记录 +2023-10-06 11:12:07 +0800 , 张zen: 数据表同步提供状态输出 +2023-10-06 10:34:11 +0800 , 张zen: yt-dlp +2023-10-06 09:18:39 +0800 , 张zen: save +2023-10-04 19:51:08 +0800 , 张zen: 更新关联方法 +2023-10-04 18:39:10 +0800 , 张zen: 直接从mediainfo获取视频帧数 +2023-10-04 16:46:32 +0800 , 张zen: 防止查找失败溢出 +2023-10-04 16:43:57 +0800 , 张zen: 快速获取帧数需要在转码命令前被运行,还未解决 +2023-10-04 16:41:53 +0800 , 张zen: 视频模块使用任务表-子任务表模式 +2023-10-03 20:17:41 +0800 , 张zen: 读写锁示例 +2023-10-03 20:17:22 +0800 , 张zen: 设置日志文件不可删除 +2023-10-03 18:34:30 +0800 , 张zen: save +2023-10-03 18:21:36 +0800 , 张zen: 字符串切片去重 +2023-10-02 19:50:41 +0800 , 张zen: save +2023-10-01 12:10:51 +0800 , zen: save +2023-09-29 22:09:04 +0800 , zen: dos2unix +2023-09-29 20:41:12 +0800 , zen: dos2unix +2023-09-29 20:26:22 +0800 , zen: save +2023-09-29 20:24:00 +0800 , zen: 随机数工具 +2023-09-28 21:32:20 +0800 , zen: ffmpeg 添加截取说明 +2023-09-28 18:51:32 +0800 , zen: 视频删除章节信息 +2023-09-25 14:11:08 +0800 , zen: 完善重命名方式 +2023-09-25 11:39:18 +0800 , zen: Windows下退出 +2023-09-21 21:42:12 +0800 , zen: save +2023-09-21 17:29:16 +0800 , zen: 图片重命名 +2023-09-21 09:44:22 +0800 , zen: save +2023-09-19 23:12:41 +0800 , zen: 增大电平 +2023-09-19 20:47:50 +0800 , zen: 增加有声小说音频文件电平 +2023-09-19 20:24:52 +0800 , zen: 使用正则表达式替代合法文件名的replace方法 +2023-09-19 11:14:35 +0800 , zen: 跳过非法文件名 +2023-09-19 11:13:14 +0800 , zen: utf8mb4 +2023-09-19 11:07:44 +0800 , zen: fix +2023-09-19 11:04:06 +0800 , zen: entry保留原始数据 +2023-09-19 10:18:13 +0800 , zen: panic测试 +2023-09-19 09:52:42 +0800 , zen: import cycle not allowed +2023-09-19 09:37:48 +0800 , zen: 保存原始entry文件 +2023-09-19 09:24:32 +0800 , zen: bilibili如果name和partname一致,仅保留一种 +2023-09-19 08:46:23 +0800 , zen: save +2023-09-19 08:43:00 +0800 , zen: for bilibili +2023-09-17 22:15:13 +0800 , zen: save +2023-09-17 21:19:13 +0800 , zen: 尽可能地不使用go module +2023-09-17 20:44:47 +0800 , zen: 使用log.slog +2023-09-17 20:34:59 +0800 , zen: 动图统一转换为gif avif无法处理透明图 +2023-09-17 19:47:57 +0800 , zen: 吃饭 +2023-09-17 19:34:03 +0800 , zen: save +2023-09-17 19:33:46 +0800 , zen: 明确文件夹递归函数不包括本身文件夹 +2023-09-17 19:33:13 +0800 , zen: 发送邮件函数改为变参函数 +2023-09-17 18:57:01 +0800 , zen: 实现音频部分任务表分离 +2023-09-17 10:41:20 +0800 , zen: 跳过已经加速的文件夹 +2023-09-16 14:28:06 +0800 , zen: rename +2023-09-16 13:07:09 +0800 , zen: rename +2023-09-16 12:03:02 +0800 , zen: 重命名功能分函数 +2023-09-16 12:02:40 +0800 , zen: 数据库写入使用协程 +2023-09-14 18:26:39 +0800 , zen: 新生成二进制可执行文件 +2023-09-13 11:19:33 +0800 , zen: save +2023-09-12 13:25:46 +0800 , zen: 添加文件列表去重功能 +2023-09-12 11:13:59 +0800 , zen: go get -u +2023-09-12 11:08:33 +0800 , zen: aac转换时重命名 +2023-09-12 10:37:01 +0800 , zen: save +2023-09-10 16:10:03 +0800 , zen: 添加新电子邮件 +2023-09-09 21:34:03 +0800 , zen: save +2023-09-09 17:50:47 +0800 , zen: Merge remote-tracking branch 'origin/master' +2023-09-09 17:50:10 +0800 , zen: 添加获取asmr网站的方案 +2023-09-09 17:48:01 +0800 , zen: 添加通用的按行写文件方法 +2023-09-07 17:36:58 +0800 , zen: Amazon AI 生成 +2023-09-07 12:16:18 +0800 , zen: 删除冗余部分 +2023-09-04 22:49:02 +0800 , zen: bilibili自带即为h265无需重编码 +2023-09-03 13:12:56 +0800 , zen: save +2023-09-02 16:06:22 +0800 , zen: 更改了video获取文件夹的方法 待测试 +2023-09-02 12:25:49 +0800 , zen: AVmerge长文件名会导致错误 +2023-08-25 17:13:07 +0800 , zen: 快捷命令 +2023-08-25 17:00:46 +0800 , zen: 程序升级1.21.0 每次运行后自动编译新版本二进制文件 +2023-08-22 15:33:38 +0800 , zen: 快速修改视频尺寸 不转码 +2023-08-22 01:28:40 +0800 , zen: 二进制可执行文件 +2023-08-20 17:35:41 +0800 , zen: init +2023-08-20 17:28:32 +0800 , root: Initial commit2023-10-06 11:12:41 +0800 , 张zen: 静态动态图片使用同一个表记录 +2023-10-06 11:12:07 +0800 , 张zen: 数据表同步提供状态输出 +2023-10-06 10:34:11 +0800 , 张zen: yt-dlp +2023-10-06 09:18:39 +0800 , 张zen: save +2023-10-04 19:51:08 +0800 , 张zen: 更新关联方法 +2023-10-04 18:39:10 +0800 , 张zen: 直接从mediainfo获取视频帧数 +2023-10-04 16:46:32 +0800 , 张zen: 防止查找失败溢出 +2023-10-04 16:43:57 +0800 , 张zen: 快速获取帧数需要在转码命令前被运行,还未解决 +2023-10-04 16:41:53 +0800 , 张zen: 视频模块使用任务表-子任务表模式 +2023-10-03 20:17:41 +0800 , 张zen: 读写锁示例 +2023-10-03 20:17:22 +0800 , 张zen: 设置日志文件不可删除 +2023-10-03 18:34:30 +0800 , 张zen: save +2023-10-03 18:21:36 +0800 , 张zen: 字符串切片去重 +2023-10-02 19:50:41 +0800 , 张zen: save +2023-10-01 12:10:51 +0800 , zen: save +2023-09-29 22:09:04 +0800 , zen: dos2unix +2023-09-29 20:41:12 +0800 , zen: dos2unix +2023-09-29 20:26:22 +0800 , zen: save +2023-09-29 20:24:00 +0800 , zen: 随机数工具 +2023-09-28 21:32:20 +0800 , zen: ffmpeg 添加截取说明 +2023-09-28 18:51:32 +0800 , zen: 视频删除章节信息 +2023-09-25 14:11:08 +0800 , zen: 完善重命名方式 +2023-09-25 11:39:18 +0800 , zen: Windows下退出 +2023-09-21 21:42:12 +0800 , zen: save +2023-09-21 17:29:16 +0800 , zen: 图片重命名 +2023-09-21 09:44:22 +0800 , zen: save +2023-09-19 23:12:41 +0800 , zen: 增大电平 +2023-09-19 20:47:50 +0800 , zen: 增加有声小说音频文件电平 +2023-09-19 20:24:52 +0800 , zen: 使用正则表达式替代合法文件名的replace方法 +2023-09-19 11:14:35 +0800 , zen: 跳过非法文件名 +2023-09-19 11:13:14 +0800 , zen: utf8mb4 +2023-09-19 11:07:44 +0800 , zen: fix +2023-09-19 11:04:06 +0800 , zen: entry保留原始数据 +2023-09-19 10:18:13 +0800 , zen: panic测试 +2023-09-19 09:52:42 +0800 , zen: import cycle not allowed +2023-09-19 09:37:48 +0800 , zen: 保存原始entry文件 +2023-09-19 09:24:32 +0800 , zen: bilibili如果name和partname一致,仅保留一种 +2023-09-19 08:46:23 +0800 , zen: save +2023-09-19 08:43:00 +0800 , zen: for bilibili +2023-09-17 22:15:13 +0800 , zen: save +2023-09-17 21:19:13 +0800 , zen: 尽可能地不使用go module +2023-09-17 20:44:47 +0800 , zen: 使用log.slog +2023-09-17 20:34:59 +0800 , zen: 动图统一转换为gif avif无法处理透明图 +2023-09-17 19:47:57 +0800 , zen: 吃饭 +2023-09-17 19:34:03 +0800 , zen: save +2023-09-17 19:33:46 +0800 , zen: 明确文件夹递归函数不包括本身文件夹 +2023-09-17 19:33:13 +0800 , zen: 发送邮件函数改为变参函数 +2023-09-17 18:57:01 +0800 , zen: 实现音频部分任务表分离 +2023-09-17 10:41:20 +0800 , zen: 跳过已经加速的文件夹 +2023-09-16 14:28:06 +0800 , zen: rename +2023-09-16 13:07:09 +0800 , zen: rename +2023-09-16 12:03:02 +0800 , zen: 重命名功能分函数 +2023-09-16 12:02:40 +0800 , zen: 数据库写入使用协程 +2023-09-14 18:26:39 +0800 , zen: 新生成二进制可执行文件 +2023-09-13 11:19:33 +0800 , zen: save +2023-09-12 13:25:46 +0800 , zen: 添加文件列表去重功能 +2023-09-12 11:13:59 +0800 , zen: go get -u +2023-09-12 11:08:33 +0800 , zen: aac转换时重命名 +2023-09-12 10:37:01 +0800 , zen: save +2023-09-10 16:10:03 +0800 , zen: 添加新电子邮件 +2023-09-09 21:34:03 +0800 , zen: save +2023-09-09 17:50:47 +0800 , zen: Merge remote-tracking branch 'origin/master' +2023-09-09 17:50:10 +0800 , zen: 添加获取asmr网站的方案 +2023-09-09 17:48:01 +0800 , zen: 添加通用的按行写文件方法 +2023-09-07 17:36:58 +0800 , zen: Amazon AI 生成 +2023-09-07 12:16:18 +0800 , zen: 删除冗余部分 +2023-09-04 22:49:02 +0800 , zen: bilibili自带即为h265无需重编码 +2023-09-03 13:12:56 +0800 , zen: save +2023-09-02 16:06:22 +0800 , zen: 更改了video获取文件夹的方法 待测试 +2023-09-02 12:25:49 +0800 , zen: AVmerge长文件名会导致错误 +2023-08-25 17:13:07 +0800 , zen: 快捷命令 +2023-08-25 17:00:46 +0800 , zen: 程序升级1.21.0 每次运行后自动编译新版本二进制文件 +2023-08-22 15:33:38 +0800 , zen: 快速修改视频尺寸 不转码 +2023-08-22 01:28:40 +0800 , zen: 二进制可执行文件 +2023-08-20 17:35:41 +0800 , zen: init +2023-08-20 17:28:32 +0800 , root: Initial commit +2023-08-20 17:30:29 +0800 , zen: 同时修改声道 +2023-08-11 10:08:03 +0800 , zen: 数据库超时五秒 +2023-08-10 22:47:36 +0800 , zen: 数据库连接使用超时控制 +2023-08-10 21:44:34 +0800 , zen: 空结构体控制协程并发数量 +2023-08-10 21:43:43 +0800 , zen: 使用协程启动mysql,如果链接失败直接修改配置文件阻止写入动作 +2023-08-09 16:56:33 +0800 , zen: 硬件加速导致画质降低 体积增大 +2023-08-09 16:56:13 +0800 , zen: 简单粗暴合并bilibili视频 +2023-08-08 19:52:19 +0800 , zen: 使用合理的硬件加速 +2023-08-07 23:49:46 +0800 , zen: 批量修改文件名 小程序 +2023-08-07 16:22:17 +0800 , zen: save +2023-08-06 11:45:21 +0800 , zen: save +2023-08-06 11:20:58 +0800 , zen: bilibili on root +2023-08-04 17:42:56 +0800 , zen: 超时控制 +2023-08-04 17:35:07 +0800 , zen: 延时命令结束后记录最后的操作 +2023-08-03 11:37:00 +0800 , zen: save +2023-07-31 12:11:20 +0800 , zen: 音频文件转换为单声道 +2023-07-31 11:33:20 +0800 , zen: 新增q换行当前转码后直接退出功能 +2023-07-31 11:06:35 +0800 , zen: 添加统一mysql开关 +2023-07-30 14:39:41 +0800 , zen: save +2023-07-29 08:30:38 +0800 , zen: 转换同时使用单声道 +2023-07-28 18:15:35 +0800 , zen: 执行命令统一提示 +2023-07-28 18:15:07 +0800 , zen: 等待30秒后退出程序 +2023-07-28 18:14:33 +0800 , zen: 手工填写加速参数 防止多次加速 保留原始文件 +2023-07-27 20:28:37 +0800 , zen: fix +2023-07-27 17:39:16 +0800 , zen: 升级依赖要慎重 +2023-07-27 17:23:27 +0800 , zen: 会直接退出 +2023-07-27 07:18:48 +0800 , zen: save +2023-07-25 21:11:28 +0800 , zen: 加速音频全部子文件夹 +2023-07-22 17:09:36 +0800 , zen: 添加视频提取音频的功能 +2023-07-21 17:24:21 +0800 , zen: 扫描文件后批量插入数据库 +2023-07-20 22:56:00 +0800 , zen: save +2023-07-18 19:51:44 +0800 , zen: 使用正规int64转换string方法 +2023-07-18 19:42:44 +0800 , zen: 查漏补缺 +2023-07-18 18:49:16 +0800 , zen: 命令执行函数提取出来 作为单独的函数 +2023-07-18 16:56:04 +0800 , zen: fix +2023-07-15 20:24:24 +0800 , zen: 可以正常运行 +2023-07-13 20:28:34 +0800 , zen: save +2023-07-13 17:39:29 +0800 , zen: 如果数据库连接出错,后期不再尝试写入 +2023-07-13 17:31:02 +0800 , zen: 提取aac写入数据库判断是否有连接 +2023-07-13 17:28:56 +0800 , zen: telegram写入数据库判断是否有连接 +2023-07-13 16:44:27 +0800 , zen: update +2023-07-06 14:09:53 +0800 , zen: fork的代码库残留tag会导致go get -u 问题 +2023-07-06 12:25:05 +0800 , zen: 兼容两种格式 +2023-07-04 15:02:53 +0800 , zen: 正式添加哔哩哔哩提取音频 +2023-07-03 20:31:41 +0800 , zen: 简易爬虫 +2023-07-03 18:15:16 +0800 , zen: 测试通过 +2023-07-03 17:54:59 +0800 , zen: 任务完成发送总结邮件 +2023-07-03 17:39:21 +0800 , zen: speedUp不关注控制台 +2023-07-03 15:01:19 +0800 , zen: 准备添加程序结束后的总结 +2023-07-03 01:16:47 +0800 , zen: 实现批量下载 +2023-07-02 18:36:17 +0800 , zen: 创建单独文件夹存放图片 +2023-07-02 18:27:49 +0800 , zen: 添加数据库支持 +2023-07-02 17:22:42 +0800 , zen: 可以正确解析 +2023-07-02 16:02:24 +0800 , zen: 快速获取Telegraph图片 +2023-07-01 03:20:58 +0800 , zen: 跳过 +2023-06-25 16:22:57 +0800 , zen: 视频文件如果就在h265文件夹里 跳过 +2023-06-22 18:21:24 +0800 , zen: 创建文件夹不报错 +2023-06-22 13:55:59 +0800 , zen: save +2023-06-22 13:48:11 +0800 , zen: save +2023-06-21 21:30:44 +0800 , zen: save +2023-06-21 13:36:47 +0800 , zen: save +2023-06-21 10:28:46 +0800 , zen: 图片处理可以获得正确文件大小 +2023-06-20 20:55:39 +0800 , 张益铭: GetAll方法有问题待修改 +2023-06-20 20:43:36 +0800 , 张益铭: 全部改为引用传递 +2023-06-20 17:21:16 +0800 , 张益铭: 全部换成引用传递 +2023-06-20 17:18:08 +0800 , 张益铭: 写入数据库必须使用引用传递 +2023-06-20 14:00:31 +0800 , 张益铭: save +2023-06-20 14:00:13 +0800 , 张益铭: 文件大小使用原始字节数 +2023-06-20 13:59:42 +0800 , 张益铭: addTag记录到数据库 +2023-06-20 12:54:55 +0800 , 张益铭: 恢复创建输出文件夹,准备排查不能获取文件大小的问题 +2023-06-19 19:03:22 +0800 , zen: strings.Trim 会丢失字符 +2023-06-18 22:24:45 +0800 , zen: save +2023-06-17 21:47:10 +0800 , zen: 图片错误记录到数据库 +2023-06-16 17:19:50 +0800 , zen: 尝试不关心输出内容 +2023-06-16 12:36:39 +0800 , zen: 清空数据库,等待第一次测试 +2023-06-16 12:34:04 +0800 , zen: clean +2023-06-16 12:32:18 +0800 , zen: 等待测试 +2023-06-16 10:56:49 +0800 , zen: 分表 +2023-06-16 10:47:23 +0800 , zen: 视频修改比例任务添加说明 +2023-06-16 10:45:28 +0800 , zen: 视频旋转任务添加说明 +2023-06-16 10:41:04 +0800 , zen: fix +2023-06-16 10:40:11 +0800 , zen: resize任务添加说明 +2023-06-16 10:39:52 +0800 , zen: fix +2023-06-16 10:30:28 +0800 , zen: 视频转h265添加说明 +2023-06-16 10:22:36 +0800 , zen: 统一函数名称 +2023-06-16 10:15:14 +0800 , zen: 音频编码aac任务添加说明 +2023-06-16 10:13:01 +0800 , zen: 音频加速任务添加任务说明 +2023-06-16 10:12:39 +0800 , zen: 图片任务添加任务说明 +2023-06-16 09:54:16 +0800 , zen: 重命名 +2023-06-16 09:53:50 +0800 , zen: 统一函数名称 +2023-06-16 09:47:56 +0800 , zen: 重命名 +2023-06-16 09:47:17 +0800 , zen: 图片类任务添加任务种类 +2023-06-16 09:45:57 +0800 , zen: 数据表添加任务种类 +2023-06-16 00:28:09 +0800 , zen: 今日结束 +2023-06-15 22:47:48 +0800 , zen: 阶段保存 +2023-06-15 13:32:56 +0800 , zen: 文件记录到数据库基本实现 +2023-06-14 23:23:07 +0800 , zen: 数据库方面基本跑通 +2023-06-14 22:09:47 +0800 , zen: Initial commit +``` \ No newline at end of file diff --git a/alert/README.md b/alert/README.md new file mode 100644 index 0000000..0fb0ef1 --- /dev/null +++ b/alert/README.md @@ -0,0 +1,16 @@ +# TTS for Linux +```shell +sudo apt-get install libsndfile1-dev libpulse-dev libncurses5-dev libmp3lame-dev libespeak-dev + +wget https://github.com/hgneng/ekho/archive/refs/tags/v9.0.tar.gz +tar xJvf ekho-xxx.tar.xz + +// or +git clone https://github.com/hgneng/ekho.git + +cd ekho-xxx +./configure --prefix=/usr/local +make +sudo make install +ekho "hello 123" +``` diff --git a/alert/constant.go b/alert/constant.go new file mode 100644 index 0000000..0fba3f0 --- /dev/null +++ b/alert/constant.go @@ -0,0 +1,55 @@ +package alert + +type Server struct { + POP3 string + POP3Port int + SMTP string + SMTPProt int +} + +var QQ = &Server{ + POP3: "pop.qq.com", + POP3Port: 995, + SMTP: "smtp.qq.cocm", + SMTPProt: 465, //587 +} + +var NetEase = &Server{ + POP3: "pop.163.com", + POP3Port: 110, + SMTP: "smtp.163.com", + SMTPProt: 25, +} + +var Gmail = &Server{ + POP3: "pop.gmail.com", + POP3Port: 995, + SMTP: "smtp.gmail.com", + SMTPProt: 587, +} + +const ( + LF = "\u000A" // \n + CR = "\u000D" // \r + CRLf = "\U000D000A" // \r\n + DOUBLECRLF = "\U000D000A\U000D000A" // \r\n\r\n + NULL = "\u0000" // null + NBSP = "\u00A0" // No-Break Space + BR = "
" +) +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 + + + + + + + + +
+ + +

Sample "Hello, World" Application

+
+
+
Just two divs peacing out
+
+ check +
One more
+

This is the home page for the HelloWorld Web application.

+

To prove that they work, you can execute either of the following links: +

+

+
+
Last one
+
+
+

+
+ + +` + +const multipleClassesHTML = ` + + + Sample Application + + +
Multiple classes
+
Single class
+
Multiple classes inorder
+
+
Inner single class
+
Inner multiple classes
+
Inner multiple classes inorder
+
+ + +` + +var doc = HTMLParse(testHTML) +var multipleClasses = HTMLParse(multipleClassesHTML) + +func TestFind(t *testing.T) { + // Find() and Attrs() + actual := doc.Find("img").Attrs()["src"] + assert.Equal(t, "images/springsource.png", actual) + // Find(...) and Text() + actual = doc.Find("a", "href", "hello").Text() + assert.Equal(t, "servlet", actual) + // Nested Find() + actual = doc.Find("div").Find("div").Text() + assert.Equal(t, "Just two divs peacing out", actual) + // Find("") for any + actual = multipleClasses.Find("body").Find("").Text() + assert.Equal(t, "Multiple classes", actual) + // Find("") with attributes + actual = doc.Find("", "id", "4").Text() + assert.Equal(t, "Last one", actual) +} + +func TestFindNextPrevElement(t *testing.T) { + // FindNextSibling() and NodeValue field + actual := doc.Find("div", "id", "0").FindNextSibling().NodeValue + assert.Equal(t, "check", strings.TrimSpace(actual)) + // FindPrevSibling() and NodeValue field + actual = doc.Find("div", "id", "2").FindPrevSibling().NodeValue + assert.Equal(t, "check", strings.TrimSpace(actual)) + // FindNextElementSibling() and NodeValue field + actual = doc.Find("table").FindNextElementSibling().NodeValue + assert.Equal(t, "div", strings.TrimSpace(actual)) + // FindPrevElementSibling() and NodeValue field + actual = doc.Find("p").FindPrevElementSibling().NodeValue + assert.Equal(t, "div", strings.TrimSpace(actual)) +} + +func TestFindAll(t *testing.T) { + // FindAll() and Attrs() + allDivs := doc.FindAll("div") + for i := 0; i < len(allDivs); i++ { + id := allDivs[i].Attrs()["id"] + actual, _ := strconv.Atoi(id) + assert.Equal(t, i, actual) + } +} + +func TestFindAllBySingleClass(t *testing.T) { + actual := multipleClasses.FindAll("div", "class", "first") + assert.Equal(t, 6, len(actual)) + actual = multipleClasses.FindAll("div", "class", "third") + assert.Equal(t, 1, len(actual)) +} + +func TestFindAllByAttribute(t *testing.T) { + actual := doc.FindAll("", "id", "2") + assert.Equal(t, 1, len(actual)) +} + +func TestFindBySingleClass(t *testing.T) { + actual := multipleClasses.Find("div", "class", "first") + assert.Equal(t, "Multiple classes", actual.Text()) + actual = multipleClasses.Find("div", "class", "third") + assert.Equal(t, "Multiple classes inorder", actual.Text()) +} + +func TestFindAllStrict(t *testing.T) { + actual := multipleClasses.FindAllStrict("div", "class", "first second") + assert.Equal(t, 2, len(actual)) + actual = multipleClasses.FindAllStrict("div", "class", "first third second") + assert.Equal(t, 0, len(actual)) + actual = multipleClasses.FindAllStrict("div", "class", "second first third") + assert.Equal(t, 1, len(actual)) +} + +func TestFindStrict(t *testing.T) { + actual := multipleClasses.FindStrict("div", "class", "first") + assert.Equal(t, "Single class", actual.Text()) + actual = multipleClasses.FindStrict("div", "class", "third") + assert.NotNil(t, actual.Error) +} + +func TestText(t *testing.T) { + //
  • To a JSP page right?
  • + li := doc.Find("ul").Find("li") + assert.Equal(t, "To a ", li.Text()) +} +func TestFullText(t *testing.T) { + //
  • To a JSP page right?
  • + li := doc.Find("ul").Find("li") + assert.Equal(t, "To a JSP page right?", li.FullText()) +} + +func TestFullTextEmpty(t *testing.T) { + //

    + h1 := doc.Find("div", "id", "5").Find("h1") + assert.Empty(t, h1.FullText()) +} + +func TestNewErrorReturnsInspectableError(t *testing.T) { + err := newError(ErrElementNotFound, "element not found") + assert.NotNil(t, err) + assert.Equal(t, ErrElementNotFound, err.Type) + assert.Equal(t, "element not found", err.Error()) +} + +func TestFindReturnsInspectableError(t *testing.T) { + r := doc.Find("bogus", "thing") + assert.IsType(t, Error{}, r.Error) + assert.Equal(t, "element `bogus` with attributes `thing` not found", r.Error.Error()) + assert.Equal(t, ErrElementNotFound, r.Error.(Error).Type) +} + +// Similar test: https://github.com/hashicorp/go-retryablehttp/blob/master/client_test.go#L616 +func TestClient_Post(t *testing.T) { + // Mock server which always responds 200. + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + t.Fatalf("bad method: %s", r.Method) + } + if r.RequestURI != "/foo/bar" { + t.Fatalf("bad uri: %s", r.RequestURI) + } + if ct := r.Header.Get("Content-Type"); ct != "application/json" { + t.Fatalf("bad content-type: %s", ct) + } + + // Check the payload + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatalf("err: %s", err) + } + expected := []byte(`{"hello":"world"}`) + if !bytes.Equal(body, expected) { + t.Fatalf("bad: %v", string(body)) + } + + w.WriteHeader(200) + })) + defer ts.Close() + + // Make the request with JSON payload + _, err := Post( + ts.URL+"/foo/bar", "application/json", `{"hello":"world"}`) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Make the request with byte payload + _, err = Post( + ts.URL+"/foo/bar", "application/json", []byte(`{"hello":"world"}`)) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Make the request with string map payload + _, err = Post( + ts.URL+"/foo/bar", + "application/json", + map[string]string{ + "hello": "world", + }) + if err != nil { + t.Fatalf("err: %v", err) + } +} + +// Similar test: https://github.com/hashicorp/go-retryablehttp/blob/add-circleci/client_test.go#L631 +func TestClient_PostForm(t *testing.T) { + // Mock server which always responds 200. + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + t.Fatalf("bad method: %s", r.Method) + } + if r.RequestURI != "/foo/bar" { + t.Fatalf("bad uri: %s", r.RequestURI) + } + if ct := r.Header.Get("Content-Type"); ct != "application/x-www-form-urlencoded" { + t.Fatalf("bad content-type: %s", ct) + } + + // Check the payload + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatalf("err: %s", err) + } + expected := []byte(`hello=world`) + if !bytes.Equal(body, expected) { + t.Fatalf("bad: %v", string(body)) + } + + w.WriteHeader(200) + })) + defer ts.Close() + + // Create the form data. + form1, err := url.ParseQuery("hello=world") + if err != nil { + t.Fatalf("err: %v", err) + } + + form2 := url.Values{ + "hello": []string{"world"}, + } + + // Make the request. + _, err = PostForm(ts.URL+"/foo/bar", form1) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Make the request. + _, err = PostForm(ts.URL+"/foo/bar", form2) + if err != nil { + t.Fatalf("err: %v", err) + } +} + +func TestHTML(t *testing.T) { + li := doc.Find("ul").Find("li") + assert.Equal(t, "
  • To a JSP page right?
  • ", li.HTML()) + +} +func TestUnit(t *testing.T) { + resp, err := Get("https://xkcd.com") + if err != nil { + os.Exit(1) + } + doc := HTMLParse(resp) + links := doc.Find("div", "id", "comicLinks").FindAll("a") + for _, link := range links { + fmt.Println(link.Text(), "| Link :", link.Attrs()["href"]) + } +} +func TestProxy(t *testing.T) { + file, err := os.OpenFile("exam.txt", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0777) + if err != nil { + return + } + uri := "https://www.pornhub.com/model/cherryl-colle/videos?page=4" + proxy := "http://192.168.1.20:8889" + withProxy, err := GetWithProxy(uri, proxy) + if err != nil { + return + } + t.Log(withProxy) + root := HTMLParse(withProxy) + as := root.FindAll("a") + for _, a := range as { + href := a.Attrs()["href"] + if strings.Contains(href, "/view_video.php?viewkey") { + fmt.Println("find ! ", href) + file.WriteString(fmt.Sprintf("%s\n", href)) + } + file.Sync() + } +} diff --git a/splicing/aac.go b/splicing/aac.go new file mode 100644 index 0000000..0824424 --- /dev/null +++ b/splicing/aac.go @@ -0,0 +1,18 @@ +package splicing + +import ( + "os/exec" + "processAll/util" +) + +/* +ffmpeg -f concat -safe 0 -i work.txt -c copy x.aac +仅处理当前文件夹下的aac文件 +*/ +func SpicingAAC(out string) { + cmd := exec.Command("ffmpeg", "-f", "concat", "-safe", "0", "-i", "work.txt", "-c", "copy", out) + err := util.ExecCommand(cmd) + if err != nil { + panic(err) + } +} diff --git a/splicing/unit_test.go b/splicing/unit_test.go new file mode 100644 index 0000000..5d9a29c --- /dev/null +++ b/splicing/unit_test.go @@ -0,0 +1,31 @@ +package splicing + +import ( + "io" + "log/slog" + "os" + "testing" +) + +func init() { + + var opt slog.HandlerOptions + + 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 TestSpicingAAC(t *testing.T) { + out := "扶她少女自慰日记9.aac" + SpicingAAC(out) +} diff --git a/splicing/work.txt b/splicing/work.txt new file mode 100644 index 0000000..df1689b --- /dev/null +++ b/splicing/work.txt @@ -0,0 +1,2 @@ +file 1.aac +file 2.aac \ No newline at end of file diff --git a/sql/lite.go b/sql/lite.go new file mode 100644 index 0000000..7fe40a2 --- /dev/null +++ b/sql/lite.go @@ -0,0 +1,38 @@ +package sql + +import ( + "fmt" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var db *gorm.DB + +func SetEngine() { + db, _ = gorm.Open(sqlite.Open("all.db"), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Warn), + }) + // 迁移 schema + err := db.AutoMigrate(Ytdlp{}) + if err != nil { + return + } + // Create + //db.Create(&Product{Code: "D42", Price: 100}) + // Read + //var product Product + //db.First(&product, 1) // 根据整型主键查找 + //db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录 + // Update - 将 product 的 price 更新为 200 + //db.Model(&product).Update("Price", 200) + // Update - 更新多个字段 + //db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段 + //db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"}) + // Delete - 删除 product + //db.Delete(&product, 1) + fmt.Println(db) +} +func GetEngine() *gorm.DB { + return db +} diff --git a/sql/unit_test.go b/sql/unit_test.go new file mode 100644 index 0000000..730f48b --- /dev/null +++ b/sql/unit_test.go @@ -0,0 +1,8 @@ +package sql + +import "testing" + +func TestInit(t *testing.T) { + + SetEngine() +} diff --git a/sql/ytdlp.go b/sql/ytdlp.go new file mode 100644 index 0000000..d2d8490 --- /dev/null +++ b/sql/ytdlp.go @@ -0,0 +1,27 @@ +package sql + +import ( + "gorm.io/gorm" + "time" +) + +type Ytdlp struct { + gorm.Model + ID uint `gorm:"primaryKey"` + URL string `gorm:"url,type=string"` + Status string `gorm:"status"` + ErrorMsg string `gorm:"error_msg"` + ErrorCode string `gorm:"error_code"` + Request string `gorm:"request"` + CreatedAt time.Time +} + +func (y *Ytdlp) FindOneByURL() *gorm.DB { + return GetEngine().First(&y, "url = ?", y.URL) +} +func (y *Ytdlp) SetOne() *gorm.DB { + return GetEngine().Create(&y) +} +func (y *Ytdlp) UpdateStatusByURL() *gorm.DB { + return GetEngine().Model(&Ytdlp{}).Where("url = ?", y.URL).Update("status = ?", y.Status) +} diff --git a/storage/mysql/mysql.go b/storage/mysql/mysql.go new file mode 100644 index 0000000..6cce3c9 --- /dev/null +++ b/storage/mysql/mysql.go @@ -0,0 +1,58 @@ +package mysql + +import ( + _ "github.com/go-sql-driver/mysql" + "log/slog" + "os" + "processAll/util" + "strings" + "time" + "xorm.io/xorm" + "xorm.io/xorm/log" + "xorm.io/xorm/names" +) + +var MyEngine *xorm.Engine + +func SetEngine() { + user := util.GetVal("mysql", "user") + ip := util.GetVal("mysql", "ip") + port := util.GetVal("mysql", "port") + passwd := util.GetVal("mysql", "passwd") + database := util.GetVal("mysql", "database") + + uri := strings.Join([]string{ip, port}, ":") + src := strings.Join([]string{user, ":", passwd, "@tcp(", uri, ")/", database, "?charset=utf8mb4"}, "") + slog.Debug("数据库链接", slog.String("参数", src)) + + MyEngine, _ = xorm.NewEngine("mysql", src) + ch := make(chan struct{}, 1) + go success(ch) + select { + case <-ch: + MyEngine.SetMapper(names.GonicMapper{}) + MyEngine.SetTZDatabase(time.Local) + if util.GetVal("log", "level") == "Debug" { + MyEngine.SetLogLevel(log.LOG_DEBUG) + //MyEngine.ShowSQL(true) + } + case <-time.After(5 * time.Second): + slog.Warn("数据库连接超时") + if err := util.SetVal("mysql", "switch", "off"); err != nil { + slog.Warn("数据库连接失败后关闭失败,退出程序") + os.Exit(-1) + } + } +} + +func GetSession() *xorm.Session { + return MyEngine.NewSession() +} +func success(ch chan struct{}) { + if err := MyEngine.Ping(); err != nil { + slog.Error("创建数据库引擎失败", slog.Any("错误信息", err)) + } else { + slog.Debug("创建数据库引擎成功", slog.Any("MyEngine", MyEngine)) + ch <- struct{}{} + } +} diff --git a/telegraph/README.md b/telegraph/README.md new file mode 100644 index 0000000..b4e8f58 --- /dev/null +++ b/telegraph/README.md @@ -0,0 +1,6 @@ +复制div结构 + +```html +
    +
    +``` \ No newline at end of file diff --git a/telegraph/exam.html b/telegraph/exam.html new file mode 100644 index 0000000..c17eee8 --- /dev/null +++ b/telegraph/exam.html @@ -0,0 +1,2 @@ +
    +
    \ No newline at end of file diff --git a/telegraph/fromFile.go b/telegraph/fromFile.go new file mode 100644 index 0000000..fa0d997 --- /dev/null +++ b/telegraph/fromFile.go @@ -0,0 +1,132 @@ +package telegraph + +import ( + "fmt" + "log/slog" + "os" + "os/exec" + "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, []string) { + var srcs []string + doc := soup.HTMLParse(html) + title := doc.Find("h1").Text() + title = replace(title) + 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 +} + +/* +解析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/telegraph/fromHtml.go b/telegraph/fromHtml.go new file mode 100644 index 0000000..66235cb --- /dev/null +++ b/telegraph/fromHtml.go @@ -0,0 +1,23 @@ +package telegraph + +import ( + "log/slog" + "processAll/soup" + "processAll/util" +) + +func GetWeb(html string) (string, error) { + // html := util.GetVal("Telegraph", "url") + proxy := util.GetVal("Telegraph", "proxy") + if proxy == "" { + proxy = "http://127.0.0.1:8889" + } + withProxy, err := soup.GetWithProxy(html, proxy) + if err != nil { + slog.Warn("Get 失败", slog.Any("错误内容", err)) + return "", err + } else { + slog.Debug("Get 成功", slog.String("网页内容", withProxy)) + return withProxy, nil + } +} diff --git a/telegraph/fromWeb.go b/telegraph/fromWeb.go new file mode 100644 index 0000000..2229563 --- /dev/null +++ b/telegraph/fromWeb.go @@ -0,0 +1,63 @@ +package telegraph + +import ( + "fmt" + "log/slog" + "os" + "os/exec" + "processAll/soup" + "processAll/util" + "strconv" + "strings" + "time" +) + +func GetAndDownload(website string) { + withProxy, err := soup.GetWithProxy(website, "http://127.0.0.1:8889") + if err != nil { + slog.Warn("Get 失败", slog.Any("错误内容", err)) + return + } + defer func() { + if err := recover(); err != nil { + slog.Warn("出问题的网页", slog.String("网页原文", withProxy)) + } + }() + slog.Debug("Get 成功", slog.String("网页内容", withProxy)) + doc := soup.HTMLParse(withProxy) + div := doc.Find("article") + h1 := div.Find("h1") + title := replace(h1.Text()) + slog.Debug("ql-editor节点", slog.Any("div", div)) + imgs := div.FindAll("img") + for i, img := range imgs { + src := img.Attrs()["src"] + if strings.Contains(src, "telegra.ph") { + src = strings.Replace(src, "https://", "http://", -1) + } else if strings.Contains(src, "23img.com") { + src = strings.Replace(src, "https://wsrv.nl/?url=", "", -1) + src = strings.Replace(src, "https://", "http://", -1) + } else { + src = strings.Join([]string{"http://telegra.ph", src}, "") + } + fmt.Println(i + 1) + slog.Debug("获取图片地址", slog.String("src", src), slog.String("所属标题", title)) + os.Mkdir(title, 0777) + fname := strings.Join([]string{strconv.Itoa(i + 1), "jpg"}, ".") + dir := strings.Join([]string{title, fname}, string(os.PathSeparator)) + 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, src) + err = util.ExecCommand(cmd) + if err != nil { + time.Sleep(time.Second) + err = util.ExecCommand(cmd) + if err != nil { + time.Sleep(time.Second) + err = util.ExecCommand(cmd) + if err != nil { + time.Sleep(time.Second) + slog.Warn("下载失败", slog.String("文件地址", src), slog.String("下载命令", fmt.Sprint(cmd))) + } + } + } + } +} diff --git a/telegraph/replace.go b/telegraph/replace.go new file mode 100644 index 0000000..0c30b26 --- /dev/null +++ b/telegraph/replace.go @@ -0,0 +1,25 @@ +package telegraph + +import "strings" + +func replace(title string) string { + 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) + //? + title = strings.Replace(title, "?", "", -1) + + for strings.Contains(title, " ") { + title = strings.Replace(title, " ", "", -1) + } + return title +} diff --git a/telegraph/unit_test.go b/telegraph/unit_test.go new file mode 100644 index 0000000..13333ff --- /dev/null +++ b/telegraph/unit_test.go @@ -0,0 +1,47 @@ +package telegraph + +import ( + "processAll/util" + "testing" +) + +func TestUnit(t *testing.T) { + f := "exam.html" + title, srcs := Parse(ReadHtml(f)) + DownloadSrc(title, srcs) + for _, v := range srcs { + t.Log(v) + } +} +func TestParse(t *testing.T) { + f := "exam.html" + title, srcs := Parse(ReadHtml(f)) + t.Log(title, srcs) +} + +func TestFromWeb(t *testing.T) { + uri := "https://telegra.ph/%E7" + web, err := GetWeb(uri) + if err != nil { + return + } + title, srcs := ParseWeb(web) + DownloadSrc(title, srcs) +} +func TestFromFile(t *testing.T) { + fname := "/Users/zen/github/processAVIWithXorm/telegraph/exam.html" + title, srcs := Parse(ReadHtml(fname)) + DownloadSrc(title, srcs) +} +func TestFromWebs(t *testing.T) { + urls := util.ReadByLine("/Users/zen/github/processAVIWithXorm/telegraph/list.txt") + for _, uri := range urls { + web, err := GetWeb(uri) + if err != nil { + return + } + title, srcs := Parse(ReadHtml(web)) + DownloadSrc(title, srcs) + } + +} diff --git a/unit_test.go b/unit_test.go new file mode 100644 index 0000000..d0a5965 --- /dev/null +++ b/unit_test.go @@ -0,0 +1,18 @@ +package main + +import ( + "log/slog" + "processAll/model" + "processAll/storage/mysql" + "testing" +) + +func TestUnit(t *testing.T) { + mysql.SetEngine() + i := new(model.Image) + all, err := i.Sum() + if err != nil { + return + } + slog.Debug("all image", slog.Int64("共处理的图片数", all)) +} diff --git a/util/Gracefully.go b/util/Gracefully.go new file mode 100644 index 0000000..ce9ae8e --- /dev/null +++ b/util/Gracefully.go @@ -0,0 +1,41 @@ +package util + +import ( + "bufio" + "fmt" + "log/slog" + "os" +) + +var ExitAfterDone = false + +func GetExitStatus() bool { + return ExitAfterDone +} + +func SetExitStatus(b bool) { + slog.Debug("改变退出状态") + ExitAfterDone = b +} +func ExitAfterRun() { + reader := bufio.NewReader(os.Stdin) + go func() { + for { + input, _ := reader.ReadString('\n') + fmt.Printf("You entered is %T\t%v", input, input) + if input == "q\n" { + slog.Debug("接收到q") + //ExitAfterDone = true + SetExitStatus(true) + slog.Info("退出状态改变", slog.Bool("新值", ExitAfterDone)) + } + if input == "q\r\n" { + slog.Debug("接收到q") + //ExitAfterDone = true + SetExitStatus(true) + slog.Info("windows 退出状态改变", slog.Bool("新值", ExitAfterDone)) + } + + } + }() +} diff --git a/util/cmd.go b/util/cmd.go new file mode 100644 index 0000000..afef368 --- /dev/null +++ b/util/cmd.go @@ -0,0 +1,58 @@ +package util + +import ( + "fmt" + "log/slog" + "os" + "os/exec" + "strings" +) + +func ExecCommand(c *exec.Cmd) (e error) { + defer func() { + if err := recover(); err != nil { + slog.Warn("命令运行出现错误", slog.String("命令原文", fmt.Sprint(c)), slog.Any("错误原文", err)) + os.Exit(-1) + } + }() + slog.Info("开始执行命令", slog.String("命令原文", fmt.Sprint(c))) + if level := GetVal("log", "level"); level == "Debug" { + stdout, err := c.StdoutPipe() + c.Stderr = c.Stdout + if err != nil { + slog.Warn("连接Stdout产生错误", slog.String("命令原文", fmt.Sprint(c)), slog.String("错误原文", fmt.Sprint(err))) + return err + } + if err = c.Start(); err != nil { + slog.Warn("启动cmd命令产生错误", slog.String("命令原文", fmt.Sprint(c)), slog.String("错误原文", fmt.Sprint(err))) + return err + } + for { + tmp := make([]byte, 1024) + _, err := stdout.Read(tmp) + t := string(tmp) + t = strings.Replace(t, "\u0000", "", -1) + fmt.Println(t) + if err != nil { + break + } + } + if err = c.Wait(); err != nil { + slog.Warn("命令执行中产生错误", slog.String("命令原文", fmt.Sprint(c)), slog.String("错误原文", fmt.Sprint(err))) + return err + } + } else { + if output, err := c.CombinedOutput(); err != nil { + slog.Warn("命令执行中产生错误", slog.String("命令原文", fmt.Sprint(c)), slog.String("错误原文", fmt.Sprint(err))) + return err + } else { + // 这是一段永远不可能被运行的代码 + slog.Debug("命令执行完毕", slog.String("输出", string(output))) + } + } + if exit := GetExitStatus(); exit { + slog.Debug("命令端获取到退出状态,命令结束后退出", slog.Bool("信号值", exit), slog.String("最后一条命令", fmt.Sprint(c))) + os.Exit(0) + } + return nil +} diff --git a/util/conf.go b/util/conf.go new file mode 100644 index 0000000..00db034 --- /dev/null +++ b/util/conf.go @@ -0,0 +1,50 @@ +package util + +import ( + "errors" + "fmt" + "log/slog" + "processAll/util/goini" +) + +const confPath = "./conf.ini" + +var ( + conf *goini.Config +) + +/* +* + - 初始化 + init函数的主要作用: + 初始化不能采用初始化表达式初始化的变量。 + 程序运行前的注册。 + 实现sync.Once功能。 + 其他 +*/ +func init() { + initConfig() +} + +func initConfig() { + conf = goini.SetConfig(confPath) + slog.Debug("读取配置文件", slog.String("文件名", confPath)) +} + +/** + * 根据键获取值 + */ +func GetVal(section, name string) string { + val, _ := conf.GetValue(section, name) + return val +} + +func SetVal(section, key, value string) error { + if err := conf.SetValue(section, key, value); err { + slog.Debug("修改配置文件成功") + return nil + } else { + slog.Warn("修改配置文件失败") + return errors.New(fmt.Sprintf("修改配置文件失败\tsextion:%s\tkey:%s\tvalue:%s\n", section, key, value)) + } +} diff --git a/util/curl.go b/util/curl.go new file mode 100644 index 0000000..57e31c8 --- /dev/null +++ b/util/curl.go @@ -0,0 +1,243 @@ +package util + +import ( + "bytes" + "encoding/json" + "io" + "log/slog" + "mime/multipart" + "net/http" + "net/url" + "os" +) + +// 文件上传 +func HttpProxyFileUpload(file *multipart.FileHeader, fileKey string, addFields map[string]string, + addHeaders map[string]string, urlPath string) (body []byte, err error) { + buf := new(bytes.Buffer) + writer := multipart.NewWriter(buf) + formFile, err := writer.CreateFormFile(fileKey, file.Filename) + if err != nil { + slog.Error("Upload Create form file failed", slog.Any("错误原文", err)) + return + } + + // 从文件读取数据,写入表单 + srcFile, err := file.Open() + if err != nil { + slog.Error("Upload Create form file failed", slog.Any("错误原文", err)) + return + } + defer srcFile.Close() + _, err = io.Copy(formFile, srcFile) + if err != nil { + slog.Error("Write to form file failed", slog.Any("错误原文", err)) + return + } + for fieldKey, fieldVal := range addFields { + if err = writer.WriteField(fieldKey, fieldVal); err != nil { + slog.Error("WriteField failed", slog.Any("错误原文", err)) + return + } + } + // 发送表单 + contentType := writer.FormDataContentType() + writer.Close() // 发送之前必须调用Close()以写入结尾行 + req, err := http.NewRequest("POST", urlPath, buf) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + for headerKey, headerVal := range addHeaders { + req.Header.Set(headerKey, headerVal) + } + + resp, err := (&http.Client{}).Do(req) + if err != nil { + slog.Error("Post failed", slog.Any("错误原文", err)) + + } + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + body, _ = io.ReadAll(resp.Body) + return +} + +func HttpPostJson(addHeaders map[string]string, data interface{}, urlPath string) (body []byte, err error) { + bytesData, err := json.Marshal(data) + if err != nil { + return + } + reader := bytes.NewReader(bytesData) + req, err := http.NewRequest("POST", urlPath, reader) + if err != nil { + return + } + req.Header.Set("Content-Type", "application/json;charset=UTF-8") + for headerKey, headerVal := range addHeaders { + req.Header.Set(headerKey, headerVal) + } + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return + } + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + body, err = io.ReadAll(resp.Body) + return +} +func HttpPostJsoDownload(addHeaders map[string]string, data interface{}, urlPath string, filePathName string) error { + bytesData, err := json.Marshal(data) + if err != nil { + return err + } + reader := bytes.NewReader(bytesData) + req, err := http.NewRequest("POST", urlPath, reader) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json;charset=UTF-8") + for headerKey, headerVal := range addHeaders { + req.Header.Set(headerKey, headerVal) + } + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + + // Create output file + out, err := os.Create(filePathName) + if err != nil { + panic(err) + } + defer out.Close() + _, err = io.Copy(out, resp.Body) + return err +} + +func HttpPostJsonPut(addHeaders map[string]string, data interface{}, urlPath string) (body []byte, err error) { + bytesData, err := json.Marshal(data) + if err != nil { + return + } + reader := bytes.NewReader(bytesData) + req, err := http.NewRequest("PUT", urlPath, reader) + if err != nil { + return + } + req.Header.Set("Content-Type", "application/json;charset=UTF-8") + for headerKey, headerVal := range addHeaders { + req.Header.Set(headerKey, headerVal) + } + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return + } + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + body, err = io.ReadAll(resp.Body) + return +} + +func HttpGet(addHeaders map[string]string, data map[string]string, urlPath string) (body []byte, err error) { + params := url.Values{} + urlInfo, err := url.Parse(urlPath) + if err != nil { + panic(err.Error()) + + } + for dataKey, dataVal := range data { + params.Set(dataKey, dataVal) + } + urlInfo.RawQuery = params.Encode() + fullUrl := urlInfo.String() + req, err := http.NewRequest("GET", fullUrl, nil) + if err != nil { + return + } + for headerKey, headerVal := range addHeaders { + req.Header.Set(headerKey, headerVal) + } + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return + } + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + body, err = io.ReadAll(resp.Body) + return +} +func HttpProxyFileUploadCustom(file *multipart.FileHeader, fileKey, filename string, addFields map[string]string, + addHeaders map[string]string, urlPath string) (body []byte, err error) { + buf := new(bytes.Buffer) + writer := multipart.NewWriter(buf) + formFile, err := writer.CreateFormFile(fileKey, filename) + if err != nil { + slog.Error("Upload Create form file failed", slog.Any("错误原文", err)) + return + } + + // 从文件读取数据,写入表单 + srcFile, err := file.Open() + if err != nil { + slog.Error("Upload Create form file failed", slog.Any("错误原文", err)) + return + } + defer srcFile.Close() + _, err = io.Copy(formFile, srcFile) + if err != nil { + slog.Error("Write to form file failed", slog.Any("错误原文", err)) + return + } + for fieldKey, fieldVal := range addFields { + if err = writer.WriteField(fieldKey, fieldVal); err != nil { + slog.Error("WriteField failed", slog.Any("错误原文", err)) + return + } + } + // 发送表单 + contentType := writer.FormDataContentType() + writer.Close() // 发送之前必须调用Close()以写入结尾行 + req, err := http.NewRequest("POST", urlPath, buf) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + for headerKey, headerVal := range addHeaders { + req.Header.Set(headerKey, headerVal) + } + + resp, err := (&http.Client{}).Do(req) + if err != nil { + slog.Error("Post failed", slog.Any("错误原文", err)) + + } + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + body, _ = io.ReadAll(resp.Body) + return +} diff --git a/util/duplicate.go b/util/duplicate.go new file mode 100644 index 0000000..38fd550 --- /dev/null +++ b/util/duplicate.go @@ -0,0 +1,16 @@ +package util + +/* +根据输入的字符串切片返回去重的字符串切片 +*/ +func DuplicateBySlice(elements []string) (dup []string) { + m := make(map[string]bool) + for _, element := range elements { + if _, ok := m[element]; !ok { // 如果元素不在map中,则添加到result和map中 + dup = append(dup, element) + m[element] = true + } + } + + return dup +} diff --git a/util/goini/conf.go b/util/goini/conf.go new file mode 100644 index 0000000..c7b803f --- /dev/null +++ b/util/goini/conf.go @@ -0,0 +1,154 @@ +package goini + +import ( + "bufio" + "errors" + "fmt" + "io" + "log/slog" + "os" + "strings" +) + +type Config struct { + filepath string //your ini file path directory+file + conflist []map[string]map[string]string //configuration information slice +} + +// Create an empty configuration file +func SetConfig(filepath string) *Config { + slog.Debug("读取配置文件", slog.String("文件名", filepath)) + c := new(Config) + c.filepath = filepath + + return c +} + +// To obtain corresponding value of the key values +func (c *Config) GetValue(section, name string) (string, error) { + c.ReadList() + conf := c.ReadList() + for _, v := range conf { + for key, value := range v { + if key == section { + return value[name], nil + } + } + } + notFound := errors.New("no value") + return "", notFound +} + +// Set the corresponding value of the key value, if not add, if there is a key change +func (c *Config) SetValue(section, key, value string) bool { + c.ReadList() + data := c.conflist + var ok bool + var index = make(map[int]bool) + var conf = make(map[string]map[string]string) + for i, v := range data { + _, ok = v[section] + index[i] = ok + } + + i, ok := func(m map[int]bool) (i int, v bool) { + for i, v := range m { + if v == true { + return i, true + } + } + return 0, false + }(index) + + if ok { + c.conflist[i][section][key] = value + return true + } else { + conf[section] = make(map[string]string) + conf[section][key] = value + c.conflist = append(c.conflist, conf) + return true + } + + return false +} + +// Delete the corresponding key values +func (c *Config) DeleteValue(section, name string) bool { + c.ReadList() + data := c.conflist + for i, v := range data { + for key, _ := range v { + if key == section { + delete(c.conflist[i][key], name) + return true + } + } + } + return false +} + +// List all the configuration file +func (c *Config) ReadList() []map[string]map[string]string { + + file, err := os.Open(c.filepath) + if err != nil { + CheckErr(err) + } + defer file.Close() + var data map[string]map[string]string + var section string + buf := bufio.NewReader(file) + for { + l, err := buf.ReadString('\n') + line := strings.TrimSpace(l) + if err != nil { + if err != io.EOF { + CheckErr(err) + } + if len(line) == 0 { + break + } + } + switch { + case len(line) == 0: + case string(line[0]) == "#": //增加配置文件备注 + case line[0] == '[' && line[len(line)-1] == ']': + section = strings.TrimSpace(line[1 : len(line)-1]) + data = make(map[string]map[string]string) + data[section] = make(map[string]string) + default: + i := strings.IndexAny(line, "=") + if i == -1 { + continue + } + value := strings.TrimSpace(line[i+1 : len(line)]) + data[section][strings.TrimSpace(line[0:i])] = value + if c.uniquappend(section) == true { + c.conflist = append(c.conflist, data) + } + } + + } + + return c.conflist +} + +func CheckErr(err error) string { + if err != nil { + return fmt.Sprintf("Error is :'%s'", err.Error()) + } + return "Notfound this error" +} + +// Ban repeated appended to the slice method +func (c *Config) uniquappend(conf string) bool { + for _, v := range c.conflist { + for k, _ := range v { + if k == conf { + return false + } + } + } + return true +} diff --git a/util/io.go b/util/io.go new file mode 100644 index 0000000..7da072f --- /dev/null +++ b/util/io.go @@ -0,0 +1,47 @@ +package util + +import ( + "bufio" + "fmt" + "io" + "log/slog" + "os" +) + +func ReadByLine(fp string) []string { + lines := []string{} + fi, err := os.Open(fp) + if err != nil { + fmt.Printf("Error: %s\n", err) + slog.Warn("按行读文件出错") + return []string{} + } + defer fi.Close() + + br := bufio.NewReader(fi) + for { + a, _, c := br.ReadLine() + if c == io.EOF { + break + } + lines = append(lines, string(a)) + } + return lines +} + +// 按行写文件 +func WriteByLine(fp string, s []string) { + file, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777) + if err != nil { + return + } + defer file.Close() + writer := bufio.NewWriter(file) + for _, v := range s { + writer.WriteString(v) + writer.WriteString("\n") + } + writer.Flush() + return + +} diff --git a/util/randon.go b/util/randon.go new file mode 100644 index 0000000..b61b4ca --- /dev/null +++ b/util/randon.go @@ -0,0 +1,19 @@ +package util + +import ( + "log/slog" + "math/rand" + "time" +) + +func RandomWithSeed() { + rand.Seed(time.Now().Unix()) + a := rand.Intn(2000) + seed := rand.New(rand.NewSource(time.Now().Unix())) + b := seed.Intn(2000) + if a == b { + slog.Info("生成的随机数", slog.Int("a", a), slog.Int("b", b)) + } else { + slog.Info("不相等") + } +} diff --git a/util/root.go b/util/root.go new file mode 100644 index 0000000..51472a0 --- /dev/null +++ b/util/root.go @@ -0,0 +1,17 @@ +package util + +import ( + "path" + "runtime" +) + +var Root string + +func SetRoot() { + _, filename, _, _ := runtime.Caller(0) + Root = path.Dir(filename) +} + +func GetRoot() string { + return Root +} diff --git a/util/rwLock.go b/util/rwLock.go new file mode 100644 index 0000000..b4de948 --- /dev/null +++ b/util/rwLock.go @@ -0,0 +1,70 @@ +package util + +import ( + "fmt" + "io/ioutil" + "os" + "sync" +) + +func RWLock() { + filePath := "test.txt" + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + writeFile(filePath, "Hello, World!") + }() + + go func() { + defer wg.Done() + readFile(filePath) + }() + + wg.Wait() +} + +func writeFile(filePath string, data string) { + rwMutex := new(sync.RWMutex) + _, err := os.Stat(filePath) + if err != nil && os.IsNotExist(err) { + file, err := os.Create(filePath) + if err != nil { + fmt.Println("Error creating file:", err) + return + } + defer file.Close() + + rwMutex.Lock() + _, err = file.WriteString(data) + if err != nil { + fmt.Println("Error writing to file:", err) + return + } + rwMutex.Unlock() + } else { + fmt.Println("File already exists") + } +} + +func readFile(filePath string) { + rwMutex := new(sync.RWMutex) + _, err := os.Stat(filePath) + if err != nil && os.IsNotExist(err) { + fmt.Println("File does not exist") + return + } else if err != nil { + fmt.Println("Error reading file:", err) + return + } + + rwMutex.RLock() + data, err := ioutil.ReadFile(filePath) + if err != nil { + fmt.Println("Error reading from file:", err) + return + } + fmt.Println(string(data)) + rwMutex.RUnlock() +} diff --git a/util/save.go b/util/save.go new file mode 100644 index 0000000..3c2508f --- /dev/null +++ b/util/save.go @@ -0,0 +1,58 @@ +package util + +import ( + "errors" + "log/slog" + "os" +) + +const ( + GB = 1024 * 1024 * 1024 +) + +/* +计算所提供文件大小 字节 +*/ + +func GetSize(fp string) (uint64, error) { + if file, err := os.Open(fp); err != nil { + return 0, err + } else if info, err := file.Stat(); err != nil { + return 0, err + } else { + defer file.Close() + size := uint64(info.Size()) + return size, nil + } +} + +/* +计算所给定的两个文件大小差 返回GB +*/ +func GetDiffSize(src, dst uint64) (float64, error) { + if dst >= src { + slog.Warn("处理后的文件比源文件更大,放弃", slog.Uint64("源文件大小", src), slog.Uint64("目标文件大小", dst)) + return 0, errors.New("处理后的文件比源文件更大,放弃") + } + save := float64(src-dst) / GB + return save, nil +} + +/* +计算所给定的两个文件名 返回GB +*/ +func GetDiffFileSize(src, dst string) (srcSize uint64, dstSize uint64, save float64, err error) { + //defer func() { + // if err := recover(); err != nil { + // slog.Warn("获取文件差值出错", slog.Any("错误原文", err)) + // return + // } + //}() + srcSize, err = GetSize(src) + dstSize, err = GetSize(dst) + save, err = GetDiffSize(srcSize, dstSize) + if err != nil { + return 0, 0, 0, errors.New("获取文件差值出错") + } + return srcSize, dstSize, save, nil +} diff --git a/util/threads.go b/util/threads.go new file mode 100644 index 0000000..c7d8682 --- /dev/null +++ b/util/threads.go @@ -0,0 +1 @@ +package util diff --git a/util/timeout.go b/util/timeout.go new file mode 100644 index 0000000..9478e13 --- /dev/null +++ b/util/timeout.go @@ -0,0 +1,20 @@ +package util + +import ( + "fmt" + "time" +) + +func WorkWithTimeout(timeout time.Duration) { + workCh := make(chan struct{}, 1) + go func() { + //LongTimeWork() //把要控制超时的函数放到子协程里去执行 + workCh <- struct{}{} + }() + select { //只执行最先到来的case + case <-workCh: //work先结束 + fmt.Println("work finish") + case <-time.After(timeout): //超时先来 + fmt.Println("work timeout") + } +} diff --git a/util/unit_test.go b/util/unit_test.go new file mode 100644 index 0000000..180fe10 --- /dev/null +++ b/util/unit_test.go @@ -0,0 +1,104 @@ +package util + +import ( + "fmt" + "github.com/klauspost/cpuid/v2" + "strings" + "sync/atomic" + "testing" + "time" +) + +var ( + concurrent int32 + concurrentLimit = make(chan struct{}, 10) +) + +func ReadDB() { + atomic.AddInt32(&concurrent, 1) + fmt.Printf("readDB并发度%v\n", atomic.LoadInt32(&concurrent)) + time.Sleep(200 * time.Microsecond) + atomic.AddInt32(&concurrent, -1) +} +func handler(f func()) { + concurrentLimit <- struct{}{} + //readDB() + f() + <-concurrentLimit + return +} +func TestLimitThreads(t *testing.T) { + for i := 0; i < 100; i++ { + go handler(ReadDB) + } + time.Sleep(3 * time.Second) +} + +// go test -v -run TestKindOfCPU ./ +func TestKindOfCPU(t *testing.T) { + fmt.Println("Name:", cpuid.CPU.BrandName) + fmt.Println("物理核心PhysicalCores:", cpuid.CPU.PhysicalCores) + fmt.Println("每个核心的线程ThreadsPerCore:", cpuid.CPU.ThreadsPerCore) + fmt.Println("逻辑核心LogicalCores:", cpuid.CPU.LogicalCores) + fmt.Println("Family", cpuid.CPU.Family, "Model:", cpuid.CPU.Model, "Vendor ID:", cpuid.CPU.VendorID) + fmt.Println("Features:", strings.Join(cpuid.CPU.FeatureSet(), ",")) + fmt.Println("Cacheline bytes:", cpuid.CPU.CacheLine) + fmt.Println("L1 Data Cache:", cpuid.CPU.Cache.L1D, "bytes") + fmt.Println("L1 Instruction Cache:", cpuid.CPU.Cache.L1I, "bytes") + fmt.Println("L2 Cache:", cpuid.CPU.Cache.L2, "bytes") + fmt.Println("L3 Cache:", cpuid.CPU.Cache.L3, "bytes") + fmt.Println("Frequency", cpuid.CPU.Hz, "hz") + + // Test if we have these specific features: + if cpuid.CPU.Supports(cpuid.SSE, cpuid.SSE2) { + fmt.Println("We have Streaming SIMD 2 Extensions") + } + t.Logf("Vendor ID is : %v\ttype is %s\n", cpuid.CPU.VendorID, cpuid.CPU.VendorID) + + if fmt.Sprintf("%v", cpuid.CPU.VendorID) == "Intel" { + t.Log(fmt.Sprintf("%v", cpuid.CPU.VendorID)) + } else { + fmt.Println("not") + } +} + +func BenchmarkRandom(b *testing.B) { + for i := 0; i < b.N; i++ { + RandomWithSeed() + } +} +func TestDuplicateBySlice(t *testing.T) { + s := ReadByLine("E:\\git\\ProcessAVI\\util\\list.txt") + r := DuplicateBySlice(s) + result := []string{} + for _, v := range r { + prefix := "ytdlp --proxy 127.0.0.1:8889" + suffix := strings.Join([]string{prefix, v}, " ") + result = append(result, suffix) + } + WriteByLine("E:\\git\\ProcessAVI\\util\\plist.ps1", result) +} + +func TestSave(t *testing.T) { + size, err := GetSize("/Users/zen/Downloads/curl.go") + if err != nil { + return + } + t.Log(size) +} + +func TestName(t *testing.T) { + a := "张韶涵2023722寓言天津站不害怕afire内场前排高清饭拍我在漫漫长夜之中飞翔寻找属于我的那道星光" //31个汉字 + var s string + t.Log(len(a)) + for i, char := range a { + t.Logf("第%d个字符:%v\n", i+1, string(char)) + if i >= 124 { + t.Log("截取124之前的完整字符") + break + } else { + s = strings.Join([]string{s, string(char)}, "") + } + } + t.Logf("截取的完整字符:%s\n", s) +} diff --git a/xhamster/file2shell.go b/xhamster/file2shell.go new file mode 100644 index 0000000..69eb1d9 --- /dev/null +++ b/xhamster/file2shell.go @@ -0,0 +1,17 @@ +package xhamster + +import ( + "processAll/util" + "strings" +) + +func File2Shell() { + wget := []string{"#!/bin/bash"} + links := util.ReadByLine("list.txt") + for _, link := range links { + prefix := "ytdlp --proxy http://172.26.0.1:8889" + cmd := strings.Join([]string{prefix, link}, " ") + wget = append(wget, cmd) + } + util.WriteByLine("list.sh", wget) +} diff --git a/xhamster/split.go b/xhamster/split.go new file mode 100644 index 0000000..d0dcced --- /dev/null +++ b/xhamster/split.go @@ -0,0 +1,18 @@ +package xhamster + +import ( + "os" + "strings" +) + +func SplitLinks(fp string) { + b, err := os.ReadFile(fp) + if err != nil { + return + } + c := string(b) + cs := strings.Replace(c, "https", "\nhttps", -1) + //cs = strings.Replace(c, "\n", "", 1) + bs := []byte(cs) + os.WriteFile("new.txt", bs, 0777) +} diff --git a/xhamster/unit_test.go b/xhamster/unit_test.go new file mode 100644 index 0000000..7053657 --- /dev/null +++ b/xhamster/unit_test.go @@ -0,0 +1,15 @@ +package xhamster + +import "testing" + +func TestFile2Shell(t *testing.T) { + File2Shell() +} + +/* +go test -v -run TestSplitLinks ./ +*/ +func TestSplitLinks(t *testing.T) { + fp := "/mnt/e/git/ProcessAVI/xhamster/list.txt" + SplitLinks(fp) +} diff --git a/ytdlp/yt-dlp.go b/ytdlp/yt-dlp.go new file mode 100644 index 0000000..93064e4 --- /dev/null +++ b/ytdlp/yt-dlp.go @@ -0,0 +1,65 @@ +package ytdlp + +import ( + "fmt" + "log/slog" + "os/exec" + "processAll/replace" + "processAll/sql" + "processAll/util" + "strconv" + "sync" +) + +func Ytdlp(fp string) { + lines := util.ReadByLine(fp) + var wg sync.WaitGroup + concurrency := util.GetVal("yt-dlp", "concurrency") + concurrencyNum, err := strconv.Atoi(concurrency) + if err != nil { + panic("yt-dlp下载并发数设置错误") + } + if concurrencyNum < 1 { + panic("下载并发数需要大于1") + } + ch := make(chan struct{}, concurrencyNum) + for _, line := range lines { + ch <- struct{}{} + wg.Add(1) + go func() { + defer wg.Done() + ytdlp(line) + <-ch + }() + slog.Debug("协程任务运行完成") + } + wg.Wait() +} +func ytdlp(line string) { + slog.Debug("协程任务运行") + prefix := util.GetVal("yt-dlp", "saveTo") + proxy := util.GetVal("yt-dlp", "proxy") + cmd := exec.Command("yt-dlp", "--proxy", proxy, "--no-playlist", "-P", prefix, "-o", "%(title)s.%(ext)s", line) + slog.Info("yt-dlp命令开始执行", slog.String("命令原文", fmt.Sprint(cmd)), slog.String("文件名", "%(title)s.%(ext)s")) + y := new(sql.Ytdlp) + y.URL = line + y.Status = "失败" + y.SetOne() + err := util.ExecCommand(cmd) + if err != nil { + slog.Warn("yt-dlp命令下载失败", slog.String("命令原文", fmt.Sprint(cmd)), slog.Any("错误原文", err)) + return + } + y.Status = "下载成功" + y.UpdateStatusByURL() + slog.Info("yt-dlp命令执行完成") +} + +func getName(link string) (string, error) { + cmd := exec.Command("yt-dlp", "--print", "filename", "-o", "%(title)s.%(ext)s", link) + fname, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + return replace.ForFileName(string(fname)), err +}