vision/cmd/vision/main.go

272 lines
6.8 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"apigo.cc/go/vision"
)
var (
// 全局参数
outFile = flag.String("o", "", "输出文件路径 (如: out.png, out.webp)")
inspect = flag.Bool("inspect", false, "查看图像详细元数据 (默认行为)")
version = flag.Bool("v", false, "显示版本信息")
// 二维码/条码生成
dataStr = flag.String("data", "", "生成二维码/条码的内容")
size = flag.Int("size", 256, "生成的二维码尺寸 (正方形)")
width = flag.Int("width", 0, "宽度 (针对预览、条码、验证码、缩放)")
height = flag.Int("height", 0, "高度 (针对预览、条码、验证码、缩放)")
// 图像处理
resizeStr = flag.String("resize", "", "缩放尺寸 (格式: 800x600)")
blur = flag.Float64("blur", 0, "模糊程度 (sigma)")
grayscale = flag.Bool("grayscale", false, "转为灰度图")
rotate = flag.Float64("rotate", 0, "顺时针旋转角度")
brightness = flag.Float64("brightness", 0, "亮度调整 (-100 到 100)")
contrast = flag.Float64("contrast", 0, "对比度调整 (-100 到 100)")
// 预览生成
previewType = flag.String("type", "", "预览类型: image, video, audio (自动识别后缀)")
_ = flag.String("p", "", "预览类型 (别名, 同 -type)")
// 验证码
captchaLen = flag.Int("len", 4, "验证码长度")
// 视频
vtime = flag.Float64("time", 0, "提取视频帧的时间点 (秒)")
)
const visionVersion = "1.0.0"
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "👁️ Vision CLI (vision) - 全能图像与媒体处理工具 v%s\n\n", visionVersion)
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " vision [flags] [file] # 处理已有文件\n")
fmt.Fprintf(os.Stderr, " vision --qrcode --data \"...\" # 生成二维码\n")
fmt.Fprintf(os.Stderr, " vision --captcha -o c.png # 生成验证码\n\n")
fmt.Fprintf(os.Stderr, "常见示例:\n")
fmt.Fprintf(os.Stderr, " vision photo.jpg # 查看图片信息及主色调\n")
fmt.Fprintf(os.Stderr, " vision code.png --decode # 识别二维码/条码\n")
fmt.Fprintf(os.Stderr, " vision in.png -o out.png --blur 2 --grayscale # 批量图像处理\n")
fmt.Fprintf(os.Stderr, " vision video.mp4 -p video -o p.webp # 生成视频动态预览\n")
fmt.Fprintf(os.Stderr, " vision video.mp4 --time 10.5 -o frame.jpg # 提取视频指定时间帧\n\n")
fmt.Fprintf(os.Stderr, "参数详解:\n")
flag.PrintDefaults()
}
decode := flag.Bool("decode", false, "识别图像中的二维码或条码")
flag.Parse()
if *version {
fmt.Printf("vision version %s\n", visionVersion)
return
}
// 处理 -p 别名
if *previewType == "" {
// 遍历 flag.Args 之前的 flags 找到 -p
flag.Visit(func(f *flag.Flag) {
if f.Name == "p" {
*previewType = f.Value.String()
}
})
}
args := flag.Args()
// 1. 无文件输入时的生成逻辑
if len(args) == 0 {
if *dataStr != "" {
runGenerate()
return
}
if flag.NFlag() > 0 && (*outFile != "" || *width > 0) {
// 如果指定了输出但没输入文件,尝试生成验证码
runCaptcha()
return
}
flag.Usage()
return
}
// 2. 有文件输入时的处理逻辑
srcFile := args[0]
// 识别预览生成 (如果是预览命令)
if *previewType != "" {
runPreview(srcFile)
return
}
// 识别视频帧提取
if strings.HasSuffix(strings.ToLower(srcFile), ".mp4") || strings.HasSuffix(strings.ToLower(srcFile), ".mov") {
if *vtime > 0 || (*outFile != "" && *previewType == "") {
runVideoExtract(srcFile)
return
}
}
// 图像处理逻辑
runImageProcess(srcFile, *decode)
}
func runGenerate() {
if *width > 0 && *height > 0 {
// 生成条码
c, err := vision.GenerateBarcode(*dataStr, *width, *height)
if err != nil {
fail("生成条码失败: %v", err)
}
save(c)
} else {
// 生成二维码
c, err := vision.GenerateQRCode(*dataStr, *size)
if err != nil {
fail("生成二维码失败: %v", err)
}
save(c)
}
}
func runCaptcha() {
opt := &vision.CaptchaOption{
Length: *captchaLen,
Width: *width,
Height: *height,
}
c := vision.GenerateCaptcha(opt)
fmt.Printf("🛡️ 验证码内容: %s\n", opt.Text)
save(c)
}
func runPreview(src string) {
if *outFile == "" {
fail("预览生成必须指定输出路径 (-o)")
}
w, h := *width, *height
if w == 0 { w = 320 }
if h == 0 { h = 180 }
var err error
switch strings.ToLower(*previewType) {
case "image":
err = vision.GenerateImagePreview(src, *outFile, w, h)
case "video":
err = vision.GenerateVideoPreview(src, *outFile, w, h)
case "audio":
err = vision.GenerateAudioPreview(src, *outFile)
default:
fail("未知的预览类型: %s (可选: image, video, audio)", *previewType)
}
if err != nil {
fail("生成预览失败: %v", err)
}
fmt.Printf("✅ 预览已生成: %s\n", *outFile)
}
func runVideoExtract(src string) {
v, err := vision.NewVideo()
if err != nil {
fail("初始化视频工具失败: %v", err)
}
frame, err := v.ExtractFrame(src, *vtime)
if err != nil {
fail("提取视频帧失败: %v", err)
}
save(frame)
}
func runImageProcess(src string, doDecode bool) {
c, err := vision.Load(src)
if err != nil {
fail("无法加载图像 '%s': %v", src, err)
}
if doDecode {
res, err := c.DecodeAll()
if err != nil {
fail("解码失败: %v", err)
}
fmt.Printf("📝 解码结果: %s\n", res)
return
}
// 批量处理
modified := false
if *resizeStr != "" {
parts := strings.Split(strings.ToLower(*resizeStr), "x")
if len(parts) == 2 {
w, _ := strconv.Atoi(parts[0])
h, _ := strconv.Atoi(parts[1])
if w > 0 && h > 0 {
c.Resize(w, h)
modified = true
}
}
}
if *blur > 0 {
c.Blur(*blur)
modified = true
}
if *grayscale {
c.Grayscale()
modified = true
}
if *rotate != 0 {
c.Rotate(*rotate)
modified = true
}
if *brightness != 0 {
c.AdjustBrightness(*brightness)
modified = true
}
if *contrast != 0 {
c.AdjustContrast(*contrast)
modified = true
}
if *outFile != "" {
save(c)
} else if modified {
fail("已应用处理,但未指定输出路径 (-o)")
} else {
// 默认 inspect 模式
fmt.Printf("🔍 图像详情: %s\n", src)
fmt.Printf(" 尺寸: %dx%d\n", c.Width(), c.Height())
hash := vision.PHash(c.Image())
fmt.Printf(" 指纹 (PHash): %016X\n", hash)
palette := c.ExtractPalette(5)
fmt.Printf(" 主要颜色 (调色板):\n")
for _, col := range palette {
fmt.Printf(" - %s (%d)\n", col.Hex, col.Count)
}
}
}
func save(c *vision.Canvas) {
path := *outFile
if path == "" {
path = "out.png"
}
if err := vision.Save(c, path); err != nil {
fail("保存失败: %v", err)
}
fmt.Printf("✨ 成功保存至: %s\n", path)
}
func fail(format string, a ...any) {
fmt.Fprintf(os.Stderr, "❌ 错误: "+format+"\n", a...)
os.Exit(1)
}