mirror of https://github.com/EasyDarwin/EasyDarwin
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
5.5 KiB
188 lines
5.5 KiB
package routers |
|
|
|
import ( |
|
"bytes" |
|
"log" |
|
"math" |
|
"os" |
|
"os/exec" |
|
"path/filepath" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"github.com/gin-gonic/gin" |
|
"github.com/penggy/EasyGoLib/utils" |
|
) |
|
|
|
/** |
|
* @apiDefine record 录像 |
|
*/ |
|
|
|
/** |
|
* @apiDefine fileInfo |
|
* @apiSuccess (200) {String} duration 格式化好的录像时长 |
|
* @apiSuccess (200) {Number} durationMillis 录像时长,毫秒为单位 |
|
* @apiSuccess (200) {String} path 录像文件的相对路径,其绝对路径为:http[s]://host:port/record/[path]。 |
|
* @apiSuccess (200) {String} folder 录像文件夹,录像文件夹以推流路径命名。 |
|
*/ |
|
|
|
/** |
|
* @api {get} /api/v1/record/folders 获取所有录像文件夹 |
|
* @apiGroup record |
|
* @apiName RecordFolders |
|
* @apiParam {Number} [start] 分页开始,从零开始 |
|
* @apiParam {Number} [limit] 分页大小 |
|
* @apiParam {String} [sort] 排序字段 |
|
* @apiParam {String=ascending,descending} [order] 排序顺序 |
|
* @apiParam {String} [q] 查询参数 |
|
* @apiSuccess (200) {Number} total 总数 |
|
* @apiSuccess (200) {Array} rows 文件夹列表 |
|
* @apiSuccess (200) {String} rows.folder 录像文件夹名称 |
|
*/ |
|
func (h *APIHandler) RecordFolders(c *gin.Context) { |
|
mp4Path := utils.Conf().Section("rtsp").Key("m3u8_dir_path").MustString("") |
|
form := utils.NewPageForm() |
|
if err := c.Bind(form); err != nil { |
|
log.Printf("record folder bind err:%v", err) |
|
return |
|
} |
|
var files = make([]interface{}, 0) |
|
if mp4Path != "" { |
|
visit := func(files *[]interface{}) filepath.WalkFunc { |
|
return func(path string, info os.FileInfo, err error) error { |
|
if err != nil { |
|
return err |
|
} |
|
if path == mp4Path { |
|
return nil |
|
} |
|
if !info.IsDir() { |
|
return nil |
|
} |
|
*files = append(*files, map[string]interface{}{"folder": info.Name()}) |
|
return filepath.SkipDir |
|
} |
|
} |
|
err := filepath.Walk(mp4Path, visit(&files)) |
|
if err != nil { |
|
log.Printf("Query RecordFolders err:%v", err) |
|
} |
|
} |
|
pr := utils.NewPageResult(files) |
|
if form.Sort != "" { |
|
pr.Sort(form.Sort, form.Order) |
|
} |
|
pr.Slice(form.Start, form.Limit) |
|
c.IndentedJSON(200, pr) |
|
|
|
} |
|
|
|
/** |
|
* @api {get} /api/v1/record/files 获取所有录像文件 |
|
* @apiGroup record |
|
* @apiName RecordFiles |
|
* @apiParam {Number} folder 录像文件所在的文件夹 |
|
* @apiParam {Number} [start] 分页开始,从零开始 |
|
* @apiParam {Number} [limit] 分页大小 |
|
* @apiParam {String} [sort] 排序字段 |
|
* @apiParam {String=ascending,descending} [order] 排序顺序 |
|
* @apiParam {String} [q] 查询参数 |
|
* @apiSuccess (200) {Number} total 总数 |
|
* @apiSuccess (200) {Array} rows 文件列表 |
|
* @apiSuccess (200) {String} rows.duration 格式化好的录像时长 |
|
* @apiSuccess (200) {Number} rows.durationMillis 录像时长,毫秒为单位 |
|
* @apiSuccess (200) {String} rows.path 录像文件的相对路径,录像文件为m3u8格式,将其放到video标签中便可直接播放。其绝对路径为:http[s]://host:port/record/[path]。 |
|
*/ |
|
func (h *APIHandler) RecordFiles(c *gin.Context) { |
|
type Form struct { |
|
utils.PageForm |
|
Folder string `form:"folder" binding:"required"` |
|
StartAt int `form:"beginUTCSecond"` |
|
StopAt int `form:"endUTCSecond"` |
|
} |
|
var form = Form{} |
|
form.Limit = math.MaxUint32 |
|
err := c.Bind(&form) |
|
if err != nil { |
|
log.Printf("record file bind err:%v", err) |
|
return |
|
} |
|
|
|
files := make([]interface{}, 0) |
|
mp4Path := utils.Conf().Section("rtsp").Key("m3u8_dir_path").MustString("") |
|
if mp4Path != "" { |
|
ffmpeg_path := utils.Conf().Section("rtsp").Key("ffmpeg_path").MustString("") |
|
ffmpeg_folder, executable := filepath.Split(ffmpeg_path) |
|
split := strings.Split(executable, ".") |
|
suffix := "" |
|
if len(split) > 1 { |
|
suffix = split[1] |
|
} |
|
ffprobe := ffmpeg_folder + "ffprobe" + suffix |
|
folder := filepath.Join(mp4Path, form.Folder) |
|
visit := func(files *[]interface{}) filepath.WalkFunc { |
|
return func(path string, info os.FileInfo, err error) error { |
|
if err != nil { |
|
return err |
|
} |
|
if path == folder { |
|
return nil |
|
} |
|
if info.IsDir() { |
|
return nil |
|
} |
|
if info.Size() == 0 { |
|
return nil |
|
} |
|
if info.Name() == ".DS_Store" { |
|
return nil |
|
} |
|
if !strings.HasSuffix(strings.ToLower(info.Name()), ".m3u8") && !strings.HasSuffix(strings.ToLower(info.Name()), ".ts") { |
|
return nil |
|
} |
|
cmd := exec.Command(ffprobe, "-i", path) |
|
cmdOutput := &bytes.Buffer{} |
|
//cmd.Stdout = cmdOutput |
|
cmd.Stderr = cmdOutput |
|
err = cmd.Run() |
|
bytes := cmdOutput.Bytes() |
|
output := string(bytes) |
|
//log.Printf("%v result:%v", cmd, output) |
|
var average = regexp.MustCompile(`Duration: ((\d+):(\d+):(\d+).(\d+))`) |
|
result := average.FindStringSubmatch(output) |
|
duration := time.Duration(0) |
|
durationStr := "" |
|
if len(result) > 0 { |
|
durationStr = result[1] |
|
h, _ := strconv.Atoi(result[2]) |
|
duration += time.Duration(h) * time.Hour |
|
m, _ := strconv.Atoi(result[3]) |
|
duration += time.Duration(m) * time.Minute |
|
s, _ := strconv.Atoi(result[4]) |
|
duration += time.Duration(s) * time.Second |
|
millis, _ := strconv.Atoi(result[5]) |
|
duration += time.Duration(millis) * time.Millisecond |
|
} |
|
|
|
*files = append(*files, map[string]interface{}{ |
|
"path": path[len(mp4Path):], |
|
"durationMillis": duration / time.Millisecond, |
|
"duration": durationStr}) |
|
return nil |
|
} |
|
} |
|
err = filepath.Walk(folder, visit(&files)) |
|
if err != nil { |
|
log.Printf("Query RecordFolders err:%v", err) |
|
} |
|
} |
|
|
|
pr := utils.NewPageResult(files) |
|
if form.Sort != "" { |
|
pr.Sort(form.Sort, form.Order) |
|
} |
|
pr.Slice(form.Start, form.Limit) |
|
c.IndentedJSON(200, pr) |
|
}
|
|
|