mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-10-07 07:00:52 +08:00
封装转码请求-2
This commit is contained in:
@@ -12,7 +12,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"m7s.live/m7s/v5/pkg/config"
|
"m7s.live/m7s/v5/pkg/config"
|
||||||
transcode "m7s.live/m7s/v5/plugin/transcode/pkg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OverlayConfig struct {
|
type OverlayConfig struct {
|
||||||
@@ -21,10 +20,12 @@ type OverlayConfig struct {
|
|||||||
OverlayImage string `json:"image"` // 图片 base64 可为空 如果图片和视频流都有,则使用图片
|
OverlayImage string `json:"image"` // 图片 base64 可为空 如果图片和视频流都有,则使用图片
|
||||||
OverlayPosition string `json:"overlay_position"` //位置 x,y
|
OverlayPosition string `json:"overlay_position"` //位置 x,y
|
||||||
Text string `json:"text"` // 文字
|
Text string `json:"text"` // 文字
|
||||||
|
TimeOffset int64 `json:"time_offset"` // 时间偏移
|
||||||
|
TimeFormat string `json:"time_format"` // 时间格式
|
||||||
FontName string `json:"font_name"` //字体文件名
|
FontName string `json:"font_name"` //字体文件名
|
||||||
FontSize int `json:"font_size"`
|
FontSize string `json:"font_size"` //字体大小
|
||||||
FontColor string `json:"font_color"` // r,g,b 颜色
|
FontColor string `json:"font_color"` // r,g,b 颜色
|
||||||
TextPosition string `json:"text_position"` //x,y 文字在图片上的位置
|
TextPosition string `json:"text_position"` //x,y 文字在图片上的位置
|
||||||
imagePath string `json:"-"`
|
imagePath string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,24 +73,54 @@ func createTmpImage(image string) (string, error) {
|
|||||||
return filePath, nil
|
return filePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rgbToHex(FontColor string) (string, error) {
|
func parseFontColor(FontColor string) (string, error) {
|
||||||
rgb := strings.Split(FontColor, ",")
|
rgb := strings.Split(FontColor, ",")
|
||||||
if len(rgb) == 3 {
|
rgbLen := len(rgb)
|
||||||
|
switch rgbLen {
|
||||||
|
case 3:
|
||||||
r, _ := strconv.Atoi(rgb[0])
|
r, _ := strconv.Atoi(rgb[0])
|
||||||
g, _ := strconv.Atoi(rgb[1])
|
g, _ := strconv.Atoi(rgb[1])
|
||||||
b, _ := strconv.Atoi(rgb[2])
|
b, _ := strconv.Atoi(rgb[2])
|
||||||
FontColor = fmt.Sprintf("#%02x%02x%02x", r, g, b)
|
FontColor = fmt.Sprintf(":fontcolor=#%02x%02x%02x", r, g, b)
|
||||||
return FontColor, nil
|
return FontColor, nil
|
||||||
} else {
|
case 0:
|
||||||
|
FontColor = ":fontcolor=black"
|
||||||
|
return FontColor, nil
|
||||||
|
default:
|
||||||
return "", fmt.Errorf("FontColor 格式不正确")
|
return "", fmt.Errorf("FontColor 格式不正确")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fontfile
|
||||||
|
func parseFontFile(fontFile string) (string, error) {
|
||||||
|
if fontFile == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
//判断文件是否存在
|
||||||
|
if _, err := os.Stat(fontFile); os.IsNotExist(err) {
|
||||||
|
return "", fmt.Errorf("fontFile 文件不存在")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(":fontfile=%s", fontFile), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fontsize
|
||||||
|
func parseFontSize(fontSize string) (string, error) {
|
||||||
|
if fontSize == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
size, err := strconv.Atoi(fontSize)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("fontSize 格式不正确")
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return "", fmt.Errorf("fontSize 不能小于0")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(":fontsize=%d", size), nil
|
||||||
|
}
|
||||||
func parseCoordinates(coordString string) (string, error) {
|
func parseCoordinates(coordString string) (string, error) {
|
||||||
|
|
||||||
if coordString == "" {
|
if coordString == "" {
|
||||||
return "", nil
|
return "x=0:y=0", nil
|
||||||
}
|
}
|
||||||
coords := strings.Split(coordString, ",")
|
coords := strings.Split(coordString, ",")
|
||||||
|
|
||||||
@@ -123,9 +154,12 @@ func (t *TranscodePlugin) api_transcode_start(w http.ResponseWriter, r *http.Req
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var inputs []string
|
inputs := []string{""}
|
||||||
var filters string
|
var filters []string
|
||||||
|
lastOverlay := "[0:v]"
|
||||||
|
out := ""
|
||||||
//循环判断
|
//循环判断
|
||||||
|
var vIdx = 0
|
||||||
for _, overlayConfig := range transReq.OverlayConfigs {
|
for _, overlayConfig := range transReq.OverlayConfigs {
|
||||||
if overlayConfig.OverlayImage == "" && overlayConfig.Text == "" && overlayConfig.OverlayStream == "" {
|
if overlayConfig.OverlayImage == "" && overlayConfig.Text == "" && overlayConfig.OverlayStream == "" {
|
||||||
http.Error(w, "image_base64 and text is required", http.StatusBadRequest)
|
http.Error(w, "image_base64 and text is required", http.StatusBadRequest)
|
||||||
@@ -139,11 +173,22 @@ func (t *TranscodePlugin) api_transcode_start(w http.ResponseWriter, r *http.Req
|
|||||||
overlayConfig.imagePath = filePath
|
overlayConfig.imagePath = filePath
|
||||||
|
|
||||||
// 将 r,g,b 颜色字符串转换为十六进制颜色
|
// 将 r,g,b 颜色字符串转换为十六进制颜色
|
||||||
overlayConfig.FontColor, err = rgbToHex(overlayConfig.FontColor)
|
overlayConfig.FontColor, err = parseFontColor(overlayConfig.FontColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "FontColor 格式不正确", http.StatusBadRequest)
|
http.Error(w, "FontColor 格式不正确", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
overlayConfig.FontName, err = parseFontFile(overlayConfig.FontName)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 字体大小
|
||||||
|
overlayConfig.FontSize, err = parseFontSize(overlayConfig.FontSize)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "FontSize 格式不正确", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//坐标
|
//坐标
|
||||||
overlayConfig.OverlayPosition, err = parseCoordinates(overlayConfig.OverlayPosition)
|
overlayConfig.OverlayPosition, err = parseCoordinates(overlayConfig.OverlayPosition)
|
||||||
@@ -161,25 +206,49 @@ func (t *TranscodePlugin) api_transcode_start(w http.ResponseWriter, r *http.Req
|
|||||||
http.Error(w, "TextPosition 格式不正确", http.StatusBadRequest)
|
http.Error(w, "TextPosition 格式不正确", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
overlayConfig.TextPosition = ":" + overlayConfig.TextPosition
|
||||||
|
//[1:v]crop=400:300:10:10[overlay];
|
||||||
if overlayConfig.imagePath != "" {
|
if overlayConfig.imagePath != "" {
|
||||||
inputs = append(inputs, overlayConfig.imagePath)
|
inputs = append(inputs, overlayConfig.imagePath)
|
||||||
} else if overlayConfig.OverlayStream != "" {
|
} else if overlayConfig.OverlayStream != "" {
|
||||||
inputs = append(inputs, overlayConfig.OverlayStream)
|
inputs = append(inputs, overlayConfig.OverlayStream)
|
||||||
}
|
}
|
||||||
filters += ""
|
// 生成 filter_complex
|
||||||
|
if overlayConfig.imagePath != "" || overlayConfig.OverlayStream != "" {
|
||||||
|
vIdx++
|
||||||
|
if overlayConfig.OverlayRegion != "" {
|
||||||
|
filters = append(filters, fmt.Sprintf("[%d:v]%s[overlay%d];", vIdx, overlayConfig.OverlayRegion, vIdx))
|
||||||
|
}
|
||||||
|
if overlayConfig.OverlayPosition != "" {
|
||||||
|
if overlayConfig.OverlayRegion != "" {
|
||||||
|
filters = append(filters, fmt.Sprintf("%s[overlay%d]overlay=%s[tmp%d]", lastOverlay, vIdx, overlayConfig.OverlayPosition, vIdx))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
filters = append(filters, fmt.Sprintf("%s[%d:v]overlay=%s[tmp%d]", lastOverlay, vIdx, overlayConfig.OverlayPosition, vIdx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastOverlay = fmt.Sprintf("[tmp%d]", vIdx)
|
||||||
|
out = lastOverlay
|
||||||
|
}
|
||||||
|
if overlayConfig.Text != "" {
|
||||||
|
text := overlayConfig.Text
|
||||||
|
if overlayConfig.TimeOffset != 0 {
|
||||||
|
text = fmt.Sprintf("%%{pts+%d} %s", overlayConfig.TimeOffset, text)
|
||||||
|
}
|
||||||
|
if overlayConfig.TimeFormat != "" {
|
||||||
|
text = fmt.Sprintf("%%{pts:%s} %s", overlayConfig.TimeFormat, text)
|
||||||
|
}
|
||||||
|
filters = append(filters, fmt.Sprintf("[tmp%d]drawtext=text='%s'%s%s%s%s[out%d]", vIdx, text, overlayConfig.FontName, overlayConfig.FontSize, overlayConfig.FontColor, overlayConfig.TextPosition, vIdx))
|
||||||
|
out = fmt.Sprintf("[out%d]", vIdx)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//ffmpeg -i input_video.mp4 -i input_image.png -filter_complex "[1:v]drawtext=fontfile=/path/to/font.ttf:fontsize=24:fontcolor=white:x=10:y=10:text='Your Text Here'[img];[0:v][img]overlay=x=100:y=100:enable='between(t,0,5)'" output_video.mp4
|
//把 overlayconfig 转为
|
||||||
|
|
||||||
// trans := transcode.NewTransform()
|
// transformer := t.Meta.Transformer()
|
||||||
// trans.(*transcode.Transformer).Start()
|
|
||||||
|
|
||||||
//把 overlayconfig 转为 filter_complex
|
// transcode := transformer.(*transcode.Transformer)
|
||||||
|
|
||||||
transformer := t.Meta.Transformer()
|
|
||||||
|
|
||||||
transcode := transformer.(*transcode.Transformer)
|
|
||||||
var cfg config.Transform
|
var cfg config.Transform
|
||||||
|
|
||||||
// 解析URL路径
|
// 解析URL路径
|
||||||
@@ -195,16 +264,18 @@ func (t *TranscodePlugin) api_transcode_start(w http.ResponseWriter, r *http.Req
|
|||||||
// 去掉开头的斜杠
|
// 去掉开头的斜杠
|
||||||
streamPath = strings.TrimPrefix(streamPath, "/")
|
streamPath = strings.TrimPrefix(streamPath, "/")
|
||||||
|
|
||||||
conf := strings.Join(inputs, " ") + filters + transReq.Scale + transReq.Encodec
|
conf := strings.Join(inputs, " -i ") + fmt.Sprintf(" -filter_complex %s ", strings.Join(filters, ";")) + fmt.Sprintf(" -map %s ", out) + transReq.Scale + transReq.Decodec
|
||||||
cfg.Output = []config.TransfromOutput{
|
cfg.Output = []config.TransfromOutput{
|
||||||
{
|
{
|
||||||
Target: targetURL,
|
Target: targetURL,
|
||||||
StreamPath: streamPath,
|
StreamPath: streamPath,
|
||||||
Conf: conf,
|
//Conf: "-log_level debug -c:v copy -an",
|
||||||
|
Conf: conf,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Transform(transReq.SrcStream, cfg)
|
t.Transform(transReq.SrcStream, cfg)
|
||||||
fmt.Println(transcode, cfg)
|
// fmt.Println(transcode, cfg)
|
||||||
|
// fmt.Println(conf)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
}
|
}
|
||||||
|
102
plugin/transcode/api_test.go
Normal file
102
plugin/transcode/api_test.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package plugin_transcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseCoordinates(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
coordString string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
{
|
||||||
|
name: "test1",
|
||||||
|
args: args{
|
||||||
|
coordString: "100,100",
|
||||||
|
},
|
||||||
|
want: "x=100:y=100",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseCoordinates(tt.args.coordString)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseCoordinates() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("parseCoordinates() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseCrop(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
cropString string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test1",
|
||||||
|
args: args{
|
||||||
|
cropString: "100,100,200,200",
|
||||||
|
},
|
||||||
|
want: "crop=100:100:200:200",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseCrop(tt.args.cropString)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseCrop() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("parseCrop() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_rgbToHex(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
FontColor string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test1",
|
||||||
|
args: args{
|
||||||
|
FontColor: "255,255,255",
|
||||||
|
},
|
||||||
|
want: "#ffffff",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseFontColor(tt.args.FontColor)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("rgbToHex() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("rgbToHex() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user