star/image/exif.go
2025-06-13 13:05:50 +08:00

232 lines
8.5 KiB
Go
Raw Permalink 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 image
import (
"b612.me/exif"
exifcommon "b612.me/exif/common"
"encoding/hex"
"fmt"
"os"
)
func Exif(path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("打开文件失败 %s", path)
}
rawExif, err := exif.SearchAndExtractExifWithReader(f)
f.Close()
if err != nil {
return fmt.Errorf("查询 EXIF 信息失败 %s: %w", path, err)
}
tags, _, err := exif.GetFlatExifDataUniversalSearch(rawExif, nil, true)
if err != nil {
return fmt.Errorf("获取 EXIF 信息失败 %s: %w", path, err)
}
ifd0Map := map[string]string{
// 根 IFD (IFD0) 标签
"IFD.Software": "生成软件名",
"IFD.ImageWidth": "图像宽度",
"IFD.ImageLength": "图像高度",
"IFD.Make": "相机制造商",
"IFD.Model": "相机型号",
"IFD.Orientation": "图像方向",
"IFD.YResolution": "Y轴分辨率",
"IFD.XResolution": "X轴分辨率",
"IFD.ResolutionUnit": "分辨率单位",
"IFD.DateTime": "最后修改时间",
"IFD.ImageDescription": "图像描述",
"IFD.Artist": "图像作者",
"IFD.Copyright": "版权信息",
"IFD.HostComputer": "创建图像的计算机",
"IFD.BitsPerSample": "每个颜色分量的位数",
"IFD.Compression": "压缩方案",
"IFD.PhotometricInterpretation": "像素组成方式",
"IFD.StripOffsets": "图像数据条带偏移量",
"IFD.SamplesPerPixel": "每个像素的分量数",
"IFD.RowsPerStrip": "每个条带的行数",
"IFD.StripByteCounts": "每个条带的字节数",
"IFD.PlanarConfiguration": "数据排列方式",
// Exif 子 IFD 标签
"IFD/Exif.DateTimeOriginal": "原始拍摄时间",
"IFD/Exif.DateTimeDigitized": "数字化时间",
"IFD/Exif.OffsetTimeOriginal": "原始拍摄时区偏移",
"IFD/Exif.ExposureTime": "曝光时间",
"IFD/Exif.FNumber": "光圈F值",
"IFD/Exif.ExposureProgram": "曝光程序",
"IFD/Exif.ISOSpeedRatings": "ISO感光度",
"IFD/Exif.ShutterSpeedValue": "快门速度APEX值",
"IFD/Exif.ApertureValue": "光圈值APEX值",
"IFD/Exif.BrightnessValue": "亮度值APEX值",
"IFD/Exif.ExposureBiasValue": "曝光补偿值EV",
"IFD/Exif.MeteringMode": "测光模式",
"IFD/Exif.Flash": "闪光灯状态",
"IFD/Exif.FocalLength": "焦距(毫米)",
"IFD/Exif.SubjectDistance": "拍摄主体距离(米)",
"IFD/Exif.FlashEnergy": "闪光灯强度",
"IFD/Exif.SpatialFrequencyResponse": "空间频率响应",
"IFD/Exif.FocalPlaneXResolution": "焦平面X分辨率",
"IFD/Exif.FocalPlaneYResolution": "焦平面Y分辨率",
"IFD/Exif.FocalPlaneResolutionUnit": "焦平面分辨率单位",
"IFD/Exif.FileSource": "文件来源",
"IFD/Exif.SceneType": "场景类型",
"IFD/Exif.CFAPattern": "CFA彩色滤镜阵列模式",
"IFD/Exif.CustomRendered": "自定义图像处理",
"IFD/Exif.ExposureMode": "曝光模式",
"IFD/Exif.WhiteBalance": "白平衡",
"IFD/Exif.DigitalZoomRatio": "数码变焦比率",
"IFD/Exif.SceneCaptureType": "场景拍摄类型",
"IFD/Exif.GainControl": "增益控制",
"IFD/Exif.Contrast": "对比度",
"IFD/Exif.Saturation": "饱和度",
"IFD/Exif.Sharpness": "锐度",
"IFD/Exif.DeviceSettingDescription": "设备设置描述",
"IFD/Exif.SubjectDistanceRange": "主体距离范围",
// GPS 子 IFD 标签 (可选添加)
"IFD/GPSInfo.GPSLatitude": "纬度",
"IFD/GPSInfo.GPSLongitude": "经度",
"IFD/GPSInfo.GPSAltitude": "海拔高度",
"IFD/GPSInfo.GPSTimeStamp": "GPS时间戳",
"IFD/GPSInfo.GPSDateStamp": "GPS日期戳",
"IFD/GPSInfo.GPSDOP": "定位精度",
}
for _, tag := range tags {
sample := "%s.%v {%#04x} [%v] %-10v\n"
if name, ok := ifd0Map[tag.IfdPath+"."+tag.TagName]; ok {
sample = "%s.%v {%#04x} [%v] (" + name + ") %-10v\n"
}
switch tag.IfdPath + "." + tag.TagName {
case "IFD.ResolutionUnit":
switch tag.Value.([]uint16)[0] {
case 1:
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) 没有单位\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
case 2:
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) 英寸\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
case 3:
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) 像素\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
default:
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) 未知单位:%v\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, tag.Value)
}
case "IFD/GPSInfo.GPSLatitude", "IFD/GPSInfo.GPSLongitude":
switch val := tag.Value.(type) {
case []exifcommon.Rational:
var value string
for i, v := range val {
switch i {
case 0:
value += fmt.Sprintf("%.0f°", float64(v.Numerator)/float64(v.Denominator))
case 1:
value += fmt.Sprintf("%.0f", float64(v.Numerator)/float64(v.Denominator))
case 2:
value += fmt.Sprintf("%.3f″", float64(v.Numerator)/float64(v.Denominator))
}
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, value)
default:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
case "IFD/GPSInfo.GPSTimeStamp":
switch val := tag.Value.(type) {
case []exifcommon.Rational:
var value string
for _, v := range val {
value += fmt.Sprintf("%.0f:", float64(v.Numerator)/float64(v.Denominator))
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, value[:len(value)-1]) // 去掉最后一个冒号
default:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
default:
switch val := tag.Value.(type) {
case []exifcommon.Rational:
switch tag.IfdPath + "." + tag.TagName {
case "IFD/Exif.ExposureTime":
sample += "秒"
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, float64(val[0].Numerator)/float64(val[0].Denominator))
case []uint16:
if len(val) == 1 {
switch tag.IfdPath + "." + tag.TagName {
case "IFD/Exif.ExposureProgram":
valStr := ""
switch val[0] {
case 0:
valStr = "未知"
case 1:
valStr = "手动"
case 2:
valStr = "自动"
case 3:
valStr = "光圈优先"
case 4:
valStr = "快门优先"
case 5:
valStr = "创意(慢速)"
case 6:
valStr = "动作(高速)"
case 7:
valStr = "肖像"
case 8:
valStr = "景观"
case 9:
valStr = "Bulb"
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, valStr)
continue
case "IFD/Exif.MeteringMode":
/*
0 = Unknown
1 = Average
2 = Center-weighted average
3 = Spot
4 = Multi-spot
5 = Multi-segment
6 = Partial
255 = Other
*/
valStr := ""
switch val[0] {
case 0:
valStr = "未知"
case 1:
valStr = "平均"
case 2:
valStr = "中央重点平均"
case 3:
valStr = "点测光"
case 4:
valStr = "多点测光"
case 5:
valStr = "多段测光"
case 6:
valStr = "局部测光"
case 255:
valStr = "其他"
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, valStr)
continue
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val[0])
} else {
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
case []uint32:
if len(val) == 1 {
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val[0])
} else {
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
case []uint8:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, hex.EncodeToString(val))
case string:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
default:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
}
}
return nil
}