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 }