新增 EXIF 数据处理模型及相关函数
This commit is contained in:
1 parent
bae2721935
commit
e9e1c8318c
2 files changed
+277
-3
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"rear/internal/consts"
|
||||
"rear/internal/model"
|
||||
"rear/internal/utils/tools"
|
||||
"rear/pkg/logger"
|
||||
"rear/pkg/utils"
|
||||
@@ -149,9 +150,10 @@ func (pt *PictureTask) Run() {
|
||||
)
|
||||
return
|
||||
}
|
||||
logger.Info("获取到 EXIF 数据", zap.Any("exifData", exifData))
|
||||
|
||||
return
|
||||
// 分割 EXIF 数据
|
||||
splitExifData := model.SplitExifData(exifData)
|
||||
|
||||
// 如果是非常规格式或 raw 则转换为 png ;如果是 PNG 则无损转换为 webp 或 jpg;如果是 webp 或 jpg 则进行压缩和其他处理
|
||||
if fileType == string(consts.FormatJPG) {
|
||||
//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 路径
|
||||
//utils.HashUtils.HashThumbPath(config.CONFIG.AppDir, hash)
|
||||
|
||||
// 压缩图像
|
||||
// 获取 exif
|
||||
// 保存到数据库
|
||||
|
||||
|
||||
Reference in new issue
Block a user