Commit f5366453 authored by 贾浩@五瓣科技's avatar 贾浩@五瓣科技

update

parent 7fb9462f
module watermark
go 1.21.4
require (
github.com/fogleman/gg v1.3.0
golang.org/x/image v0.20.0
)
require github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
package main
import (
"image"
"strings"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
_ "golang.org/x/image/webp"
"github.com/fogleman/gg"
)
func drawMultilineText(dc *gg.Context, text string, x, y float64, width, lineHeight float64, horizontalAlign, verticalAlign float64) {
lines := wordWrap(dc, text, width)
totalHeight := float64(len(lines)) * lineHeight
// 调整 y 坐标以实现垂直对齐
y -= totalHeight * verticalAlign
for _, line := range lines {
dc.DrawStringAnchored(line, x, y, horizontalAlign, 0)
y += lineHeight
}
}
func wordWrap(dc *gg.Context, text string, width float64) []string {
var lines []string
words := strings.Fields(text)
if len(words) == 0 {
return lines
}
var line string
for _, word := range words {
testLine := line
if testLine != "" {
testLine += " "
}
testLine += word
w, _ := dc.MeasureString(testLine)
if w > width {
if line == "" {
lines = append(lines, word)
} else {
lines = append(lines, line)
line = word
}
} else {
line = testLine
}
}
if line != "" {
lines = append(lines, line)
}
return lines
}
func getLines(text string, width int, fontsize int, chinese bool) (lines int, err error) {
dc := gg.NewContext(width, 100)
fontPath := "./en.ttf"
if chinese {
fontPath = "./zh.ttf"
}
err = dc.LoadFontFace(fontPath, float64(fontsize))
if err != nil {
return
}
return len(wordWrap(dc, text, float64(width))), nil
}
func drawText(img image.Image, text string, fontsize int, chinese bool) (imgOut *gg.Context, overHeight int, err error) {
width := img.Bounds().Max.X
height := img.Bounds().Max.Y
lines, err := getLines(text, width, fontsize, chinese)
if err != nil {
return
}
dc := gg.NewContext(width, height+50*lines)
dc.DrawImage(img, 0, 0)
// 设置背景颜色
dc.SetRGB(0, 0, 0) // 黑色背景
dc.DrawRectangle(0, float64(height), float64(width), float64(50*lines))
dc.Fill()
// 加载字体
fontPath := "./en.ttf"
if chinese {
fontPath = "./zh.ttf"
}
err = dc.LoadFontFace(fontPath, float64(fontsize))
if err != nil {
return
}
// 设置文本颜色
dc.SetRGB(1, 1, 1) // 白色文本
// 添加文本
drawMultilineText(dc, text, float64(0), float64(height)+35, float64(width), 50, 0, 0)
return dc, 50 * lines, nil
}
......@@ -6,6 +6,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"image"
"io"
"log"
"mime/multipart"
......@@ -15,6 +16,13 @@ import (
"strconv"
"strings"
"time"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"github.com/fogleman/gg"
_ "golang.org/x/image/webp"
)
var maxFileSize = 200 << 20
......@@ -38,7 +46,6 @@ func main() {
http.HandleFunc("/add-watermark", addWatermarkHandler)
http.HandleFunc("/add-watermark-v2", addWatermarkHandlerV2)
http.HandleFunc("/add-watermark-meme", addWatermarkMemeHandler)
log.Println("Server starting on port 8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
......@@ -171,6 +178,7 @@ func addWatermarkHandlerV2(w http.ResponseWriter, r *http.Request) {
watermarkPaths := make([]string, 0)
watermarkFiles := make([]multipart.File, 0)
watermarkPositions := make([]string, 0)
contextId := randomId()
hasWatermark := false
for i := 1; i <= 5; i++ {
......@@ -188,7 +196,7 @@ func addWatermarkHandlerV2(w http.ResponseWriter, r *http.Request) {
break
}
hasWatermark = true
watermarkPath := fmt.Sprintf("/%s_%s", randomId(), watermarkHeader.Filename)
watermarkPath := fmt.Sprintf("./%s_%s", contextId, watermarkHeader.Filename)
saveFile(watermarkFile, watermarkPath)
watermarkPaths = append(watermarkPaths, watermarkPath)
watermarkFiles = append(watermarkFiles, watermarkFile)
......@@ -210,8 +218,8 @@ func addWatermarkHandlerV2(w http.ResponseWriter, r *http.Request) {
}()
// 创建临时文件
videoPath := fmt.Sprintf("/%s_%s", randomId(), videoHeader.Filename)
outputPath := fmt.Sprintf("/%s_%s", randomId(), "output_"+videoHeader.Filename)
videoPath := fmt.Sprintf("./%s_%s", contextId, videoHeader.Filename)
outputPath := fmt.Sprintf("./%s_%s", contextId, "output_"+videoHeader.Filename)
defer os.RemoveAll(videoPath)
defer os.RemoveAll(outputPath)
saveFile(videoFile, videoPath)
......@@ -248,14 +256,67 @@ func addWatermarkHandlerV2(w http.ResponseWriter, r *http.Request) {
imageSuffix := []string{"webp", ".png", ".jpg", ".jpeg", ".gif"}
for _, suffix := range imageSuffix {
if strings.HasSuffix(strings.ToLower(videoHeader.Filename), suffix) {
// 处理文字水印
textArgs := r.FormValue("args")
temp := struct {
Text string `json:"text"`
Chinese bool `json:"chinese"`
Fontsize int `json:"fontsize"`
}{}
var overHeight int
if textArgs != "" {
err := json.Unmarshal([]byte(textArgs), &temp)
if err != nil {
log.Println("err1", err)
http.Error(w, "Error retrieving the watermark text", http.StatusBadRequest)
return
}
f, err := os.Open(videoPath)
if err != nil {
http.Error(w, "Error retrieving the watermark text", http.StatusBadRequest)
return
}
img, _, err := image.Decode(f)
if err != nil {
log.Println("err2", err)
http.Error(w, "Error retrieving the watermark text", http.StatusBadRequest)
return
}
var ggContext *gg.Context
ggContext, overHeight, err = drawText(img, temp.Text, temp.Fontsize, temp.Chinese)
if err != nil {
fmt.Println(err)
http.Error(w, "Error retrieving the watermark text", http.StatusBadRequest)
return
}
// 重新保存该图片
err = ggContext.SavePNG(videoPath)
if err != nil {
log.Println("err3", err)
http.Error(w, "Error retrieving the watermark text", http.StatusBadRequest)
return
}
}
args := []string{"-i", videoPath}
for i := 0; i < len(watermarkPaths); i++ {
args = append(args, "-i", watermarkPaths[i])
}
args = append(args, "-filter_complex")
filterComplex := fmt.Sprintf("[0][1]overlay=%s[tmp0];", watermarkPositions[0])
position := watermarkPositions[0]
if overHeight > 0 && strings.Contains(position, "main_h") {
position += "-" + strconv.Itoa(overHeight)
}
filterComplex := fmt.Sprintf("[0][1]overlay=%s[tmp0];", position)
for i := 0; i < len(watermarkPositions[1:]); i++ {
filterComplex += fmt.Sprintf("[tmp%d][%d]overlay=%s[tmp%d];", i, i+2, watermarkPositions[i+1], i+1)
position = watermarkPositions[i+1]
if overHeight > 0 && strings.Contains(position, "main_h") {
position += "-" + strconv.Itoa(overHeight)
}
filterComplex += fmt.Sprintf("[tmp%d][%d]overlay=%s[tmp%d];", i, i+2, position, i+1)
}
args = append(args, filterComplex[:len(filterComplex)-1], "-map", fmt.Sprintf("[tmp%d]", len(watermarkPaths)-1), outputPath)
......@@ -292,104 +353,6 @@ func addWatermarkHandlerV2(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, outputPath)
}
// 添加文本水印
func addWatermarkMemeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
return
}
// 解析multipart form
err := r.ParseMultipartForm(10 << 20) // 10 MB
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 获取上传的图片文件
imgFile, imgHeader, err := r.FormFile("input")
if err != nil {
http.Error(w, "Error retrieving the img file", http.StatusBadRequest)
return
}
defer imgFile.Close()
// 创建临时文件
imgPath := fmt.Sprintf("/%s_%s", randomId(), imgHeader.Filename)
outputPath := fmt.Sprintf("/%s_%s", randomId(), "output_"+imgHeader.Filename)
defer os.RemoveAll(imgPath)
defer os.RemoveAll(outputPath)
saveFile(imgFile, imgPath)
formArgs := r.FormValue("args")
temp := struct {
Position [][]int `json:"position"`
Text []string `json:"text"`
Chinese bool `json:"chinese"`
Height int `json:"height"`
Fontsize int `json:"fontsize"`
}{}
err = json.Unmarshal([]byte(formArgs), &temp)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
start := time.Now()
log.Printf("start processing image: %s\n", imgPath)
var cmd *exec.Cmd
var isImage = false
// 如果是图片
imageSuffix := []string{"webp", ".png", ".jpg", ".jpeg", ".gif"}
for _, suffix := range imageSuffix {
if strings.HasSuffix(strings.ToLower(imgHeader.Filename), suffix) {
args := []string{"-i", imgPath, "-vf"}
filter := fmt.Sprintf("pad=iw:ih+%d:0:0:black,", temp.Height)
for i := 0; i < len(temp.Text); i++ {
if temp.Chinese {
filter += fmt.Sprintf("drawtext=fontfile=/zh.ttf:text='%s':fontsize=%d:fontcolor=white:x=%d:y=h-%d+%d,", temp.Text[i], temp.Fontsize, temp.Position[i][0], temp.Height, temp.Position[i][1])
continue
}
filter += fmt.Sprintf("drawtext=fontfile=/en.ttf:text='%s':fontsize=%d:fontcolor=white:x=%d:y=h-%d+%d,", temp.Text[i], temp.Fontsize, temp.Position[i][0], temp.Height, temp.Position[i][1])
}
args = append(args, filter[:len(filter)-1], outputPath)
cmd = exec.Command("ffmpeg", args...)
isImage = true
break
}
}
if !isImage {
http.Error(w, "Error retrieving the img file", http.StatusBadRequest)
return
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
log.Printf("FFmpeg command: %s\n", cmd.String())
err = cmd.Run()
if err != nil {
http.Error(w, "Error executing FFmpeg command: "+err.Error(), http.StatusInternalServerError)
log.Printf("Error executing FFmpeg command: %s, %s\n", stderr.String(), stdout.String())
return
}
log.Printf("finish processing image: %s, cost: %s\n", imgPath, time.Since(start))
// 发送处理后的视频
w.Header().Set("Content-Disposition", "attachment; filename=watermarked_"+imgHeader.Filename)
w.Header().Set("Content-Type", imgHeader.Header.Get("Content-Type"))
http.ServeFile(w, r, outputPath)
}
func saveFile(file io.Reader, path string) error {
out, err := os.Create(path)
if err != nil {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment