webp_server_go/encoder/process.go

343 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package encoder
import (
"errors"
"os"
"path"
"slices"
"strings"
"webp_server_go/config"
"webp_server_go/helper"
"github.com/davidbyttow/govips/v2/vips"
log "github.com/sirupsen/logrus"
)
func resizeImage(img *vips.ImageRef, extraParams config.ExtraParams) error {
imageHeight := img.Height()
imageWidth := img.Width()
imgHeightWidthRatio := float32(imageHeight) / float32(imageWidth)
log.Infof("开始调整图像大小。原始尺寸: %dx%d, 比例: %.2f", imageWidth, imageHeight, imgHeightWidthRatio)
log.Infof("请求的参数: MaxWidth=%d, MaxHeight=%d, Width=%d, Height=%d",
extraParams.MaxWidth, extraParams.MaxHeight, extraParams.Width, extraParams.Height)
if extraParams.MaxHeight > 0 && extraParams.MaxWidth > 0 {
if imageHeight > extraParams.MaxHeight || imageWidth > extraParams.MaxWidth {
heightExceedRatio := float32(imageHeight) / float32(extraParams.MaxHeight)
widthExceedRatio := float32(imageWidth) / float32(extraParams.MaxWidth)
if heightExceedRatio > widthExceedRatio {
newWidth := int(float32(extraParams.MaxHeight) / imgHeightWidthRatio)
log.Infof("使用MaxHeight调整大小: %dx%d", newWidth, extraParams.MaxHeight)
err := img.Thumbnail(newWidth, extraParams.MaxHeight, 0)
if err != nil {
log.Errorf("调整大小失败: %v", err)
return err
}
} else {
newHeight := int(float32(extraParams.MaxWidth) * imgHeightWidthRatio)
log.Infof("使用MaxWidth调整大小: %dx%d", extraParams.MaxWidth, newHeight)
err := img.Thumbnail(extraParams.MaxWidth, newHeight, 0)
if err != nil {
log.Errorf("调整大小失败: %v", err)
return err
}
}
} else {
log.Info("图像尺寸在MaxWidth和MaxHeight范围内无需调整")
}
}
if extraParams.MaxHeight > 0 && imageHeight > extraParams.MaxHeight && extraParams.MaxWidth == 0 {
newWidth := int(float32(extraParams.MaxHeight) / imgHeightWidthRatio)
log.Infof("仅使用MaxHeight调整大小: %dx%d", newWidth, extraParams.MaxHeight)
err := img.Thumbnail(newWidth, extraParams.MaxHeight, 0)
if err != nil {
log.Errorf("调整大小失败: %v", err)
return err
}
}
if extraParams.MaxWidth > 0 && imageWidth > extraParams.MaxWidth && extraParams.MaxHeight == 0 {
newHeight := int(float32(extraParams.MaxWidth) * imgHeightWidthRatio)
log.Infof("仅使用MaxWidth调整大小: %dx%d", extraParams.MaxWidth, newHeight)
err := img.Thumbnail(extraParams.MaxWidth, newHeight, 0)
if err != nil {
log.Errorf("调整大小失败: %v", err)
return err
}
}
if extraParams.Width > 0 && extraParams.Height > 0 {
log.Infof("使用指定的Width和Height调整大小: %dx%d", extraParams.Width, extraParams.Height)
cropInteresting := getCropInteresting()
err := img.Thumbnail(extraParams.Width, extraParams.Height, cropInteresting)
if err != nil {
log.Errorf("调整大小失败: %v", err)
return err
}
}
if extraParams.Width > 0 && extraParams.Height == 0 {
newHeight := int(float32(extraParams.Width) * imgHeightWidthRatio)
log.Infof("仅使用Width调整大小: %dx%d", extraParams.Width, newHeight)
err := img.Thumbnail(extraParams.Width, newHeight, 0)
if err != nil {
log.Errorf("调整大小失败: %v", err)
return err
}
}
if extraParams.Height > 0 && extraParams.Width == 0 {
newWidth := int(float32(extraParams.Height) / imgHeightWidthRatio)
log.Infof("仅使用Height调整大小: %dx%d", newWidth, extraParams.Height)
err := img.Thumbnail(newWidth, extraParams.Height, 0)
if err != nil {
log.Errorf("调整大小失败: %v", err)
return err
}
}
log.Infof("图像调整完成。新尺寸: %dx%d", img.Width(), img.Height())
return nil
}
func getCropInteresting() vips.Interesting {
cropInteresting := vips.InterestingAttention
switch config.Config.ExtraParamsCropInteresting {
case "InterestingNone":
cropInteresting = vips.InterestingNone
case "InterestingCentre":
cropInteresting = vips.InterestingCentre
case "InterestingEntropy":
cropInteresting = vips.InterestingEntropy
case "InterestingAttention":
cropInteresting = vips.InterestingAttention
case "InterestingLow":
cropInteresting = vips.InterestingLow
case "InterestingHigh":
cropInteresting = vips.InterestingHigh
case "InterestingAll":
cropInteresting = vips.InterestingAll
}
log.Infof("使用裁剪策略: %s", config.Config.ExtraParamsCropInteresting)
return cropInteresting
}
func ResizeItself(raw, dest string, extraParams config.ExtraParams) {
log.Infof("开始调整图像大小: 源文件=%s, 目标文件=%s", raw, dest)
// 创建目标目录
if err := os.MkdirAll(path.Dir(dest), 0755); err != nil {
log.Errorf("创建目标目录失败: %v", err)
return
}
// 加载图像
img, err := vips.LoadImageFromFile(raw, &vips.ImportParams{
FailOnError: boolFalse,
})
if err != nil {
log.Warnf("加载图像失败: 文件=%s, 错误=%v", raw, err)
return
}
defer img.Close()
// 调整图像大小
if err := resizeImage(img, extraParams); err != nil {
log.Warnf("调整图像大小失败: %v", err)
return
}
// 移除元数据(如果配置要求)
if config.Config.StripMetadata {
log.Debug("正在移除图像元数据")
img.RemoveMetadata()
}
// 导出图像
buf, _, err := img.ExportNative()
if err != nil {
log.Errorf("导出图像失败: %v", err)
return
}
// 写入文件
if err := os.WriteFile(dest, buf, 0600); err != nil {
log.Errorf("写入目标文件失败: 文件=%s, 错误=%v", dest, err)
return
}
log.Infof("图像大小调整成功: 目标文件=%s", dest)
}
// 预处理图像(自动旋转、调整大小等)
func preProcessImage(img *vips.ImageRef, imageType string, extraParams config.ExtraParams) (bool, error) {
log.Debugf("开始预处理图像: 类型=%s, 宽度=%d, 高度=%d", imageType, img.Metadata().Width, img.Metadata().Height)
// 标志,用于指示是否应该复制原图
var shouldCopyOriginal bool
// 检查宽度/高度并忽略特定图像格式
switch imageType {
case "webp":
if img.Metadata().Width > config.WebpMax || img.Metadata().Height > config.WebpMax {
log.Warnf("WebP图像尺寸超限: 宽度=%d, 高度=%d, 最大限制=%d", img.Metadata().Width, img.Metadata().Height, config.WebpMax)
shouldCopyOriginal = true
return shouldCopyOriginal, errors.New("WebP图像太大")
}
if slices.Contains(webpIgnore, img.Format()) {
log.Infof("WebP编码器忽略图像类型: %s", img.Format())
shouldCopyOriginal = true
return shouldCopyOriginal, errors.New("WebP 编码器:忽略图像类型")
}
case "avif":
if img.Metadata().Width > config.AvifMax || img.Metadata().Height > config.AvifMax {
log.Warnf("AVIF图像尺寸超限: 宽度=%d, 高度=%d, 最大限制=%d", img.Metadata().Width, img.Metadata().Height, config.AvifMax)
shouldCopyOriginal = true
return shouldCopyOriginal, errors.New("AVIF图像太大")
}
if slices.Contains(avifIgnore, img.Format()) {
log.Infof("AVIF编码器忽略图像类型: %s", img.Format())
shouldCopyOriginal = true
return shouldCopyOriginal, errors.New("AVIF 编码器:忽略图像类型")
}
}
// 自动旋转
if err := img.AutoRotate(); err != nil {
log.Errorf("图像自动旋转失败: %v", err)
shouldCopyOriginal = true
return shouldCopyOriginal, err
}
log.Debug("图像自动旋转完成")
// 额外参数处理
if config.Config.EnableExtraParams {
// 检查是否需要进行尺寸调整
if extraParams.MaxWidth != 0 || extraParams.MaxHeight != 0 || extraParams.Width != 0 || extraParams.Height != 0 {
log.Debug("开始应用额外图像处理参数")
if err := resizeImage(img, extraParams); err != nil {
log.Errorf("应用额外图像处理参数失败: %v", err)
// 这里不设置 shouldCopyOriginal 为 true因为我们不想在这种情况下复制原图
return shouldCopyOriginal, err
}
log.Debug("额外图像处理参数应用完成")
} else {
log.Debug("未设置任何尺寸参数,跳过图像尺寸调整")
}
}
// log.Debug("图像预处理完成")
return shouldCopyOriginal, nil
}
func ProcessAndSaveImage(rawImageAbs, exhaustFilename string, extraParams config.ExtraParams) error {
// log.Infof("开始处理图像: 源文件=%s, 目标文件=%s", rawImageAbs, exhaustFilename)
// 创建目标目录
if err := os.MkdirAll(path.Dir(exhaustFilename), 0755); err != nil {
log.Errorf("创建目标目录失败: %v", err)
return err
}
// 获取原图文件大小
originalInfo, err := os.Stat(rawImageAbs)
if err != nil {
log.Errorf("获取原图文件信息失败: %v", err)
return err
}
originalSize := originalInfo.Size()
// 如果原始图像是 NEF 格式,先转换为 JPG
if strings.HasSuffix(strings.ToLower(rawImageAbs), ".nef") {
tempJPG, converted := ConvertRawToJPG(rawImageAbs, exhaustFilename)
if converted {
defer func() {
log.Infoln("移除中间转换文件:", tempJPG)
if err := os.Remove(tempJPG); err != nil {
log.Warnln("删除转换文件失败", err)
}
}()
rawImageAbs = tempJPG
}
}
// 加载图像
img, err := vips.LoadImageFromFile(rawImageAbs, &vips.ImportParams{
FailOnError: boolFalse,
NumPages: intMinusOne,
})
if err != nil {
log.Warnf("无法打开源图像: %v", err)
return err
}
defer img.Close()
// 确定输出格式
var imageType string
switch {
case strings.HasSuffix(exhaustFilename, ".avif"):
imageType = "avif"
case strings.HasSuffix(exhaustFilename, ".jxl"):
imageType = "jxl"
default:
imageType = "webp"
}
// 预处理图像(自动旋转、调整大小等)
shouldCopyOriginal, err := preProcessImage(img, imageType, extraParams)
if err != nil {
log.Warnf("预处理源图像时出错: %v", err)
if shouldCopyOriginal {
log.Infof("由于预处理错误,将复制原图")
return helper.CopyFile(rawImageAbs, exhaustFilename)
}
// 如果不应该复制原图,就返回错误
return err
}
// 根据图像类型进行编码
var encoderErr error
switch imageType {
case "webp":
encoderErr = webpEncoder(img, rawImageAbs, exhaustFilename)
case "avif":
encoderErr = avifEncoder(img, rawImageAbs, exhaustFilename)
case "jxl":
encoderErr = jxlEncoder(img, rawImageAbs, exhaustFilename)
}
if encoderErr != nil {
log.Errorf("图像编码失败: %v", encoderErr)
return helper.CopyFile(rawImageAbs, exhaustFilename) // 这里可以考虑复制原图
}
// 比较转换后的文件大小
convertedInfo, err := os.Stat(exhaustFilename)
if err != nil {
log.Errorf("获取转换后文件信息失败: %v", err)
return err
}
if convertedInfo.Size() > originalSize {
log.Infof("转换后的图片大于原图,使用原图: %s", rawImageAbs)
// 删除转换后的大文件
if err := os.Remove(exhaustFilename); err != nil {
log.Warnf("删除大的转换文件失败: %v", err)
}
// 将原图复制到 EXHAUST_PATH
if err := helper.CopyFile(rawImageAbs, exhaustFilename); err != nil {
log.Errorf("复制原图到 EXHAUST_PATH 失败: %v", err)
return err
}
// log.Infof("成功将原图复制到 EXHAUST_PATH: %s", exhaustFilename)
} else {
// log.Infof("图像处理成功: 目标文件=%s", exhaustFilename)
}
return nil
}