-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathmain.go
232 lines (198 loc) · 6.59 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package main
import (
"errors"
"flag"
"fmt"
"mi_camera_merge/lib/log"
"os"
"os/exec"
"path/filepath"
"strconv"
"time"
)
const (
VERSION = "v1.1"
AUTHOR = "开发者:红烧猎人(联系作者:https://blog.enianteam.com/u/sun/content/11)"
ADDRESS = "工具开源地址和使用教程:https://github.com/hslr-s/xiaomi-camera-merge"
ERROR_EMPTY = "empty folder"
)
var Logger *log.Log
func main() {
var path string
var delete bool
var maxMerge string
flag.StringVar(&path, "path", "./", "视频的保存目录") // 定义字符串类型的标志
flag.BoolVar(&delete, "delete", false, "删除已经合并的视频文件夹") // 定义字符串类型的标志
flag.StringVar(&maxMerge, "max_merge", "0", "默认:0 不限制. 每次最大合并数量,如果视频量巨多,请使用本参数,分段执行") // 定义字符串类型的标志
flag.Parse() // 解析标志
// DOCKER环境使用 读取环境变量是否删除删除已经合并的视频文件夹
envDelete := os.Getenv("DELETE_SUCCESS")
if envDelete == "true" {
delete = true
}
envMaxMerge := os.Getenv("MAX_MERGE")
if envDelete != "" {
maxMerge = envMaxMerge
}
PrintAppInfo()
fmt.Println("正在初始化,请稍后")
fmt.Println("===========================")
// 初始化日志
currentTime := time.Now()
formattedTime := currentTime.Format("20060102_150405")
logPath := path + "/xiaomi-video-merge-log"
if err := os.MkdirAll(logPath, 0777); err != nil {
fmt.Println("错误中断执行:日志文件夹创建失败:", err.Error())
return
}
Logger = log.NewLog(logPath + "/" + formattedTime + ".log")
LoggerAppInfo()
EchoLog("=== 本次运行配置 ===")
EchoLog("视频储存的主目录:", path)
EchoLog("合并后是否删除源文件夹:", delete)
EchoLog("合并的时间取决于视频数量和尺寸,请尽量不要在合并过程中,修改操作目录的权限、删除目录等操作")
EchoLog("=== 合并开始 ===")
StartMergeHour(path, delete, maxMerge)
EchoLog("=== 合并结束 ===")
EchoLog("请注意!结束不代表全部成功了,具体请向上查看详情")
}
func StartMergeHour(path string, deleteSrc bool, maxMerge string) {
maxMergeInt := 0
i := 0
if v, err := strconv.Atoi(maxMerge); err == nil {
maxMergeInt = v
}
// 指定要遍历的目录
files, _ := os.ReadDir(path)
for _, v := range files {
// 合并 有效路径
if v.IsDir() && len(v.Name()) == 10 {
workDir := path + "/" + v.Name()
if savePath, err := MergeMp4ToMovByHour(workDir, path); err != nil {
EchoLog("错误:目录:", workDir, "视频合并失败。错误原因:", err)
} else {
if deleteSrc {
if err := os.RemoveAll(workDir); err != nil {
EchoLog("目录:", workDir, "视频合并成功,已保存在:", savePath, ",未能成功删除源目录,请手动删除,否则下次运行程序,会再次合并")
} else {
EchoLog("目录:", workDir, "视频合并成功,已保存在:", savePath, ",已删除源目录")
}
} else {
EchoLog("目录:", workDir, "视频合并成功,已保存在:", savePath)
}
}
i++
EchoLog("==========", i)
// 最大执行数量
if maxMergeInt != 0 && maxMergeInt == i {
return
}
}
}
}
// 按小时合并视频
func MergeMp4ToMovByHour(dir, outputPath string) (string, error) {
fileContent := ""
_, pathName := filepath.Split(dir)
year := pathName[0:4]
month := pathName[4:6]
dateNum := pathName[6:8]
// fmt.Println(pathName, year, month, dateNum, pathName[8:10])
outputPath += "/" + year + "/" + month + "/" + dateNum
// 创建目录
os.MkdirAll(outputPath, 0777)
// 使用 filepath.Walk 函数遍历目录
i := 0 // 待合并文件数量统计
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
// 判断文件是否为 .mp4 文件
if filepath.Ext(path) == ".mp4" {
// 打印文件路径
// fmt.Println(path)
_, fileName := filepath.Split(path)
fileContent += "file " + fileName + "\n"
i++
}
return nil
})
if err != nil {
// 目录遍历失败,终止合并
return "", errors.New("遍历目录失败:" + err.Error())
}
if i == 0 {
return "", errors.New(ERROR_EMPTY)
}
// 生成文件列表
filesTxtPath := dir + "/files.txt"
if err := SaveFileList(filesTxtPath, fileContent); err != nil {
return "", errors.New("生成临时文件出错" + err.Error())
}
defer os.Remove(filesTxtPath)
mergeFileName := pathName[8:10] + ".mov"
EchoLog("执行 ffmpeg 指令")
// 执行指令 ffmpeg -f concat -i files.txt -c copy !name!.mov
if err := ExecCommand(dir, "ffmpeg", "-f", "concat", "-i", "files.txt", "-c", "copy", mergeFileName); err != nil {
return "", err
}
// 移动文件
if err := os.Rename(dir+"/"+mergeFileName, outputPath+"/"+mergeFileName); err != nil {
os.Remove(dir + "/" + mergeFileName)
return "", errors.New("转移合并后的视频出错(为节省空间,已删除临时合并文件,记得重新合并)错误原因:" + err.Error())
}
// fmt.Println("保存到文件", outputPath+"/"+mergeFileName)
return outputPath + "/" + mergeFileName, nil
}
func SaveFileList(filePath, content string) error {
// 创建一个文件
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
// 写入文件内容
fmt.Fprintln(file, content)
// 保存文件
err = file.Sync()
if err != nil {
return err
}
return nil
}
func ExecCommand(workpath, name string, arg ...string) error {
// 创建一个命令
cmd := exec.Command(name, arg...)
cmd.Dir = workpath
EchoLog("执行命令:", cmd)
// 执行命令
out, err := cmd.Output()
if err != nil {
EchoLog("指令执行错误:\n", err)
return err
}
// 打印命令输出
if string(out) != "" {
EchoLog("指令执行结果输出:\n", string(out))
}
return nil
}
func PrintAppInfo() {
fmt.Println("===========================")
fmt.Println("小米摄像头视频合并工具", VERSION)
fmt.Println("===========================")
fmt.Println(AUTHOR)
fmt.Println(ADDRESS)
fmt.Println("===========================")
}
func LoggerAppInfo() {
Logger.WriteContent("===========================")
Logger.WriteContent("小米摄像头视频合并工具", VERSION)
Logger.WriteContent("===========================")
Logger.WriteContent(AUTHOR)
Logger.WriteContent(ADDRESS)
Logger.WriteContent("===========================")
}
func EchoLog(a ...any) {
fmt.Println(a...)
if err := Logger.WriteContent(a...); err != nil {
fmt.Println("日志生成错误:", err.Error())
}
}