mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 01:15:52 +08:00
Revert "fix: snap manual mode not save"
This reverts commit 5a2af0e7fd
.
This commit is contained in:
1
go.mod
1
go.mod
@@ -102,6 +102,7 @@ require (
|
||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||
github.com/pion/turn/v2 v2.1.2 // indirect
|
||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
||||
github.com/pion/webrtc/v4 v4.0.7 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
|
@@ -56,11 +56,11 @@ GET /query?streamPath={streamPath}&snapTime={timestamp}
|
||||
## 截图模式说明
|
||||
|
||||
### 时间间隔模式 (snapmode: 0)
|
||||
- 按照配置的 `timeinterval` 定时对流进行截图
|
||||
- 按照配置的 `snaptimeinterval` 定时对流进行截图
|
||||
- 适合需要固定时间间隔截图的场景
|
||||
|
||||
### 关键帧间隔模式 (snapmode: 1)
|
||||
- 按照配置的 `iframeinterval` 对关键帧进行截图
|
||||
- 按照配置的 `snapiframeinterval` 对关键帧进行截图
|
||||
- 适合需要按视频内容变化进行截图的场景
|
||||
|
||||
### HTTP请求模式 (snapmode: 2)
|
||||
@@ -90,15 +90,15 @@ GET /query?streamPath={streamPath}&snapTime={timestamp}
|
||||
配置示例:
|
||||
```yaml
|
||||
snap:
|
||||
watermark:
|
||||
snapwatermark:
|
||||
text: "测试水印 $T{2006-01-02 15:04:05}"
|
||||
fontpath: "/path/to/font.ttf"
|
||||
fontcolor: "rgba(255,0,0,0.5)"
|
||||
fontsize: 48
|
||||
offsetx: 20
|
||||
offsety: 20
|
||||
mode: 0
|
||||
timeinterval: 1m
|
||||
snapmode: 0
|
||||
snaptimeinterval: 1m
|
||||
```
|
||||
|
||||
## 数据库记录
|
||||
@@ -115,24 +115,24 @@ snap:
|
||||
1. 基础配置示例:
|
||||
```yaml
|
||||
snap:
|
||||
timeinterval: 30s
|
||||
savepath: "./snapshots"
|
||||
mode: 1
|
||||
iframeinterval: 5
|
||||
snaptimeinterval: 30s
|
||||
snapsavepath: "./snapshots"
|
||||
snapmode: 1
|
||||
snapiframeinterval: 5
|
||||
```
|
||||
|
||||
2. 带水印的配置示例:
|
||||
```yaml
|
||||
snap:
|
||||
watermark:
|
||||
snapwatermark:
|
||||
text: "测试水印"
|
||||
fontpath: "/path/to/font.ttf"
|
||||
fontcolor: "rgba(255,0,0,0.5)"
|
||||
fontsize: 48
|
||||
offsetx: 20
|
||||
offsety: 20
|
||||
mode: 0
|
||||
timeinterval: 1m
|
||||
snapmode: 0
|
||||
snaptimeinterval: 1m
|
||||
```
|
||||
|
||||
3. API调用示例:
|
||||
|
@@ -49,9 +49,9 @@ func (p *SnapPlugin) snap(streamPath string) (*bytes.Buffer, error) {
|
||||
//}).WaitStarted()
|
||||
|
||||
// 如果设置了水印文字,添加水印
|
||||
if p.Watermark.Text != "" {
|
||||
if p.SnapWatermark.Text != "" {
|
||||
// 读取字体文件
|
||||
fontBytes, err := os.ReadFile(p.Watermark.FontPath)
|
||||
fontBytes, err := os.ReadFile(p.SnapWatermark.FontPath)
|
||||
if err != nil {
|
||||
p.Error("read font file failed", "error", err.Error())
|
||||
return nil, err
|
||||
@@ -72,7 +72,7 @@ func (p *SnapPlugin) snap(streamPath string) (*bytes.Buffer, error) {
|
||||
}
|
||||
|
||||
// 解码颜色
|
||||
rgba, err := parseRGBA(p.Watermark.FontColor)
|
||||
rgba, err := parseRGBA(p.SnapWatermark.FontColor)
|
||||
if err != nil {
|
||||
p.Error("parse color failed", "error", err.Error())
|
||||
return nil, err
|
||||
@@ -84,9 +84,9 @@ func (p *SnapPlugin) snap(streamPath string) (*bytes.Buffer, error) {
|
||||
|
||||
// 添加水印
|
||||
result, err := watermark.DrawWatermarkSingle(img, watermark.TextConfig{
|
||||
Text: p.Watermark.Text,
|
||||
Text: p.SnapWatermark.Text,
|
||||
Font: font,
|
||||
FontSize: p.Watermark.FontSize,
|
||||
FontSize: p.SnapWatermark.FontSize,
|
||||
Spacing: 10,
|
||||
RowSpacing: 10,
|
||||
ColSpacing: 20,
|
||||
@@ -96,8 +96,8 @@ func (p *SnapPlugin) snap(streamPath string) (*bytes.Buffer, error) {
|
||||
Color: rgba,
|
||||
IsGrid: false,
|
||||
Angle: 0,
|
||||
OffsetX: p.Watermark.OffsetX,
|
||||
OffsetY: p.Watermark.OffsetY,
|
||||
OffsetX: p.SnapWatermark.OffsetX,
|
||||
OffsetY: p.SnapWatermark.OffsetY,
|
||||
}, false)
|
||||
if err != nil {
|
||||
p.Error("add watermark failed", "error", err.Error())
|
||||
@@ -138,9 +138,9 @@ func (p *SnapPlugin) doSnap(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 如果设置了水印文字,添加水印
|
||||
if p.Watermark.Text != "" {
|
||||
if p.SnapWatermark.Text != "" {
|
||||
// 读取字体文件
|
||||
fontBytes, err := os.ReadFile(p.Watermark.FontPath)
|
||||
fontBytes, err := os.ReadFile(p.SnapWatermark.FontPath)
|
||||
if err != nil {
|
||||
p.Error("read font file failed", "error", err.Error())
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
@@ -164,7 +164,7 @@ func (p *SnapPlugin) doSnap(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 解码颜色
|
||||
rgba, err := parseRGBA(p.Watermark.FontColor)
|
||||
rgba, err := parseRGBA(p.SnapWatermark.FontColor)
|
||||
if err != nil {
|
||||
p.Error("parse color failed", "error", err.Error())
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
@@ -177,9 +177,9 @@ func (p *SnapPlugin) doSnap(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 添加水印
|
||||
result, err := watermark.DrawWatermarkSingle(img, watermark.TextConfig{
|
||||
Text: p.Watermark.Text,
|
||||
Text: p.SnapWatermark.Text,
|
||||
Font: font,
|
||||
FontSize: p.Watermark.FontSize,
|
||||
FontSize: p.SnapWatermark.FontSize,
|
||||
Spacing: 10,
|
||||
RowSpacing: 10,
|
||||
ColSpacing: 20,
|
||||
@@ -189,8 +189,8 @@ func (p *SnapPlugin) doSnap(rw http.ResponseWriter, r *http.Request) {
|
||||
Color: rgba,
|
||||
IsGrid: false,
|
||||
Angle: 0,
|
||||
OffsetX: p.Watermark.OffsetX,
|
||||
OffsetY: p.Watermark.OffsetY,
|
||||
OffsetX: p.SnapWatermark.OffsetX,
|
||||
OffsetY: p.SnapWatermark.OffsetY,
|
||||
}, false)
|
||||
if err != nil {
|
||||
p.Error("add watermark failed", "error", err.Error())
|
||||
@@ -207,23 +207,20 @@ func (p *SnapPlugin) doSnap(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if p.SavePath != "" && p.IsManualModeSave {
|
||||
//判断 SavePath 是否存在
|
||||
if _, err := os.Stat(p.SavePath); os.IsNotExist(err) {
|
||||
os.MkdirAll(p.SavePath, 0755)
|
||||
}
|
||||
// 保存截图并记录到数据库
|
||||
if p.DB != nil {
|
||||
now := time.Now()
|
||||
filename := fmt.Sprintf("%s_%s.jpg", streamPath, now.Format("20060102150405.000"))
|
||||
filename = strings.ReplaceAll(filename, "/", "_")
|
||||
savePath := filepath.Join(p.SavePath, filename)
|
||||
// 保存到本地
|
||||
err = os.WriteFile(savePath, buf.Bytes(), 0644)
|
||||
if err != nil {
|
||||
p.Error("save snapshot failed", "error", err.Error())
|
||||
savePath = ""
|
||||
}
|
||||
// 保存截图并记录到数据库
|
||||
if p.DB != nil {
|
||||
savePath := filepath.Join(p.SnapSavePath, filename)
|
||||
|
||||
if p.SnapSavePath != "" {
|
||||
// 保存到本地
|
||||
err = os.WriteFile(savePath, buf.Bytes(), 0644)
|
||||
if err != nil {
|
||||
p.Error("save snapshot failed", "error", err.Error())
|
||||
savePath = ""
|
||||
}
|
||||
// 保存记录到数据库
|
||||
record := snap_pkg.SnapRecord{
|
||||
StreamName: streamPath,
|
||||
@@ -284,7 +281,7 @@ func (p *SnapPlugin) querySnap(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 计算时间差(秒)
|
||||
timeDiff := targetTime.Sub(record.SnapTime).Seconds()
|
||||
if timeDiff > float64(time.Duration(p.QueryTimeDelta)*time.Second) {
|
||||
if timeDiff > float64(time.Duration(p.SnapQueryTimeDelta)*time.Second) {
|
||||
http.Error(rw, "no snapshot found within time delta", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ var _ = m7s.InstallPlugin[SnapPlugin](snap.NewTransform)
|
||||
|
||||
type SnapPlugin struct {
|
||||
m7s.Plugin
|
||||
Watermark struct {
|
||||
SnapWatermark struct {
|
||||
Text string `default:"" desc:"水印文字内容"`
|
||||
FontPath string `default:"" desc:"水印字体文件路径"`
|
||||
FontColor string `default:"rgba(255,165,0,1)" desc:"水印字体颜色,支持rgba格式"`
|
||||
@@ -28,25 +28,25 @@ type SnapPlugin struct {
|
||||
OffsetY int `default:"0" desc:"水印位置Y"`
|
||||
} `desc:"水印配置"`
|
||||
// 定时任务相关配置
|
||||
TimeInterval time.Duration `default:"1m" desc:"截图间隔"`
|
||||
SavePath string `default:"snaps" desc:"截图保存路径"`
|
||||
Filter string `default:".*" desc:"截图流过滤器,支持正则表达式"`
|
||||
IFrameInterval int `default:"3" desc:"间隔多少帧截图"`
|
||||
Mode int `default:"1" desc:"截图模式 0:间隔时间 1:间隔关键帧"`
|
||||
QueryTimeDelta int `default:"3" desc:"查询截图时允许的最大时间差(秒)"`
|
||||
IsManualModeSave bool `default:"false" desc:"手动截图是否保存文件"`
|
||||
filterRegex *regexp.Regexp
|
||||
SnapTimeInterval time.Duration `default:"1m" desc:"截图间隔"`
|
||||
SnapSavePath string `default:"snaps" desc:"截图保存路径"`
|
||||
Filter string `default:".*" desc:"截图流过滤器,支持正则表达式"`
|
||||
SnapIFrameInterval int `default:"3" desc:"间隔多少帧截图"`
|
||||
SnapMode int `default:"1" desc:"截图模式 0:间隔时间 1:间隔关键帧"`
|
||||
SnapQueryTimeDelta int `default:"3" desc:"查询截图时允许的最大时间差(秒)"`
|
||||
SnapSaveManual bool `default:"false" desc:"手动截图是否保存文件"`
|
||||
filterRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
// OnInit 在插件初始化时添加定时任务
|
||||
func (p *SnapPlugin) OnInit() (err error) {
|
||||
// 检查 Mode 的值范围
|
||||
if p.Mode < 0 || p.Mode > 1 {
|
||||
// 检查 SnapMode 的值范围
|
||||
if p.SnapMode < 0 || p.SnapMode > 1 {
|
||||
p.Error("invalid snap mode",
|
||||
"mode", p.Mode,
|
||||
"mode", p.SnapMode,
|
||||
"valid_range", "0-1",
|
||||
)
|
||||
return fmt.Errorf("invalid snap mode: %d, valid range is 0-1", p.Mode)
|
||||
return fmt.Errorf("invalid snap mode: %d, valid range is 0-1", p.SnapMode)
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
@@ -59,7 +59,7 @@ func (p *SnapPlugin) OnInit() (err error) {
|
||||
}
|
||||
|
||||
// 创建保存目录
|
||||
if err = os.MkdirAll(p.SavePath, 0755); err != nil {
|
||||
if err = os.MkdirAll(p.SnapSavePath, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,23 +71,23 @@ func (p *SnapPlugin) OnInit() (err error) {
|
||||
|
||||
// 初始化全局水印配置
|
||||
snap.GlobalWatermarkConfig = snap.WatermarkConfig{
|
||||
Text: p.Watermark.Text,
|
||||
FontPath: p.Watermark.FontPath,
|
||||
FontSize: p.Watermark.FontSize,
|
||||
Text: p.SnapWatermark.Text,
|
||||
FontPath: p.SnapWatermark.FontPath,
|
||||
FontSize: p.SnapWatermark.FontSize,
|
||||
FontColor: color.RGBA{}, // 将在下面解析
|
||||
OffsetX: p.Watermark.OffsetX,
|
||||
OffsetY: p.Watermark.OffsetY,
|
||||
OffsetX: p.SnapWatermark.OffsetX,
|
||||
OffsetY: p.SnapWatermark.OffsetY,
|
||||
}
|
||||
|
||||
if p.Watermark.Text != "" {
|
||||
if p.SnapWatermark.Text != "" {
|
||||
// 判断字体是否存在
|
||||
if _, err := os.Stat(p.Watermark.FontPath); os.IsNotExist(err) {
|
||||
p.Error("watermark font file not found", "path", p.Watermark.FontPath)
|
||||
if _, err := os.Stat(p.SnapWatermark.FontPath); os.IsNotExist(err) {
|
||||
p.Error("watermark font file not found", "path", p.SnapWatermark.FontPath)
|
||||
return fmt.Errorf("watermark font file not found: %w", err)
|
||||
}
|
||||
// 解析颜色
|
||||
if p.Watermark.FontColor != "" {
|
||||
rgba := p.Watermark.FontColor
|
||||
if p.SnapWatermark.FontColor != "" {
|
||||
rgba := p.SnapWatermark.FontColor
|
||||
rgba = strings.TrimPrefix(rgba, "rgba(")
|
||||
rgba = strings.TrimSuffix(rgba, ")")
|
||||
parts := strings.Split(rgba, ",")
|
||||
@@ -118,18 +118,18 @@ func (p *SnapPlugin) OnInit() (err error) {
|
||||
}
|
||||
|
||||
//如果截图模式不是时间模式,则不加定时任务
|
||||
if p.Mode != 0 {
|
||||
if p.SnapMode != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果间隔时间小于0,则不添加定时任务;等于0则走onpub的transform
|
||||
if p.TimeInterval <= 0 {
|
||||
if p.SnapTimeInterval <= 0 {
|
||||
return
|
||||
}
|
||||
// 添加定时任务
|
||||
p.AddTask(&SnapTimerTask{
|
||||
Interval: p.TimeInterval,
|
||||
SavePath: p.SavePath,
|
||||
Interval: p.SnapTimeInterval,
|
||||
SavePath: p.SnapSavePath,
|
||||
Plugin: p,
|
||||
})
|
||||
|
||||
|
@@ -156,20 +156,20 @@ type Transformer struct {
|
||||
|
||||
func (t *Transformer) Start() (err error) {
|
||||
// 获取配置,带默认值检查
|
||||
if t.TransformJob.Plugin.Config.Has("TimeInterval") {
|
||||
t.snapTimeInterval = t.TransformJob.Plugin.Config.Get("TimeInterval").GetValue().(time.Duration)
|
||||
if t.TransformJob.Plugin.Config.Has("SnapTimeInterval") {
|
||||
t.snapTimeInterval = t.TransformJob.Plugin.Config.Get("SnapTimeInterval").GetValue().(time.Duration)
|
||||
} else {
|
||||
t.snapTimeInterval = time.Minute // 默认1分钟
|
||||
}
|
||||
|
||||
if t.TransformJob.Plugin.Config.Has("SavePath") {
|
||||
t.savePath = t.TransformJob.Plugin.Config.Get("SavePath").GetValue().(string)
|
||||
if t.TransformJob.Plugin.Config.Has("SnapSavePath") {
|
||||
t.savePath = t.TransformJob.Plugin.Config.Get("SnapSavePath").GetValue().(string)
|
||||
} else {
|
||||
t.savePath = "snaps" // 默认保存路径
|
||||
}
|
||||
|
||||
if t.TransformJob.Plugin.Config.Has("Mode") {
|
||||
t.snapMode = t.TransformJob.Plugin.Config.Get("Mode").GetValue().(int)
|
||||
if t.TransformJob.Plugin.Config.Has("SnapMode") {
|
||||
t.snapMode = t.TransformJob.Plugin.Config.Get("SnapMode").GetValue().(int)
|
||||
} else {
|
||||
t.snapMode = 1 // 默认使用关键帧模式
|
||||
}
|
||||
@@ -182,8 +182,8 @@ func (t *Transformer) Start() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.TransformJob.Plugin.Config.Has("IFrameInterval") {
|
||||
t.snapFrameInterval = t.TransformJob.Plugin.Config.Get("IFrameInterval").GetValue().(int)
|
||||
if t.TransformJob.Plugin.Config.Has("SnapIFrameInterval") {
|
||||
t.snapFrameInterval = t.TransformJob.Plugin.Config.Get("SnapIFrameInterval").GetValue().(int)
|
||||
} else {
|
||||
t.snapFrameInterval = 3 // 默认每3个I帧截图一次
|
||||
}
|
||||
|
@@ -57,7 +57,7 @@ func (t *SnapTimerTask) Tick(any) {
|
||||
if t.Plugin.DB != nil {
|
||||
record := snap_pkg.SnapRecord{
|
||||
StreamName: streamPath,
|
||||
SnapMode: t.Plugin.Mode,
|
||||
SnapMode: t.Plugin.SnapMode,
|
||||
SnapTime: now,
|
||||
SnapPath: savePath,
|
||||
}
|
||||
|
Reference in New Issue
Block a user