-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathvideo_cut_service.go
226 lines (205 loc) · 5.61 KB
/
video_cut_service.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
package videocut
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"time"
)
// GenerateRandomString 生成随机字符串
func GenerateRandomString(length int) string {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
rand.Seed(time.Now().UTC().UnixNano())
result := make([]byte, length)
for i := 0; i < length; i++ {
result[i] = alphanum[rand.Intn(len(alphanum))]
}
return string(result)
}
type StatusString string
const (
TASK_STATUS_RUNNING StatusString = "分析中"
TASK_STATUS_FAILED StatusString = "分析失败"
TASK_STATUS_SUCCESS StatusString = "分析成功"
)
type taskStatus struct {
cancel context.CancelFunc // use cancel to stop task
status StatusString
failedReason string
}
var runningTaskMap sync.Map // taskId -> taskStatus 的映射
var outMap sync.Map // taskId -> outputPath 的映射
// VideoCutReq 创建视频裁剪任务请求
type VideoCutReq struct {
StartTime float32 // 视频开始时间
EndTIme float32 // 视频结束时间
InputVideo string // 输入视频地址
}
// VideoCutRsp 创建视频裁剪任务返回
type VideoCutRsp struct {
TaskId string // 任务 ID
}
func cut(ctx context.Context, taskId, inputVideo string, startTime, endTIme float32) {
// 输出放同目录,修改文件名
spt := strings.Split(inputVideo, ".")
l := len(spt)
if l > 1 {
l--
}
outputVideo := fmt.Sprintf("%s_cut_%g_%g.mp4", strings.Join(spt[:l], "."), startTime, endTIme)
// install ffmpeg first
cmd := exec.CommandContext(ctx, "ffmpeg",
"-i", inputVideo,
"-ss", fmt.Sprintf("%f", startTime),
"-to", fmt.Sprintf("%f", endTIme),
"-c:v", "libx264",
"-crf", "30",
"-y", outputVideo,
)
errout := new(bytes.Buffer)
cmd.Stderr = errout
if err := cmd.Run(); err != nil {
os.Remove(outputVideo) // 出错删除输出文件
err = fmt.Errorf("exec ffmpeg failed: %v, stderr: %v", err, errout)
log.Printf("%v", err)
if st, ok := runningTaskMap.Load(taskId); ok {
st.(taskStatus).cancel() // 执行 cancel 清空 ctx
runningTaskMap.Store(taskId, taskStatus{
status: TASK_STATUS_FAILED,
failedReason: err.Error(),
})
}
} else {
if st, ok := runningTaskMap.Load(taskId); ok {
st.(taskStatus).cancel() // 执行 cancel 清空 ctx
runningTaskMap.Store(taskId, taskStatus{
status: TASK_STATUS_SUCCESS,
})
outMap.Store(taskId, outputVideo) // 存储输出路径
} else {
os.Remove(outputVideo) // 任务找不到删除输出文件
}
}
}
func videoCut(w http.ResponseWriter, r *http.Request) {
input, _ := ioutil.ReadAll(r.Body)
var req VideoCutReq
json.Unmarshal(input, &req)
taskId := "work-" + GenerateRandomString(8)
// 添加
ctx, cancel := context.WithCancel(context.Background())
status := taskStatus{
cancel: cancel, // 支持 stop cancel 任务
status: TASK_STATUS_RUNNING,
}
runningTaskMap.Store(taskId, status)
// 异步执行
go cut(ctx, taskId, req.InputVideo, req.StartTime, req.EndTIme)
rsp := VideoCutRsp{
TaskId: taskId,
}
bs, _ := json.Marshal(rsp)
w.Write(bs)
// log.Printf("success create task, task_id: %s, inputVideo: %s, startTime: %g, endTime: %g\n",
// rsp.TaskId, req.InputVideo, req.StartTime, req.EndTIme)
}
// StatusRequest ...
type StatusRequest struct {
TaskId string
}
// StatusResponse ...
type StatusResponse struct {
TaskId string
Status StatusString
Reason string
}
func status(w http.ResponseWriter, r *http.Request) {
input, _ := ioutil.ReadAll(r.Body)
var req StatusRequest
json.Unmarshal(input, &req)
task, ok := runningTaskMap.Load(req.TaskId)
var rsp StatusResponse
rsp.TaskId = req.TaskId
if !ok {
rsp.Status = TASK_STATUS_FAILED
rsp.Reason = "任务未找到"
} else {
rsp.Status = task.(taskStatus).status
rsp.Reason = task.(taskStatus).failedReason
}
if rsp.Status != TASK_STATUS_RUNNING {
// 如果任务已经完成,获取状态后删除临时状态
runningTaskMap.Delete(req.TaskId)
}
bs, _ := json.Marshal(rsp)
w.Write(bs)
}
// GetOutputVideoRequest ...
type GetOutputVideoRequest struct {
TaskId string
}
// GetOutputVideoResponse ...
type GetOutputVideoResponse struct {
TaskId string
OutputVideo string // 输出视频
Reason string // 如果找不到输出,返回找不到输出的原因
}
func getOutputVideo(w http.ResponseWriter, r *http.Request) {
input, _ := ioutil.ReadAll(r.Body)
var req GetOutputVideoRequest
json.Unmarshal(input, &req)
out, ok := outMap.Load(req.TaskId)
var rsp GetOutputVideoResponse
rsp.TaskId = req.TaskId
if ok {
rsp.OutputVideo = out.(string)
} else {
rsp.Reason = "请求参数错误,未找到任务输出"
}
bs, _ := json.Marshal(rsp)
w.Write(bs)
}
// StopRequest ...
type StopRequest struct {
TaskId string
}
// StopResponse ...
type StopResponse struct {
Reason string
}
func stop(w http.ResponseWriter, r *http.Request) {
input, _ := ioutil.ReadAll(r.Body)
var req StopRequest
json.Unmarshal(input, &req)
status, ok := runningTaskMap.Load(req.TaskId)
var rsp StopResponse
if !ok {
rsp.Reason = "未找到该任务"
} else {
if status.(taskStatus).status != TASK_STATUS_RUNNING {
rsp.Reason = "任务没有在执行中,请查询任务状态"
} else {
runningTaskMap.Delete(req.TaskId)
status.(taskStatus).cancel() // 取消任务
}
}
bs, _ := json.Marshal(rsp)
w.Write(bs)
}
// StartServer ...
func StartServer() {
http.HandleFunc("/VideoCut", videoCut)
http.HandleFunc("/Status", status)
http.HandleFunc("/GetOutputVideo", getOutputVideo)
http.HandleFunc("/Stop", stop)
fmt.Printf("start videocut service ...\n")
http.ListenAndServe("127.0.0.1:8001", nil)
}