新增 EXIF 数据处理模型及相关函数

This commit is contained in:
jimmy committed 2025-06-29 21:35:06 +08:00
1 parent bae2721935
commit e9e1c8318c
2 files changed
+277 -3

No files matched your search

+259
View File
@@ -0,0 +1,259 @@
package model
import (
"fmt"
"strconv"
)
/*
基础文件信息(文件级元数据)
这些信息即使图片没有 EXIF、XMP、IPTC 等元数据,也会存在,来自文件本身:
| 字段名 | 示例 | 说明 |
| ---------------------------------------------- | --------------------------- | --------------- |
| `FileName` | `image.jpg` | 文件名 |
| `FileSize` | `2.3 MB` | 文件大小 |
| `FileModifyDate` | `2024:11:04 10:40:29+08:00` | 最后修改时间(文件系统时间戳) |
| `FileCreateDate` | `2025:06:29 20:23:38+08:00` | 创建时间(视系统而定) |
| `FileAccessDate` | `2025:06:29 20:23:42+08:00` | 访问时间 |
| `Directory` | `D:/photos/` | 所在路径 |
| `MIMEType` | `image/jpeg` | 文件类型 |
| `FileType` / `FileTypeExtension` | `JPEG` / `JPG` | 扩展名和类型 |
| `ImageSize` | `5184x3888` | 像素尺寸 |
| `Megapixels` | `20.1` | 计算得出 |
| `EncodingProcess` | `Baseline DCT` | JPEG 编码方式 |
| `ColorComponents` | `3` | 通道数(RGB 通常为 3) |
| `YResolution`, `XResolution`, `ResolutionUnit` | `180`, `180`, `inch` | DPI 设置 |
*/
type BaseImageInfo struct {
FileName string `json:"FileName"`
FileSize int64 `json:"FileSize"`
ImageWidth int `json:"ImageWidth"`
ImageHeight int `json:"ImageHeight"`
ImageSize string `json:"ImageSize"`
// 文件类型
MIMEType string `json:"MIMEType"`
// 文件扩展名
FileType string `json:"FileType"`
// 文件扩展名
FileTypeExt string `json:"FileTypeExtension"`
// 文件最后修改时间
ModifyDate string `json:"FileModifyDate"`
// 文件创建时间
CreateDate string `json:"FileCreateDate"`
ColorSpace string `json:"ColorSpace"` // 色彩空间 (sRGB, Adobe RGB等)
BitsPerSample int `json:"BitsPerSample"` // 每个颜色通道的位深度
Resolution string `json:"Resolution"` // 分辨率信息
Quality int `json:"Quality"` // JPEG质量
}
type ExifInfo struct {
Model string `json:"Model"`
Make string `json:"Make"`
ISO int `json:"ISO"`
GPSLatitude float64 `json:"GPSLatitude"`
GPSLongitude float64 `json:"GPSLongitude"`
ExposureTime float64 `json:"ExposureTime"`
Aperture float64 `json:"Aperture"`
FNumber float64 `json:"FNumber"` // 光圈值
FocalLength float64 `json:"FocalLength"`
LensID string `json:"LensID"`
Title string `json:"Title"`
Description string `json:"Description"`
DateTimeOrig string `json:"DateTimeOriginal"`
}
type ParsedExif struct {
BaseInfo BaseImageInfo
Exif ExifInfo
OtherFields map[string]interface{}
}
// 安全类型转换函数
func safeIntConvert(v interface{}) int {
switch val := v.(type) {
case int:
return val
case int64:
return int(val)
case float64:
return int(val)
case string:
if i, err := strconv.Atoi(val); err == nil {
return i
}
}
return 0
}
func safeInt64Convert(v interface{}) int64 {
switch val := v.(type) {
case int:
return int64(val)
case int64:
return val
case float64:
return int64(val)
case string:
if i, err := strconv.ParseInt(val, 10, 64); err == nil {
return i
}
}
return 0
}
func safeFloat64Convert(v interface{}) float64 {
switch val := v.(type) {
case float64:
return val
case float32:
return float64(val)
case int:
return float64(val)
case int64:
return float64(val)
case string:
if f, err := strconv.ParseFloat(val, 64); err == nil {
return f
}
}
return 0.0
}
func safeStringConvert(v interface{}) string {
switch val := v.(type) {
case string:
return val
case int, int64, float64, float32:
return fmt.Sprintf("%v", val)
}
return ""
}
func SplitExifData(data map[string]interface{}) *ParsedExif {
var base BaseImageInfo
var exif ExifInfo
// 手动提取BaseImageInfo字段
if v, ok := data["FileName"]; ok {
base.FileName = safeStringConvert(v)
}
if v, ok := data["FileSize"]; ok {
base.FileSize = safeInt64Convert(v)
}
if v, ok := data["ImageWidth"]; ok {
base.ImageWidth = safeIntConvert(v)
}
if v, ok := data["ImageHeight"]; ok {
base.ImageHeight = safeIntConvert(v)
}
if v, ok := data["ImageSize"]; ok {
base.ImageSize = safeStringConvert(v)
}
if v, ok := data["MIMEType"]; ok {
base.MIMEType = safeStringConvert(v)
}
if v, ok := data["FileType"]; ok {
base.FileType = safeStringConvert(v)
}
if v, ok := data["FileTypeExtension"]; ok {
base.FileTypeExt = safeStringConvert(v)
}
if v, ok := data["FileModifyDate"]; ok {
base.ModifyDate = safeStringConvert(v)
}
if v, ok := data["FileCreateDate"]; ok {
base.CreateDate = safeStringConvert(v)
}
if v, ok := data["ColorSpace"]; ok {
base.ColorSpace = safeStringConvert(v)
}
if v, ok := data["BitsPerSample"]; ok {
base.BitsPerSample = safeIntConvert(v)
}
// 尝试从XResolution构建分辨率信息
if v, ok := data["XResolution"]; ok {
base.Resolution = safeStringConvert(v)
}
if v, ok := data["JPEGQuality"]; ok {
base.Quality = safeIntConvert(v)
}
// 手动提取ExifInfo字段
if v, ok := data["Model"]; ok {
exif.Model = safeStringConvert(v)
}
if v, ok := data["Make"]; ok {
exif.Make = safeStringConvert(v)
}
if v, ok := data["ISO"]; ok {
exif.ISO = safeIntConvert(v)
}
if v, ok := data["GPSLatitude"]; ok {
exif.GPSLatitude = safeFloat64Convert(v)
}
if v, ok := data["GPSLongitude"]; ok {
exif.GPSLongitude = safeFloat64Convert(v)
}
if v, ok := data["ExposureTime"]; ok {
exif.ExposureTime = safeFloat64Convert(v)
}
if v, ok := data["Aperture"]; ok {
exif.Aperture = safeFloat64Convert(v)
}
if v, ok := data["FNumber"]; ok {
exif.FNumber = safeFloat64Convert(v)
}
if v, ok := data["FocalLength"]; ok {
exif.FocalLength = safeFloat64Convert(v)
}
if v, ok := data["LensID"]; ok {
exif.LensID = safeStringConvert(v)
}
if v, ok := data["Title"]; ok {
exif.Title = safeStringConvert(v)
}
if v, ok := data["Description"]; ok {
exif.Description = safeStringConvert(v)
}
if v, ok := data["DateTimeOriginal"]; ok {
exif.DateTimeOrig = safeStringConvert(v)
}
// 定义已处理的字段
processedFields := map[string]bool{
"FileName": true, "FileSize": true, "ImageWidth": true, "ImageHeight": true,
"ImageSize": true, "MIMEType": true, "FileType": true, "FileTypeExtension": true,
"FileModifyDate": true, "FileCreateDate": true, "ColorSpace": true, "BitsPerSample": true,
"XResolution": true, "JPEGQuality": true,
"Model": true, "Make": true, "ISO": true, "GPSLatitude": true, "GPSLongitude": true,
"ExposureTime": true, "Aperture": true, "FNumber": true, "FocalLength": true,
"LensID": true, "Title": true, "Description": true, "DateTimeOriginal": true,
}
// 提取剩余字段
other := make(map[string]interface{})
for k, v := range data {
if !processedFields[k] {
other[k] = v
}
}
return &ParsedExif{
BaseInfo: base,
Exif: exif,
OtherFields: other,
}
}
// 辅助函数:判断字符串是否在切片中
func isIn(str string, slice ...string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
+18 -3
View File
@@ -12,6 +12,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"rear/internal/consts" "rear/internal/consts"
"rear/internal/model"
"rear/internal/utils/tools" "rear/internal/utils/tools"
"rear/pkg/logger" "rear/pkg/logger"
"rear/pkg/utils" "rear/pkg/utils"
@@ -149,9 +150,10 @@ func (pt *PictureTask) Run() {
) )
return return
} }
logger.Info("获取到 EXIF 数据", zap.Any("exifData", exifData))
return // 分割 EXIF 数据
splitExifData := model.SplitExifData(exifData)
// 如果是非常规格式或 raw 则转换为 png ;如果是 PNG 则无损转换为 webp 或 jpg;如果是 webp 或 jpg 则进行压缩和其他处理 // 如果是非常规格式或 raw 则转换为 png ;如果是 PNG 则无损转换为 webp 或 jpg;如果是 webp 或 jpg 则进行压缩和其他处理
if fileType == string(consts.FormatJPG) { if fileType == string(consts.FormatJPG) {
//options := tools.DefaultOptions() //options := tools.DefaultOptions()
@@ -165,10 +167,23 @@ func (pt *PictureTask) Run() {
} }
// 图像转换后,将转换后照片信息检索判断是否有必要进行压缩
// 压缩图像 【判断图像的大小是否需要压缩】
imgWidth := splitExifData.BaseInfo.ImageWidth
imgHeight := splitExifData.BaseInfo.ImageHeight
logger.Info("图像宽度",
zap.Int("width", imgWidth),
zap.Int("height", imgHeight),
)
// 如果图像的宽度或高度小于 800,则不进行压缩
return
// 分割 Hash 路径 // 分割 Hash 路径
//utils.HashUtils.HashThumbPath(config.CONFIG.AppDir, hash) //utils.HashUtils.HashThumbPath(config.CONFIG.AppDir, hash)
// 压缩图像
// 获取 exif // 获取 exif
// 保存到数据库 // 保存到数据库