diff --git a/pkg/mediameta/exif.go b/pkg/mediameta/exif.go index df55c885..b682197e 100644 --- a/pkg/mediameta/exif.go +++ b/pkg/mediameta/exif.go @@ -263,90 +263,51 @@ func (e *exifExtractor) Extract(ctx context.Context, ext string, source entityso return metas, nil } -func ExtractExifMap(exifMap map[string]string, gpsTime time.Time) []driver.MediaMeta { - metas := make([]driver.MediaMeta, 0) - if value, ok := exifMap["Artist"]; ok { - metas = append(metas, driver.MediaMeta{ - Key: Artist, - Value: SanitizeMeta(value), - }) - } - - if value, ok := exifMap["Copyright"]; ok { - metas = append(metas, driver.MediaMeta{ - Key: Copyright, - Value: SanitizeString(value), - }) - } - - cameraMode := "" - if value, ok := exifMap["CameraModel"]; ok && !IsUInt(value) { - cameraMode = SanitizeString(value) - } else if value, ok = exifMap["Model"]; ok && !IsUInt(value) { - cameraMode = SanitizeString(value) - } else if value, ok = exifMap["UniqueCameraModel"]; ok && !IsUInt(value) { - cameraMode = SanitizeString(value) - } - if cameraMode != "" { - metas = append(metas, driver.MediaMeta{ - Key: CameraModel, - Value: cameraMode, - }) - } - - cameraMake := "" - if value, ok := exifMap["CameraMake"]; ok && !IsUInt(value) { - cameraMake = SanitizeString(value) - } else if value, ok = exifMap["Make"]; ok && !IsUInt(value) { - cameraMake = SanitizeString(value) - } - if cameraMake != "" { - metas = append(metas, driver.MediaMeta{ - Key: CameraMake, - Value: cameraMake, - }) - } - - if value, ok := exifMap["CameraOwnerName"]; ok { - metas = append(metas, driver.MediaMeta{ - Key: CameraOwnerName, - Value: SanitizeString(value), - }) - } - - if value, ok := exifMap["BodySerialNumber"]; ok { - metas = append(metas, driver.MediaMeta{ - Key: BodySerialNumber, - Value: SanitizeString(value), - }) - } - - if value, ok := exifMap["LensMake"]; ok && !IsUInt(value) { - metas = append(metas, driver.MediaMeta{ - Key: LensMake, - Value: SanitizeString(value), - }) - } +// exifTagTable maps EXIF tag names (in fallback order) to MediaMeta keys. +// skipUint entries ignore pure-integer values, which vendor APIs sometimes +// return instead of the real string value. +var exifTagTable = []struct { + keys []string + metaKey string + clean func(string) string + skipUint bool +}{ + {[]string{"Artist"}, Artist, SanitizeMeta, false}, + {[]string{"Copyright"}, Copyright, SanitizeString, false}, + {[]string{"CameraModel", "Model", "UniqueCameraModel"}, CameraModel, SanitizeString, true}, + {[]string{"CameraMake", "Make"}, CameraMake, SanitizeString, true}, + {[]string{"CameraOwnerName"}, CameraOwnerName, SanitizeString, false}, + {[]string{"BodySerialNumber"}, BodySerialNumber, SanitizeString, false}, + {[]string{"LensMake"}, LensMake, SanitizeString, true}, + {[]string{"LensModel", "Lens"}, LensModel, SanitizeString, true}, + {[]string{"Software"}, Software, SanitizeString, false}, + {[]string{"ISOSpeedRatings"}, ISOSpeedRatings, nil, false}, + {[]string{"PixelXDimension", "ImageWidth"}, PixelXDimension, nil, false}, + {[]string{"PixelYDimension", "ImageLength"}, PixelYDimension, nil, false}, + {[]string{"ImageDescription"}, ImageDescription, SanitizeDescription, false}, + {[]string{"ProjectionType"}, ProjectionType, SanitizeString, false}, +} - lens := "" - if value, ok := exifMap["LensModel"]; ok && !IsUInt(value) { - lens = SanitizeString(value) - } else if value, ok = exifMap["Lens"]; ok && !IsUInt(value) { - lens = SanitizeString(value) - } - if lens != "" { - metas = append(metas, driver.MediaMeta{ - Key: LensModel, - Value: lens, - }) +func extractExifTags(metas []driver.MediaMeta, exifMap map[string]string, from, to int) []driver.MediaMeta { + for _, e := range exifTagTable[from:to] { + for _, k := range e.keys { + v, ok := exifMap[k] + if !ok || (e.skipUint && IsUInt(v)) { + continue + } + if e.clean != nil { + v = e.clean(v) + } + metas = append(metas, driver.MediaMeta{Key: e.metaKey, Value: v}) + break + } } + return metas +} - if value, ok := exifMap["Software"]; ok { - metas = append(metas, driver.MediaMeta{ - Key: Software, - Value: SanitizeString(value), - }) - } +func ExtractExifMap(exifMap map[string]string, gpsTime time.Time) []driver.MediaMeta { + metas := make([]driver.MediaMeta, 0) + metas = extractExifTags(metas, exifMap, 0, 9) if value, ok := exifMap["ExposureTime"]; ok { value = strings.TrimSuffix(value, " sec.") @@ -427,38 +388,9 @@ func ExtractExifMap(exifMap map[string]string, gpsTime time.Time) []driver.Media }) } - if value, ok := exifMap["ISOSpeedRatings"]; ok { - metas = append(metas, driver.MediaMeta{ - Key: ISOSpeedRatings, - Value: value, - }) - } + metas = extractExifTags(metas, exifMap, 9, 10) - width := "" - if value, ok := exifMap["PixelXDimension"]; ok { - width = value - } else if value, ok := exifMap["ImageWidth"]; ok { - width = value - } - if width != "" { - metas = append(metas, driver.MediaMeta{ - Key: PixelXDimension, - Value: width, - }) - } - - height := "" - if value, ok := exifMap["PixelYDimension"]; ok { - height = value - } else if value, ok := exifMap["ImageLength"]; ok { - height = value - } - if height != "" { - metas = append(metas, driver.MediaMeta{ - Key: PixelYDimension, - Value: height, - }) - } + metas = extractExifTags(metas, exifMap, 10, 12) orientation := "1" if value, ok := exifMap["Orientation"]; ok { @@ -498,19 +430,7 @@ func ExtractExifMap(exifMap map[string]string, gpsTime time.Time) []driver.Media }) } - if value, ok := exifMap["ImageDescription"]; ok { - metas = append(metas, driver.MediaMeta{ - Key: ImageDescription, - Value: SanitizeDescription(value), - }) - } - - if value, ok := exifMap["ProjectionType"]; ok { - metas = append(metas, driver.MediaMeta{ - Key: ProjectionType, - Value: SanitizeString(value), - }) - } + metas = extractExifTags(metas, exifMap, 12, 14) return metas } diff --git a/pkg/mediameta/exif_extract_test.go b/pkg/mediameta/exif_extract_test.go new file mode 100644 index 00000000..19a8cd27 --- /dev/null +++ b/pkg/mediameta/exif_extract_test.go @@ -0,0 +1,98 @@ +package mediameta + +import ( + "testing" + "time" + + "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver" + "github.com/stretchr/testify/assert" +) + +func findMeta(metas []driver.MediaMeta, key string) (driver.MediaMeta, bool) { + for _, m := range metas { + if m.Key == key { + return m, true + } + } + return driver.MediaMeta{}, false +} + +func TestExtractExifMap(t *testing.T) { + a := assert.New(t) + metas := ExtractExifMap(map[string]string{ + "Artist": "ann", + "CameraModel": "12345", // pure-uint vendor garbage, must fall back + "Model": "X-T5", + "CameraMake": "Fuji", + "LensModel": "9999", // uint, falls back + "Lens": "XF35", + "Software": "darktable", + "PixelXDimension": "6000", + "ImageLength": "4000", + "ImageDescription": "desc", + "ProjectionType": "equirectangular", + "DateTimeOriginal": "2020:01:02 03:04:05", + "ISOSpeedRatings": "200", + "Flash": "1", + "FNumber": "28/10", + "Orientation": "6", + "ImageWidth": "9999", // shadowed by PixelXDimension + }, time.Time{}) + + v, ok := findMeta(metas, CameraModel) + a.True(ok) + a.Equal("X-T5", v.Value) + + v, ok = findMeta(metas, LensModel) + a.True(ok) + a.Equal("XF35", v.Value) + + v, ok = findMeta(metas, PixelXDimension) + a.True(ok) + a.Equal("6000", v.Value) + + v, ok = findMeta(metas, PixelYDimension) + a.True(ok) + a.Equal("4000", v.Value) + + v, ok = findMeta(metas, Orientation) + a.True(ok) + a.Equal("6", v.Value) + + v, ok = findMeta(metas, TakenAt) + a.True(ok) + a.Equal("2020-01-02T03:04:05Z", v.Value) + + v, ok = findMeta(metas, Flash) + a.True(ok) + a.Equal("1", v.Value) + + v, ok = findMeta(metas, FNumber) + a.True(ok) + a.Equal("2.800000", v.Value) + + v, ok = findMeta(metas, ImageDescription) + a.True(ok) + a.Equal("desc", v.Value) + + v, ok = findMeta(metas, ProjectionType) + a.True(ok) + a.Equal("equirectangular", v.Value) + + _, ok = findMeta(metas, Copyright) + a.False(ok) +} + +func TestExtractExifMapDefaults(t *testing.T) { + a := assert.New(t) + metas := ExtractExifMap(map[string]string{}, time.Time{}) + + // Orientation is always emitted, defaulting to "1". + v, ok := findMeta(metas, Orientation) + a.True(ok) + a.Equal("1", v.Value) + + // No date tags and zero GPS time -> no TakenAt. + _, ok = findMeta(metas, TakenAt) + a.False(ok) +}