fix: snap use global config to reduce font memory

This commit is contained in:
banshan
2025-01-05 15:56:45 +08:00
parent 5a2af0e7fd
commit df348e6946
3 changed files with 53 additions and 154 deletions

View File

@@ -14,7 +14,6 @@ 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"
@@ -36,79 +35,51 @@ 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)
//transformer := snap.NewTransform().(*snap.Transformer) if err := snap_pkg.ProcessWithFFmpeg(annexb, buf); err != nil {
//transformer.TransformJob.Init(transformer, &p.Plugin, streamPath, config.Transform{ return nil, err
// Output: []config.TransfromOutput{ }
// {
// Target: streamPath,
// StreamPath: streamPath,
// Conf: buf,
// },
// },
//}).WaitStarted()
// 如果设置了水印文字,添加水印 // 如果设置了水印文字,添加水印
if p.Watermark.Text != "" { if p.Watermark.Text != "" && snap_pkg.GlobalWatermarkConfig.Font != nil {
// 读取字体文件
fontBytes, err := os.ReadFile(p.Watermark.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 {
p.Error("decode image failed", "error", err.Error()) return nil, fmt.Errorf("decode image failed: %w", err)
return nil, err
}
// 解码颜色
rgba, err := parseRGBA(p.Watermark.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: p.Watermark.Text, Text: snap_pkg.GlobalWatermarkConfig.Text,
Font: font, Font: snap_pkg.GlobalWatermarkConfig.Font,
FontSize: p.Watermark.FontSize, FontSize: snap_pkg.GlobalWatermarkConfig.FontSize,
Spacing: 10, Spacing: 2,
RowSpacing: 10, RowSpacing: 10,
ColSpacing: 20, ColSpacing: 20,
Rows: 1, Rows: 1,
Cols: 1, Cols: 1,
DPI: 72, DPI: 72,
Color: rgba, Color: snap_pkg.GlobalWatermarkConfig.FontColor,
IsGrid: false, IsGrid: false,
Angle: 0, Angle: 0,
OffsetX: p.Watermark.OffsetX, OffsetX: snap_pkg.GlobalWatermarkConfig.OffsetX,
OffsetY: p.Watermark.OffsetY, OffsetY: snap_pkg.GlobalWatermarkConfig.OffsetY,
}, false) }, false)
if err != nil { if err != nil {
p.Error("add watermark failed", "error", err.Error()) return nil, fmt.Errorf("add watermark failed: %w", err)
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 {
p.Error("encode image failed", "error", err.Error()) return nil, fmt.Errorf("encode image failed: %w", err)
return nil, err
} }
} }
@@ -123,108 +94,30 @@ func (p *SnapPlugin) doSnap(rw http.ResponseWriter, r *http.Request) {
return return
} }
// 获取视频帧 // 调用 snap 进行截图
annexb, _, err := snap_pkg.GetVideoFrame(streamPath, p.Server) buf, err := p.snap(streamPath)
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
} }
// 处理视频帧生成图片 // 处理保存逻辑
buf := new(bytes.Buffer) var savePath string
if err := snap_pkg.ProcessWithFFmpeg(annexb, buf); err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
// 如果设置了水印文字,添加水印
if p.Watermark.Text != "" {
// 读取字体文件
fontBytes, err := os.ReadFile(p.Watermark.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.Watermark.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.Watermark.Text,
Font: font,
FontSize: p.Watermark.FontSize,
Spacing: 10,
RowSpacing: 10,
ColSpacing: 20,
Rows: 1,
Cols: 1,
DPI: 72,
Color: rgba,
IsGrid: false,
Angle: 0,
OffsetX: p.Watermark.OffsetX,
OffsetY: p.Watermark.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.SavePath != "" && p.IsManualModeSave { if p.SavePath != "" && p.IsManualModeSave {
//判断 SavePath 是否存在
if _, err := os.Stat(p.SavePath); os.IsNotExist(err) {
os.MkdirAll(p.SavePath, 0755)
}
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.SavePath, filename)
// 保存到本地 // 保存到本地
err = os.WriteFile(savePath, buf.Bytes(), 0644) if err := os.WriteFile(savePath, buf.Bytes(), 0644); err != nil {
if err != nil {
p.Error("save snapshot failed", "error", err.Error()) p.Error("save snapshot failed", "error", err.Error())
savePath = "" savePath = ""
} }
// 保存截图并记录到数据库
if p.DB != nil { // 保存截图记录到数据库
// 保存记录到数据库 if p.DB != nil && savePath != "" {
record := snap_pkg.SnapRecord{ record := snap_pkg.SnapRecord{
StreamName: streamPath, StreamName: streamPath,
SnapMode: 2, // HTTP请求截图模式 SnapMode: 2, // HTTP请求截图模式
@@ -235,15 +128,13 @@ 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
} }
} }

View File

@@ -4,17 +4,23 @@ 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"
snap "m7s.live/v5/plugin/snap/pkg" snap "m7s.live/v5/plugin/snap/pkg"
) )
const (
SnapModeTimeInterval = iota
SnapModeIFrameInterval
SnapModeManual
)
var _ = m7s.InstallPlugin[SnapPlugin](snap.NewTransform) var _ = m7s.InstallPlugin[SnapPlugin](snap.NewTransform)
type SnapPlugin struct { type SnapPlugin struct {
@@ -41,7 +47,7 @@ type SnapPlugin struct {
// OnInit 在插件初始化时添加定时任务 // OnInit 在插件初始化时添加定时任务
func (p *SnapPlugin) OnInit() (err error) { func (p *SnapPlugin) OnInit() (err error) {
// 检查 Mode 的值范围 // 检查 Mode 的值范围
if p.Mode < 0 || p.Mode > 1 { if p.Mode < SnapModeTimeInterval || p.Mode > SnapModeManual {
p.Error("invalid snap mode", p.Error("invalid snap mode",
"mode", p.Mode, "mode", p.Mode,
"valid_range", "0-1", "valid_range", "0-1",
@@ -92,11 +98,13 @@ func (p *SnapPlugin) OnInit() (err error) {
rgba = strings.TrimSuffix(rgba, ")") rgba = strings.TrimSuffix(rgba, ")")
parts := strings.Split(rgba, ",") parts := strings.Split(rgba, ",")
if len(parts) == 4 { if len(parts) == 4 {
r, _ := strconv.Atoi(strings.TrimSpace(parts[0])) fontColor, err := parseRGBA(p.Watermark.FontColor)
g, _ := strconv.Atoi(strings.TrimSpace(parts[1])) if err == nil {
b, _ := strconv.Atoi(strings.TrimSpace(parts[2])) snap.GlobalWatermarkConfig.FontColor = fontColor
a, _ := strconv.ParseFloat(strings.TrimSpace(parts[3]), 64) } else {
snap.GlobalWatermarkConfig.FontColor = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a * 255)} p.Error("parse color failed", "error", err.Error())
snap.GlobalWatermarkConfig.FontColor = color.RGBA{uint8(255), uint8(255), uint8(255), uint8(255)}
}
} }
} }
} }

View File

@@ -28,12 +28,12 @@ type WatermarkConfig struct {
FontColor color.RGBA // 字体颜色 FontColor color.RGBA // 字体颜色
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
} }
@@ -42,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
} }
@@ -62,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
} }
@@ -91,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,