Compare commits

...

5 Commits

Author SHA1 Message Date
banshan
421f77c1de Revert "fix: snap manual mode"
This reverts commit d26690c7fa.
2025-01-05 16:19:11 +08:00
banshan
d2715d61d5 Revert "fix: snap manual mode not save"
This reverts commit 5a2af0e7fd.
2025-01-05 16:19:11 +08:00
banshan
3649b667d9 Revert "fix: snap use global config to reduce font memory"
This reverts commit df348e6946.
2025-01-05 16:19:11 +08:00
banshan
d74b9efdbf Revert "fix: snap timeinterval crash"
This reverts commit f440d4cbbf.
2025-01-05 16:19:11 +08:00
banshan
7f2712ae71 Revert "fix: add verify timeinterval and iframeinterval in snap config"
This reverts commit 3189973690.
2025-01-05 16:19:11 +08:00
8 changed files with 231 additions and 165 deletions

View File

@@ -46,22 +46,21 @@ cascadeserver:
snap: snap:
enable: false enable: false
ismanualmodesave: true # 手动截图是否保存文件 snapsavemanual: false # 手动截图是否保存文件
watermark: snapwatermark:
text: "Monibuca $T{2006-01-02 15:04:05.000}" text: "Monibuca$T{2006-01-02 15:04:05}"
fontpath: "/System/Library/Fonts/STHeiti Light.ttc" # mac字体路径 fontpath: "/System/Library/Fonts/STHeiti Light.ttc" # mac字体路径
# fontpath: "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" # linux字体路径 思源黑体 # fontpath: "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" # linux字体路径 思源黑体
# fontpath: "C:/Windows/Fonts/msyh.ttf" # windows字体路径 微软雅黑 # fontpath: "C:/Windows/Fonts/msyh.ttf" # windows字体路径 微软雅黑
fontsize: 16 fontsize: 36
fontspacing: 2 # 添加字体间距配置
fontcolor: "rgba(255,165,0,1)" fontcolor: "rgba(255,165,0,1)"
offsetx: 10 offsetx: 10
offsety: 10 offsety: 10
mode: 2 #截图模式0-时间间隔1-关键帧间隔 2-HTTP请求模式手动触发 snapmode: 1
timeinterval: 3s snaptimeinterval: 1s
savepath: "./snap" snapsavepath: "./snaps"
iframeinterval: 3 # 截图i帧间隔默认为3即每隔3个i帧截图一次 snapiframeinterval: 3
querytimedelta: 3 # 查询截图时允许的最大时间差(秒) snapquerytimedelta: 3 # 查询截图时允许的最大时间差(秒)
filter: "^live/.*" filter: "^live/.*"
onpub: onpub:
transform: transform:

1
go.mod
View File

@@ -102,6 +102,7 @@ require (
github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v2 v2.1.2 // indirect github.com/pion/turn/v2 v2.1.2 // indirect
github.com/pion/turn/v4 v4.0.0 // 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect

View File

@@ -17,7 +17,7 @@ snap:
snapsavepath: "snaps" # 截图保存路径 snapsavepath: "snaps" # 截图保存路径
filter: ".*" # 截图流过滤器,支持正则表达式 filter: ".*" # 截图流过滤器,支持正则表达式
snapiframeinterval: 3 # 间隔多少帧截图 snapiframeinterval: 3 # 间隔多少帧截图
snapmode: 1 # 截图模式0-时间间隔1-关键帧间隔 2-HTTP请求模式手动触发 snapmode: 1 # 截图模式0-时间间隔1-关键帧间隔
snapquerytimedelta: 3 # 查询截图时允许的最大时间差(秒) snapquerytimedelta: 3 # 查询截图时允许的最大时间差(秒)
``` ```
@@ -56,11 +56,11 @@ GET /query?streamPath={streamPath}&snapTime={timestamp}
## 截图模式说明 ## 截图模式说明
### 时间间隔模式 (snapmode: 0) ### 时间间隔模式 (snapmode: 0)
- 按照配置的 `timeinterval` 定时对流进行截图 - 按照配置的 `snaptimeinterval` 定时对流进行截图
- 适合需要固定时间间隔截图的场景 - 适合需要固定时间间隔截图的场景
### 关键帧间隔模式 (snapmode: 1) ### 关键帧间隔模式 (snapmode: 1)
- 按照配置的 `iframeinterval` 对关键帧进行截图 - 按照配置的 `snapiframeinterval` 对关键帧进行截图
- 适合需要按视频内容变化进行截图的场景 - 适合需要按视频内容变化进行截图的场景
### HTTP请求模式 (snapmode: 2) ### HTTP请求模式 (snapmode: 2)
@@ -90,15 +90,15 @@ GET /query?streamPath={streamPath}&snapTime={timestamp}
配置示例: 配置示例:
```yaml ```yaml
snap: snap:
watermark: snapwatermark:
text: "测试水印 $T{2006-01-02 15:04:05}" text: "测试水印 $T{2006-01-02 15:04:05}"
fontpath: "/path/to/font.ttf" fontpath: "/path/to/font.ttf"
fontcolor: "rgba(255,0,0,0.5)" fontcolor: "rgba(255,0,0,0.5)"
fontsize: 48 fontsize: 48
offsetx: 20 offsetx: 20
offsety: 20 offsety: 20
mode: 0 snapmode: 0
timeinterval: 1m snaptimeinterval: 1m
``` ```
## 数据库记录 ## 数据库记录
@@ -115,24 +115,24 @@ snap:
1. 基础配置示例: 1. 基础配置示例:
```yaml ```yaml
snap: snap:
timeinterval: 30s snaptimeinterval: 30s
savepath: "./snapshots" snapsavepath: "./snapshots"
mode: 1 snapmode: 1
iframeinterval: 5 snapiframeinterval: 5
``` ```
2. 带水印的配置示例: 2. 带水印的配置示例:
```yaml ```yaml
snap: snap:
watermark: snapwatermark:
text: "测试水印" text: "测试水印"
fontpath: "/path/to/font.ttf" fontpath: "/path/to/font.ttf"
fontcolor: "rgba(255,0,0,0.5)" fontcolor: "rgba(255,0,0,0.5)"
fontsize: 48 fontsize: 48
offsetx: 20 offsetx: 20
offsety: 20 offsety: 20
mode: 0 snapmode: 0
timeinterval: 1m snaptimeinterval: 1m
``` ```
3. API调用示例 3. API调用示例

View File

@@ -14,6 +14,7 @@ import (
"time" "time"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/golang/freetype/truetype"
"m7s.live/v5/pkg" "m7s.live/v5/pkg"
snap_pkg "m7s.live/v5/plugin/snap/pkg" snap_pkg "m7s.live/v5/plugin/snap/pkg"
"m7s.live/v5/plugin/snap/pkg/watermark" "m7s.live/v5/plugin/snap/pkg/watermark"
@@ -35,51 +36,79 @@ func parseRGBA(rgba string) (color.RGBA, error) {
// snap 方法负责实际的截图操作 // snap 方法负责实际的截图操作
func (p *SnapPlugin) snap(streamPath string) (*bytes.Buffer, error) { func (p *SnapPlugin) snap(streamPath string) (*bytes.Buffer, error) {
// 获取视频帧
annexb, _, err := snap_pkg.GetVideoFrame(streamPath, p.Server)
if err != nil {
return nil, err
}
// 处理视频帧生成图片
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err := snap_pkg.ProcessWithFFmpeg(annexb, buf); err != nil { //transformer := snap.NewTransform().(*snap.Transformer)
return nil, err //transformer.TransformJob.Init(transformer, &p.Plugin, streamPath, config.Transform{
} // Output: []config.TransfromOutput{
// {
// Target: streamPath,
// StreamPath: streamPath,
// Conf: buf,
// },
// },
//}).WaitStarted()
// 如果设置了水印文字,添加水印 // 如果设置了水印文字,添加水印
if p.Watermark.Text != "" && snap_pkg.GlobalWatermarkConfig.Font != nil { if p.SnapWatermark.Text != "" {
// 读取字体文件
fontBytes, err := os.ReadFile(p.SnapWatermark.FontPath)
if err != nil {
p.Error("read font file failed", "error", err.Error())
return nil, err
}
// 解析字体
font, err := truetype.Parse(fontBytes)
if err != nil {
p.Error("parse font failed", "error", err.Error())
return nil, err
}
// 解码图片 // 解码图片
img, _, err := image.Decode(bytes.NewReader(buf.Bytes())) img, _, err := image.Decode(bytes.NewReader(buf.Bytes()))
if err != nil { if err != nil {
return nil, fmt.Errorf("decode image failed: %w", err) p.Error("decode image failed", "error", err.Error())
return nil, err
}
// 解码颜色
rgba, err := parseRGBA(p.SnapWatermark.FontColor)
if err != nil {
p.Error("parse color failed", "error", err.Error())
return nil, err
}
// 确保alpha通道正确
if rgba.A == 0 {
rgba.A = 255 // 如果完全透明,改为不透明
} }
// 添加水印 // 添加水印
result, err := watermark.DrawWatermarkSingle(img, watermark.TextConfig{ result, err := watermark.DrawWatermarkSingle(img, watermark.TextConfig{
Text: snap_pkg.GlobalWatermarkConfig.Text, Text: p.SnapWatermark.Text,
Font: snap_pkg.GlobalWatermarkConfig.Font, Font: font,
FontSize: snap_pkg.GlobalWatermarkConfig.FontSize, FontSize: p.SnapWatermark.FontSize,
Spacing: snap_pkg.GlobalWatermarkConfig.FontSpacing, Spacing: 10,
RowSpacing: 10, RowSpacing: 10,
ColSpacing: 20, ColSpacing: 20,
Rows: 1, Rows: 1,
Cols: 1, Cols: 1,
DPI: 72, DPI: 72,
Color: snap_pkg.GlobalWatermarkConfig.FontColor, Color: rgba,
IsGrid: false, IsGrid: false,
Angle: 0, Angle: 0,
OffsetX: snap_pkg.GlobalWatermarkConfig.OffsetX, OffsetX: p.SnapWatermark.OffsetX,
OffsetY: snap_pkg.GlobalWatermarkConfig.OffsetY, OffsetY: p.SnapWatermark.OffsetY,
}, false) }, false)
if err != nil { if err != nil {
return nil, fmt.Errorf("add watermark failed: %w", err) p.Error("add watermark failed", "error", err.Error())
return nil, err
} }
// 清空原buffer并写入新图片 // 清空原buffer并写入新图片
buf.Reset() buf.Reset()
if err := imaging.Encode(buf, result, imaging.JPEG); err != nil { if err := imaging.Encode(buf, result, imaging.JPEG); err != nil {
return nil, fmt.Errorf("encode image failed: %w", err) p.Error("encode image failed", "error", err.Error())
return nil, err
} }
} }
@@ -94,30 +123,105 @@ func (p *SnapPlugin) doSnap(rw http.ResponseWriter, r *http.Request) {
return return
} }
// 调用 snap 进行截图 // 获取视频帧
buf, err := p.snap(streamPath) annexb, _, err := snap_pkg.GetVideoFrame(streamPath, p.Server)
if err != nil { if err != nil {
p.Error("snap failed", "error", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
return return
} }
// 处理保存逻辑 // 处理视频帧生成图片
var savePath string buf := new(bytes.Buffer)
if p.SavePath != "" && p.IsManualModeSave { if err := snap_pkg.ProcessWithFFmpeg(annexb, buf); err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
// 如果设置了水印文字,添加水印
if p.SnapWatermark.Text != "" {
// 读取字体文件
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)
return
}
// 解析字体
font, err := truetype.Parse(fontBytes)
if err != nil {
p.Error("parse font failed", "error", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
// 解码图片
img, _, err := image.Decode(bytes.NewReader(buf.Bytes()))
if err != nil {
p.Error("decode image failed", "error", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
// 解码颜色
rgba, err := parseRGBA(p.SnapWatermark.FontColor)
if err != nil {
p.Error("parse color failed", "error", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
// 确保alpha通道正确
if rgba.A == 0 {
rgba.A = 255 // 如果完全透明,改为不透明
}
// 添加水印
result, err := watermark.DrawWatermarkSingle(img, watermark.TextConfig{
Text: p.SnapWatermark.Text,
Font: font,
FontSize: p.SnapWatermark.FontSize,
Spacing: 10,
RowSpacing: 10,
ColSpacing: 20,
Rows: 1,
Cols: 1,
DPI: 72,
Color: rgba,
IsGrid: false,
Angle: 0,
OffsetX: p.SnapWatermark.OffsetX,
OffsetY: p.SnapWatermark.OffsetY,
}, false)
if err != nil {
p.Error("add watermark failed", "error", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
// 清空原buffer并写入新图片
buf.Reset()
if err := imaging.Encode(buf, result, imaging.JPEG); err != nil {
p.Error("encode image failed", "error", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
}
// 保存截图并记录到数据库
if p.DB != nil {
now := time.Now() now := time.Now()
filename := fmt.Sprintf("%s_%s.jpg", streamPath, now.Format("20060102150405.000")) filename := fmt.Sprintf("%s_%s.jpg", streamPath, now.Format("20060102150405.000"))
filename = strings.ReplaceAll(filename, "/", "_") filename = strings.ReplaceAll(filename, "/", "_")
savePath = filepath.Join(p.SavePath, filename) savePath := filepath.Join(p.SnapSavePath, filename)
// 保存到本地 if p.SnapSavePath != "" {
if err := os.WriteFile(savePath, buf.Bytes(), 0644); err != nil { // 保存到本地
p.Error("save snapshot failed", "error", err.Error()) err = os.WriteFile(savePath, buf.Bytes(), 0644)
savePath = "" if err != nil {
} p.Error("save snapshot failed", "error", err.Error())
savePath = ""
// 保存截图记录到数据库 }
if p.DB != nil && savePath != "" { // 保存记录到数据库
record := snap_pkg.SnapRecord{ record := snap_pkg.SnapRecord{
StreamName: streamPath, StreamName: streamPath,
SnapMode: 2, // HTTP请求截图模式 SnapMode: 2, // HTTP请求截图模式
@@ -128,13 +232,15 @@ func (p *SnapPlugin) doSnap(rw http.ResponseWriter, r *http.Request) {
p.Error("save snapshot record failed", "error", err.Error()) p.Error("save snapshot record failed", "error", err.Error())
} }
} }
} }
// 返回图片
rw.Header().Set("Content-Type", "image/jpeg") rw.Header().Set("Content-Type", "image/jpeg")
rw.Header().Set("Content-Length", strconv.Itoa(buf.Len())) rw.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
if _, err := buf.WriteTo(rw); err != nil { if _, err := buf.WriteTo(rw); err != nil {
p.Error("write response failed", "error", err.Error()) p.Error("write response failed", "error", err.Error())
return
} }
} }
@@ -175,7 +281,7 @@ func (p *SnapPlugin) querySnap(rw http.ResponseWriter, r *http.Request) {
// 计算时间差(秒) // 计算时间差(秒)
timeDiff := targetTime.Sub(record.SnapTime).Seconds() 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) http.Error(rw, "no snapshot found within time delta", http.StatusNotFound)
return return
} }

View File

@@ -4,11 +4,11 @@ import (
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
"image/color" "image/color"
snap_pkg "m7s.live/v5/plugin/snap/pkg" snap_pkg "m7s.live/v5/plugin/snap/pkg"
m7s "m7s.live/v5" m7s "m7s.live/v5"
@@ -19,50 +19,34 @@ var _ = m7s.InstallPlugin[SnapPlugin](snap.NewTransform)
type SnapPlugin struct { type SnapPlugin struct {
m7s.Plugin m7s.Plugin
Watermark struct { SnapWatermark struct {
Text string `default:"" desc:"水印文字内容"` Text string `default:"" desc:"水印文字内容"`
FontPath string `default:"" desc:"水印字体文件路径"` FontPath string `default:"" desc:"水印字体文件路径"`
FontColor string `default:"rgba(255,165,0,1)" desc:"水印字体颜色支持rgba格式"` FontColor string `default:"rgba(255,165,0,1)" desc:"水印字体颜色支持rgba格式"`
FontSize float64 `default:"36" desc:"水印字体大小"` FontSize float64 `default:"36" desc:"水印字体大小"`
FontSpacing float64 `default:"2" desc:"水印字体间距"` OffsetX int `default:"0" desc:"水印位置X"`
OffsetX int `default:"0" desc:"水印位置X"` OffsetY int `default:"0" desc:"水印位置Y"`
OffsetY int `default:"0" desc:"水印位置Y"`
} `desc:"水印配置"` } `desc:"水印配置"`
// 定时任务相关配置 // 定时任务相关配置
TimeInterval time.Duration `default:"1m" desc:"截图间隔"` SnapTimeInterval time.Duration `default:"1m" desc:"截图间隔"`
SavePath string `default:"snaps" desc:"截图保存路径"` SnapSavePath string `default:"snaps" desc:"截图保存路径"`
Filter string `default:".*" desc:"截图流过滤器,支持正则表达式"` Filter string `default:".*" desc:"截图流过滤器,支持正则表达式"`
IFrameInterval int `default:"3" desc:"间隔多少帧截图"` SnapIFrameInterval int `default:"3" desc:"间隔多少帧截图"`
Mode int `default:"1" desc:"截图模式 0:间隔时间 1:间隔关键帧"` SnapMode int `default:"1" desc:"截图模式 0:间隔时间 1:间隔关键帧"`
QueryTimeDelta int `default:"3" desc:"查询截图时允许的最大时间差(秒)"` SnapQueryTimeDelta int `default:"3" desc:"查询截图时允许的最大时间差(秒)"`
IsManualModeSave bool `default:"false" desc:"手动截图是否保存文件"` SnapSaveManual bool `default:"false" desc:"手动截图是否保存文件"`
filterRegex *regexp.Regexp filterRegex *regexp.Regexp
} }
// OnInit 在插件初始化时添加定时任务 // OnInit 在插件初始化时添加定时任务
func (p *SnapPlugin) OnInit() (err error) { func (p *SnapPlugin) OnInit() (err error) {
// 检查 Mode 的值范围 // 检查 SnapMode 的值范围
if p.Mode < snap.SnapModeTimeInterval || p.Mode > snap.SnapModeManual { if p.SnapMode < 0 || p.SnapMode > 1 {
p.Error("invalid snap mode", p.Error("invalid snap mode",
"mode", p.Mode, "mode", p.SnapMode,
"valid_range", "0-1", "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)
}
// 检查 interval 是否大于0
if p.TimeInterval < 0 {
p.Error("invalid snap time interval",
"interval", p.TimeInterval,
"valid_range", ">=0",
)
return fmt.Errorf("invalid snap time interval: %d, valid range is >=0", p.TimeInterval)
}
if p.IFrameInterval < 0 {
p.Error("invalid snap i-frame interval",
"interval", p.IFrameInterval,
"valid_range", ">=0",
)
return fmt.Errorf("invalid snap i-frame interval: %d, valid range is >=0", p.IFrameInterval)
} }
// 初始化数据库 // 初始化数据库
@@ -75,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 return
} }
@@ -87,35 +71,32 @@ func (p *SnapPlugin) OnInit() (err error) {
// 初始化全局水印配置 // 初始化全局水印配置
snap.GlobalWatermarkConfig = snap.WatermarkConfig{ snap.GlobalWatermarkConfig = snap.WatermarkConfig{
Text: p.Watermark.Text, Text: p.SnapWatermark.Text,
FontPath: p.Watermark.FontPath, FontPath: p.SnapWatermark.FontPath,
FontSize: p.Watermark.FontSize, FontSize: p.SnapWatermark.FontSize,
FontSpacing: p.Watermark.FontSpacing, FontColor: color.RGBA{}, // 将在下面解析
FontColor: color.RGBA{}, // 将在下面解析 OffsetX: p.SnapWatermark.OffsetX,
OffsetX: p.Watermark.OffsetX, OffsetY: p.SnapWatermark.OffsetY,
OffsetY: p.Watermark.OffsetY,
} }
if p.Watermark.Text != "" { if p.SnapWatermark.Text != "" {
// 判断字体是否存在 // 判断字体是否存在
if _, err := os.Stat(p.Watermark.FontPath); os.IsNotExist(err) { if _, err := os.Stat(p.SnapWatermark.FontPath); os.IsNotExist(err) {
p.Error("watermark font file not found", "path", p.Watermark.FontPath) p.Error("watermark font file not found", "path", p.SnapWatermark.FontPath)
return fmt.Errorf("watermark font file not found: %w", err) return fmt.Errorf("watermark font file not found: %w", err)
} }
// 解析颜色 // 解析颜色
if p.Watermark.FontColor != "" { if p.SnapWatermark.FontColor != "" {
rgba := p.Watermark.FontColor rgba := p.SnapWatermark.FontColor
rgba = strings.TrimPrefix(rgba, "rgba(") rgba = strings.TrimPrefix(rgba, "rgba(")
rgba = strings.TrimSuffix(rgba, ")") rgba = strings.TrimSuffix(rgba, ")")
parts := strings.Split(rgba, ",") parts := strings.Split(rgba, ",")
if len(parts) == 4 { if len(parts) == 4 {
fontColor, err := parseRGBA(p.Watermark.FontColor) r, _ := strconv.Atoi(strings.TrimSpace(parts[0]))
if err == nil { g, _ := strconv.Atoi(strings.TrimSpace(parts[1]))
snap.GlobalWatermarkConfig.FontColor = fontColor b, _ := strconv.Atoi(strings.TrimSpace(parts[2]))
} else { a, _ := strconv.ParseFloat(strings.TrimSpace(parts[3]), 64)
p.Error("parse color failed", "error", err.Error()) snap.GlobalWatermarkConfig.FontColor = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a * 255)}
snap.GlobalWatermarkConfig.FontColor = color.RGBA{uint8(255), uint8(255), uint8(255), uint8(255)}
}
} }
} }
} }
@@ -137,18 +118,18 @@ func (p *SnapPlugin) OnInit() (err error) {
} }
//如果截图模式不是时间模式,则不加定时任务 //如果截图模式不是时间模式,则不加定时任务
if p.Mode != snap_pkg.SnapModeTimeInterval { if p.SnapMode != 0 {
return return
} }
// 如果间隔时间小于0则不添加定时任务;等于0则走onpub的transform // 如果间隔时间小于0则不添加定时任务;等于0则走onpub的transform
if p.TimeInterval <= 0 { if p.SnapTimeInterval <= 0 {
return return
} }
// 添加定时任务 // 添加定时任务
p.AddTask(&SnapTimerTask{ p.AddTask(&SnapTimerTask{
Interval: p.TimeInterval, Interval: p.SnapTimeInterval,
SavePath: p.SavePath, SavePath: p.SnapSavePath,
Plugin: p, Plugin: p,
}) })

View File

@@ -17,12 +17,6 @@ import (
"m7s.live/v5/pkg/task" "m7s.live/v5/pkg/task"
) )
const (
SnapModeTimeInterval = iota
SnapModeIFrameInterval
SnapModeManual
)
// GetVideoFrame 获取视频帧数据 // GetVideoFrame 获取视频帧数据
func GetVideoFrame(streamPath string, server *m7s.Server) (pkg.AnnexB, *pkg.AVTrack, error) { func GetVideoFrame(streamPath string, server *m7s.Server) (pkg.AnnexB, *pkg.AVTrack, error) {
// 获取发布者 // 获取发布者
@@ -162,34 +156,34 @@ type Transformer struct {
func (t *Transformer) Start() (err error) { func (t *Transformer) Start() (err error) {
// 获取配置,带默认值检查 // 获取配置,带默认值检查
if t.TransformJob.Plugin.Config.Has("TimeInterval") { if t.TransformJob.Plugin.Config.Has("SnapTimeInterval") {
t.snapTimeInterval = t.TransformJob.Plugin.Config.Get("TimeInterval").GetValue().(time.Duration) t.snapTimeInterval = t.TransformJob.Plugin.Config.Get("SnapTimeInterval").GetValue().(time.Duration)
} else { } else {
t.snapTimeInterval = time.Minute // 默认1分钟 t.snapTimeInterval = time.Minute // 默认1分钟
} }
if t.TransformJob.Plugin.Config.Has("SavePath") { if t.TransformJob.Plugin.Config.Has("SnapSavePath") {
t.savePath = t.TransformJob.Plugin.Config.Get("SavePath").GetValue().(string) t.savePath = t.TransformJob.Plugin.Config.Get("SnapSavePath").GetValue().(string)
} else { } else {
t.savePath = "snaps" // 默认保存路径 t.savePath = "snaps" // 默认保存路径
} }
if t.TransformJob.Plugin.Config.Has("Mode") { if t.TransformJob.Plugin.Config.Has("SnapMode") {
t.snapMode = t.TransformJob.Plugin.Config.Get("Mode").GetValue().(int) t.snapMode = t.TransformJob.Plugin.Config.Get("SnapMode").GetValue().(int)
} else { } else {
t.snapMode = SnapModeIFrameInterval // 默认使用关键帧模式 t.snapMode = 1 // 默认使用关键帧模式
} }
// 检查snapmode是否有效 // 检查snapmode是否有效
if t.snapMode != SnapModeIFrameInterval && t.snapMode != SnapModeTimeInterval { if t.snapMode != 0 && t.snapMode != 1 {
t.Debug("invalid snap mode, skip snapshot", t.Debug("invalid snap mode, skip snapshot",
"mode", t.snapMode, "mode", t.snapMode,
) )
return nil return nil
} }
if t.TransformJob.Plugin.Config.Has("IFrameInterval") { if t.TransformJob.Plugin.Config.Has("SnapIFrameInterval") {
t.snapFrameInterval = t.TransformJob.Plugin.Config.Get("IFrameInterval").GetValue().(int) t.snapFrameInterval = t.TransformJob.Plugin.Config.Get("SnapIFrameInterval").GetValue().(int)
} else { } else {
t.snapFrameInterval = 3 // 默认每3个I帧截图一次 t.snapFrameInterval = 3 // 默认每3个I帧截图一次
} }
@@ -215,7 +209,7 @@ func (t *Transformer) Start() (err error) {
} }
// 如果是时间间隔模式且间隔时间不为0则跳过订阅模式 // 如果是时间间隔模式且间隔时间不为0则跳过订阅模式
if t.snapMode == SnapModeTimeInterval && t.snapTimeInterval != 0 { if t.snapMode == 0 && t.snapTimeInterval != 0 {
t.Info("snap interval is set, skipping subscriber mode", t.Info("snap interval is set, skipping subscriber mode",
"interval", t.snapTimeInterval, "interval", t.snapTimeInterval,
"save_path", t.savePath, "save_path", t.savePath,
@@ -228,20 +222,6 @@ func (t *Transformer) Start() (err error) {
} }
func (t *Transformer) Go() error { func (t *Transformer) Go() error {
// 检查snapmode是否有效
if t.snapMode != SnapModeIFrameInterval && t.snapMode != SnapModeTimeInterval {
t.Debug("invalid snap mode, skip snapshot",
"mode", t.snapMode,
)
return nil
}
if t.snapMode == SnapModeTimeInterval && t.snapTimeInterval != 0 {
t.Info("snap interval is set, skipping subscriber mode",
"interval", t.snapTimeInterval,
"save_path", t.savePath,
)
return nil
}
// 1. 通过 TransformJob 获取 Subscriber // 1. 通过 TransformJob 获取 Subscriber
subscriber := t.TransformJob.Subscriber subscriber := t.TransformJob.Subscriber

View File

@@ -22,19 +22,18 @@ var (
// WatermarkConfig 水印配置 // WatermarkConfig 水印配置
type WatermarkConfig struct { type WatermarkConfig struct {
Text string // 水印文字 Text string // 水印文字
FontPath string // 字体文件路径 FontPath string // 字体文件路径
FontSize float64 // 字体大小 FontSize float64 // 字体大小
FontColor color.RGBA // 字体颜色 FontColor color.RGBA // 字体颜色
FontSpacing float64 // 字体间距 OffsetX int // X轴偏移
OffsetX int // X轴偏移 OffsetY int // Y轴偏移
OffsetY int // Y轴偏移 font *truetype.Font // 缓存的字体对象
Font *truetype.Font // 缓存的字体对象
} }
// LoadFont 加载字体文件 // LoadFont 加载字体文件
func (w *WatermarkConfig) LoadFont() error { func (w *WatermarkConfig) LoadFont() error {
if w.Font != nil { if w.font != nil {
return nil return nil
} }
@@ -43,7 +42,7 @@ func (w *WatermarkConfig) LoadFont() error {
fontCacheLock.RUnlock() fontCacheLock.RUnlock()
if exists { if exists {
w.Font = cachedFont w.font = cachedFont
return nil return nil
} }
@@ -63,7 +62,7 @@ func (w *WatermarkConfig) LoadFont() error {
fontCache[w.FontPath] = font fontCache[w.FontPath] = font
fontCacheLock.Unlock() fontCacheLock.Unlock()
w.Font = font w.font = font
return nil return nil
} }
@@ -92,7 +91,7 @@ func AddWatermark(imgData []byte, config WatermarkConfig) ([]byte, error) {
// 添加水印 // 添加水印
result, err := watermark.DrawWatermarkSingle(img, watermark.TextConfig{ result, err := watermark.DrawWatermarkSingle(img, watermark.TextConfig{
Text: config.Text, Text: config.Text,
Font: config.Font, Font: config.font,
FontSize: config.FontSize, FontSize: config.FontSize,
Spacing: 10, Spacing: 10,
RowSpacing: 10, RowSpacing: 10,

View File

@@ -57,7 +57,7 @@ func (t *SnapTimerTask) Tick(any) {
if t.Plugin.DB != nil { if t.Plugin.DB != nil {
record := snap_pkg.SnapRecord{ record := snap_pkg.SnapRecord{
StreamName: streamPath, StreamName: streamPath,
SnapMode: t.Plugin.Mode, SnapMode: t.Plugin.SnapMode,
SnapTime: now, SnapTime: now,
SnapPath: savePath, SnapPath: savePath,
} }