Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
W
watermark
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Odysseus
watermark
Commits
f5366453
Commit
f5366453
authored
Sep 13, 2024
by
贾浩@五瓣科技
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update
parent
7fb9462f
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
192 additions
and
104 deletions
+192
-104
go.mod
go.mod
+10
-0
go.sum
go.sum
+6
-0
text.go
text.go
+109
-0
watermark.go
watermark.go
+67
-104
No files found.
go.mod
0 → 100644
View file @
f5366453
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
go.sum
0 → 100644
View file @
f5366453
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=
text.go
0 → 100644
View file @
f5366453
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
}
watermark.go
View file @
f5366453
...
...
@@ -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
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment