text overlay positions preset

This commit is contained in:
Dmitry Sergeev
2026-09-16 09:55:24 +03:00
parent 8727a9a357
commit 12cdad0805
2 changed files with 55 additions and 7 deletions
+23 -4
View File
@@ -35,6 +35,7 @@ type appArgs struct {
textOverlay string textOverlay string
overlayX int overlayX int
overlayY int overlayY int
overlayPos string
videoWidth uint videoWidth uint
videoHeight uint videoHeight uint
@@ -277,9 +278,26 @@ func flagSetAddFlags(fs *pflag.FlagSet, args *appArgs) {
fs.StringVarP(&args.pattern, "pattern", "p", "bars", "Video pattern type") fs.StringVarP(&args.pattern, "pattern", "p", "bars", "Video pattern type")
fs.BoolVar(&args.listPatterns, "list-patterns", false, "List video available video patterns and exit") fs.BoolVar(&args.listPatterns, "list-patterns", false, "List video available video patterns and exit")
fs.StringVarP(&args.textOverlay, "text", "t", "", "Text overlay above video pattern") fs.StringVarP(&args.textOverlay, "text", "t", "", "Text overlay above video pattern. Ignored if text-pos set")
fs.IntVar(&args.overlayX, "text-x", 0, "Text overlay X position in px") fs.IntVar(&args.overlayX, "text-x", 0, "Text overlay X position in px. Ignored if text-pos set")
fs.IntVar(&args.overlayY, "text-y", 0, "Text overlay Y position in px") fs.IntVar(
&args.overlayY,
"text-y",
0,
"Text overlay Y position in px")
fs.StringVar(
&args.overlayPos,
"text-pos",
"",
"Text overlay position with pre-defined values:\n"+
"tl - top-left corner\n"+
"tc - top-center\n"+
"tr - top-right corner\n"+
"cc - center of the frame\n"+
"bl - bottom-left corner\n"+
"bc - bottom-center\n"+
"br - bottom-right corner",
)
fs.UintVar(&args.videoWidth, "width", 1920, "Video pattern width") fs.UintVar(&args.videoWidth, "width", 1920, "Video pattern width")
fs.UintVar(&args.videoHeight, "height", 1080, "Video pattern height") fs.UintVar(&args.videoHeight, "height", 1080, "Video pattern height")
@@ -376,7 +394,7 @@ func main() {
args.overlayX > int(vi.width) || args.overlayX > int(vi.width) ||
args.overlayY < 0 || args.overlayY < 0 ||
args.overlayY > int(vi.height) { args.overlayY > int(vi.height) {
log.Fatalf("Overlay X/Y pos can't be negative or greater, than video width/height") log.Fatalf("overlay X/Y pos can't be negative or greater, than video width/height")
} }
overlay, err = generator.NewTextOverlay( overlay, err = generator.NewTextOverlay(
args.textOverlay, args.textOverlay,
@@ -384,6 +402,7 @@ func main() {
int(vi.height), int(vi.height),
args.overlayX, args.overlayX,
args.overlayY, args.overlayY,
args.overlayPos,
face, face,
) )
if err != nil { if err != nil {
+32 -3
View File
@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"image" "image"
"os" "os"
"slices"
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/font/opentype" "golang.org/x/image/font/opentype"
@@ -22,6 +23,8 @@ type TextOverlay struct {
blocks []byte // pre-packed v210 tile: h rows x (w/6) blocks x 16 bytes blocks []byte // pre-packed v210 tile: h rows x (w/6) blocks x 16 bytes
} }
var TextPosAvailable []string = []string{"tl", "tc", "tr", "cc", "bl", "bc", "br"}
// LoadFace parses a TTF/OTF file and builds a render-ready face at the given // LoadFace parses a TTF/OTF file and builds a render-ready face at the given
// pixel size (DPI 72, full hinting for crisp video text). Call once and // pixel size (DPI 72, full hinting for crisp video text). Call once and
// reuse for every overlay. // reuse for every overlay.
@@ -48,7 +51,7 @@ func LoadFace(path string, size float64) (font.Face, error) {
// NewTextOverlay rasterizes text (white, anti-aliased) onto a black backing box. // NewTextOverlay rasterizes text (white, anti-aliased) onto a black backing box.
// Box width and x are multiples of 6 so the box // Box width and x are multiples of 6 so the box
// covers whole v210 blocks and the per-frame copy is word-aligned. // covers whole v210 blocks and the per-frame copy is word-aligned.
func NewTextOverlay(text string, frameW, frameH, posX, posY int, face font.Face) (*TextOverlay, error) { func NewTextOverlay(text string, frameW, frameH, posX, posY int, textPos string, face font.Face) (*TextOverlay, error) {
if text == "" { if text == "" {
return nil, fmt.Errorf("text: empty string") return nil, fmt.Errorf("text: empty string")
} }
@@ -89,8 +92,34 @@ func NewTextOverlay(text string, frameW, frameH, posX, posY int, face font.Face)
return nil, fmt.Errorf("text: box %dpx does not fit frame height %dpx", topMargin+h, frameH) return nil, fmt.Errorf("text: box %dpx does not fit frame height %dpx", topMargin+h, frameH)
} }
// posX = ((frameW - w) / 2 / 6) * 6 if textPos != "" {
// posY = topMargin if !slices.Contains(TextPosAvailable, textPos) {
return nil, fmt.Errorf("text: unknown text position preset '%s'", textPos)
}
switch textPos {
case "tl":
posX = 0
posY = 0
case "tc":
posX = ((frameW - w) / 2 / 6) * 6
posY = 0
case "tr":
posX = frameW - w
posY = 0
case "cc":
posX = ((frameW - w) / 2 / 6) * 6
posY = (frameH - h) / 2
case "bl":
posX = 0
posY = frameH - h
case "bc":
posX = ((frameW - w) / 2 / 6) * 6
posY = frameH - h
case "br":
posX = frameW - w
posY = frameH - h
}
}
o := &TextOverlay{ o := &TextOverlay{
frameW: frameW, frameW: frameW,
x: posX, x: posX,