refactor(mediameta): table-driven EXIF tag extraction

ExtractExifMap repeated the same lookup-append block ~14 times, with
first-non-uint fallback chains for camera/lens tags. A declarative tag
table plus a small extraction loop covers both patterns; the complex
value transforms (exposure, focal length, dates) stay explicit.
Includes a regression test covering fallbacks, defaults and formats.

Authored By: TDvorak <info@tdvorak.dev>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3582/head
Tomas Dvorak 2 weeks ago
parent 4de3d4bb3b
commit 26fcb1b2eb

@ -263,90 +263,51 @@ func (e *exifExtractor) Extract(ctx context.Context, ext string, source entityso
return metas, nil return metas, nil
} }
func ExtractExifMap(exifMap map[string]string, gpsTime time.Time) []driver.MediaMeta { // exifTagTable maps EXIF tag names (in fallback order) to MediaMeta keys.
metas := make([]driver.MediaMeta, 0) // skipUint entries ignore pure-integer values, which vendor APIs sometimes
if value, ok := exifMap["Artist"]; ok { // return instead of the real string value.
metas = append(metas, driver.MediaMeta{ var exifTagTable = []struct {
Key: Artist, keys []string
Value: SanitizeMeta(value), metaKey string
}) clean func(string) string
} skipUint bool
}{
if value, ok := exifMap["Copyright"]; ok { {[]string{"Artist"}, Artist, SanitizeMeta, false},
metas = append(metas, driver.MediaMeta{ {[]string{"Copyright"}, Copyright, SanitizeString, false},
Key: Copyright, {[]string{"CameraModel", "Model", "UniqueCameraModel"}, CameraModel, SanitizeString, true},
Value: SanitizeString(value), {[]string{"CameraMake", "Make"}, CameraMake, SanitizeString, true},
}) {[]string{"CameraOwnerName"}, CameraOwnerName, SanitizeString, false},
} {[]string{"BodySerialNumber"}, BodySerialNumber, SanitizeString, false},
{[]string{"LensMake"}, LensMake, SanitizeString, true},
cameraMode := "" {[]string{"LensModel", "Lens"}, LensModel, SanitizeString, true},
if value, ok := exifMap["CameraModel"]; ok && !IsUInt(value) { {[]string{"Software"}, Software, SanitizeString, false},
cameraMode = SanitizeString(value) {[]string{"ISOSpeedRatings"}, ISOSpeedRatings, nil, false},
} else if value, ok = exifMap["Model"]; ok && !IsUInt(value) { {[]string{"PixelXDimension", "ImageWidth"}, PixelXDimension, nil, false},
cameraMode = SanitizeString(value) {[]string{"PixelYDimension", "ImageLength"}, PixelYDimension, nil, false},
} else if value, ok = exifMap["UniqueCameraModel"]; ok && !IsUInt(value) { {[]string{"ImageDescription"}, ImageDescription, SanitizeDescription, false},
cameraMode = SanitizeString(value) {[]string{"ProjectionType"}, ProjectionType, SanitizeString, false},
} }
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 { func extractExifTags(metas []driver.MediaMeta, exifMap map[string]string, from, to int) []driver.MediaMeta {
metas = append(metas, driver.MediaMeta{ for _, e := range exifTagTable[from:to] {
Key: BodySerialNumber, for _, k := range e.keys {
Value: SanitizeString(value), v, ok := exifMap[k]
}) if !ok || (e.skipUint && IsUInt(v)) {
continue
} }
if e.clean != nil {
if value, ok := exifMap["LensMake"]; ok && !IsUInt(value) { v = e.clean(v)
metas = append(metas, driver.MediaMeta{
Key: LensMake,
Value: SanitizeString(value),
})
} }
metas = append(metas, driver.MediaMeta{Key: e.metaKey, Value: v})
lens := "" break
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,
})
} }
return metas
}
if value, ok := exifMap["Software"]; ok { func ExtractExifMap(exifMap map[string]string, gpsTime time.Time) []driver.MediaMeta {
metas = append(metas, driver.MediaMeta{ metas := make([]driver.MediaMeta, 0)
Key: Software, metas = extractExifTags(metas, exifMap, 0, 9)
Value: SanitizeString(value),
})
}
if value, ok := exifMap["ExposureTime"]; ok { if value, ok := exifMap["ExposureTime"]; ok {
value = strings.TrimSuffix(value, " sec.") 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 = extractExifTags(metas, exifMap, 9, 10)
metas = append(metas, driver.MediaMeta{
Key: ISOSpeedRatings,
Value: value,
})
}
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 := "" metas = extractExifTags(metas, exifMap, 10, 12)
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,
})
}
orientation := "1" orientation := "1"
if value, ok := exifMap["Orientation"]; ok { 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 = extractExifTags(metas, exifMap, 12, 14)
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),
})
}
return metas return metas
} }

@ -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)
}
Loading…
Cancel
Save