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
|
/**
* @description 使用mediainfo计算本地音频或视频的时长,比ffmpeg更精确到毫秒
* @method GetMediaDurationByMediainfo
* @param filePath string 本地音频或视频文件路径
* @return float64 时长(秒),错误信息
*/
func GetAudioDurationLocal(filePath, mediainfoPath string) (float64, error) {
var duration float64
// 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return duration, fmt.Errorf("文件不存在: %s", filePath)
}
// 执行mediainfo命令,输出JSON格式
cmd := exec.Command(mediainfoPath, "--Output=JSON", filePath)
out, err := cmd.CombinedOutput()
if err != nil {
return duration, fmt.Errorf("执行mediainfo命令失败: %v, 输出: %s", err, string(out))
}
// 解析JSON输出
var mediainfoOutput struct {
Media struct {
Track []struct {
Type string `json:"@type"`
Duration string `json:"Duration"`
} `json:"track"`
} `json:"media"`
}
if err = json.Unmarshal(out, &mediainfoOutput); err != nil {
return duration, fmt.Errorf("解析mediainfo JSON输出失败: %v", err)
}
// 查找General或Video或Audio类型的track,获取Duration
// Duration字段可能为毫秒或秒,需要根据实际情况判断
var durationStr string
for _, track := range mediainfoOutput.Media.Track {
// 优先使用General类型的Duration,如果没有则使用Video或Audio
if track.Type == "General" && track.Duration != "" {
durationStr = track.Duration
break
} else if (track.Type == "Video" || track.Type == "Audio") && track.Duration != "" && durationStr == "" {
durationStr = track.Duration
}
}
if durationStr == "" {
return duration, fmt.Errorf("无法从mediainfo输出中获取Duration信息")
}
// 处理Duration字符串,可能包含单位(如 "12565 ms" 或 "12.565 s")
durationStr = strings.TrimSpace(durationStr)
durationStrLower := strings.ToLower(durationStr)
var durationFloat float64
// 检查是否包含单位标识
if strings.Contains(durationStrLower, "ms") || strings.Contains(durationStrLower, "毫秒") {
// 包含毫秒单位,提取数值并转换为秒
// 移除单位标识,提取数字部分
durationStr = strings.TrimSpace(strings.TrimSuffix(strings.TrimSuffix(durationStrLower, "ms"), "毫秒"))
durationStr = strings.TrimSpace(strings.TrimSuffix(durationStr, "s")) // 移除可能的 "s"
durationFloat, err = strconv.ParseFloat(durationStr, 64)
if err != nil {
return duration, fmt.Errorf("解析Duration值失败: %v, 原始值: %s", err, durationStr)
}
duration = durationFloat / 1000.0
} else if strings.HasSuffix(durationStrLower, "s") && !strings.HasSuffix(durationStrLower, "ms") || strings.Contains(durationStrLower, "秒") {
// 包含秒单位(但不包含ms),提取数值即可
durationStr = strings.TrimSpace(strings.TrimSuffix(durationStrLower, "s"))
durationStr = strings.TrimSpace(strings.TrimSuffix(durationStr, "秒"))
durationFloat, err = strconv.ParseFloat(durationStr, 64)
if err != nil {
return duration, fmt.Errorf("解析Duration值失败: %v, 原始值: %s", err, durationStr)
}
duration = durationFloat
} else {
// 不包含单位,需要判断是毫秒还是秒
// 通常mediainfo的Duration是毫秒,但如果值很小(小于1000),可能是秒
durationFloat, err = strconv.ParseFloat(durationStr, 64)
if err != nil {
return duration, fmt.Errorf("解析Duration值失败: %v, 原始值: %s", err, durationStr)
}
// 如果值小于1000,可能是秒;否则认为是毫秒
// 注意:对于小于1秒的音频(如0.5秒),mediainfo可能返回500(毫秒)或0.5(秒)
// 这里采用保守策略:如果值小于1000,认为是秒;否则认为是毫秒
if durationFloat < 1000 {
duration = durationFloat
} else {
duration = durationFloat / 1000.0
}
}
return duration, nil
}
// 获取音频宽高-本地文件(eg:ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 /Users/xxx/Downloads/xxx.mp4)
func GetMediaSizeLocal(filepath, ffprobePath, bashPath string) (int64, int64, error) {
var width, height int64
cmd := exec.Command(bashPath, "-c", ffprobePath+" -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 "+filepath)
out, err := cmd.CombinedOutput()
if err != nil {
return width, height, errors.New("cmd err: " + err.Error() + "->" + string(out))
}
sieStr := strings.TrimSpace(string(out))
sizeArr := strings.Split(sieStr, ",")
if len(sizeArr) < 2 {
return width, height, nil
}
height, _ = strconv.ParseInt(sizeArr[0], 10, 64)
width, _ = strconv.ParseInt(sizeArr[1], 10, 64)
return width, height, nil
}
// 从视频中提取音频-链接
func GetAudioFile(url, filepath, ffmpegPath, bashPath string) (string, error) {
var (
audioFile string
err error
)
ext := GetUrlFileExt(url)
if ext == "" {
ext = ".mp4"
}
videoFile := filepath + "/" + uuid.New().String() + ext
if err := DownLoad(url, videoFile); err != nil {
return audioFile, err
}
audioFile, err = GetAudioFileLocal(videoFile, filepath, ffmpegPath, bashPath)
if err != nil {
return audioFile, err
}
if err := os.Remove(videoFile); err != nil {
return audioFile, errors.New("remove err: " + err.Error())
}
return audioFile, nil
}
// 从视频中提取音频-本地文件(eg:ffmpeg -i /Users/xxx/Downloads/xxx.mp4 -f mp3 -vn /Users/xxx/Downloads/xxx.mp3)
func GetAudioFileLocal(videoPath, filepath, ffmpegPath, bashPath string) (string, error) {
audioFile := filepath + "/" + uuid.New().String() + ".mp3"
cmd := exec.Command(bashPath, "-c", ffmpegPath+" -i "+videoPath+" -f mp3 -vn "+audioFile)
out, err := cmd.CombinedOutput()
if err != nil {
return audioFile, errors.New("cmd err: " + err.Error() + "->" + string(out))
}
return audioFile, nil
}
// 转换音视频格式-链接
// formatExt 转换格式后缀,如.mp3, .mp4
func TransferMediaFormat(formatExt, url, filepath, ffmpegPath, bashPath string) (string, error) {
var (
outputFile string
err error
)
ext := GetUrlFileExt(url)
if ext == "" {
ext = formatExt
}
mediaFile := filepath + "/" + uuid.New().String() + ext
if err := DownLoad(url, mediaFile); err != nil {
return outputFile, err
}
outputFile, err = TransferMediaFormatLocal(formatExt, mediaFile, filepath, ffmpegPath, bashPath)
if err != nil {
return outputFile, err
}
if err := os.Remove(mediaFile); err != nil {
return outputFile, errors.New("remove err: " + err.Error())
}
return outputFile, nil
}
// 转换音视频格式-本地文件
// formatExt 转换格式后缀,如.mp3, .mp4
func TransferMediaFormatLocal(formatExt, mediaFile, filepath, ffmpegPath, bashPath string) (string, error) {
outputFile := filepath + "/" + uuid.New().String() + formatExt
cmd := exec.Command(bashPath, "-c", ffmpegPath+" -i "+mediaFile+" "+outputFile)
out, err := cmd.CombinedOutput()
if err != nil {
return outputFile, errors.New("cmd err: " + err.Error() + "->" + string(out))
}
return outputFile, nil
}
|