Cards View: Improve camera and lens information #2040 #3077 #3816

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2023-10-13 11:35:43 +02:00
parent 57b1bb4b7d
commit 09ad17d10a
10 changed files with 148 additions and 115 deletions

View File

@ -3183,9 +3183,9 @@
}
},
"node_modules/@types/node": {
"version": "20.8.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz",
"integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==",
"version": "20.8.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.5.tgz",
"integrity": "sha512-SPlobFgbidfIeOYlzXiEjSYeIJiOCthv+9tSQVpvk4PAdIIc+2SmjNVzWXk9t0Y7dl73Zdf+OgXKHX9XtkqUpw==",
"dependencies": {
"undici-types": "~5.25.1"
}
@ -5547,9 +5547,9 @@
}
},
"node_modules/define-data-property": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz",
"integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
"dependencies": {
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
@ -5793,9 +5793,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.551",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz",
"integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw=="
"version": "1.4.553",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.553.tgz",
"integrity": "sha512-HiRdtyKS2+VhiXvjhMvvxiMC33FJJqTA5EB2YHgFZW6v7HkK4Q9Ahv2V7O2ZPgAjw+MyCJVMQvigj13H8t+wvA=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
@ -7561,9 +7561,9 @@
}
},
"node_modules/fraction.js": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
"integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==",
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"engines": {
"node": "*"
},
@ -7644,9 +7644,12 @@
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/function.prototype.name": {
"version": "1.1.6",
@ -9604,9 +9607,9 @@
}
},
"node_modules/magic-string": {
"version": "0.30.4",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz",
"integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==",
"version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
@ -9635,9 +9638,9 @@
}
},
"node_modules/maplibre-gl": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.4.1.tgz",
"integrity": "sha512-RPcdaiZ52G3X+PaHQxqQ1d4I8iTIPRl4OXhPU/3o37kDf+ImLXpUVZj4p0qBCGm71n79daVzaCMG9QxfSSQbnQ==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.5.0.tgz",
"integrity": "sha512-kPEBz6r1LBOZjUpFy+4wZU5Nvnkr60wBtYN/JD6N7oaA4Prpe21afYKxi1oWzSPSfspS1tWNF18GlpF2XcmNSA==",
"dependencies": {
"@mapbox/geojson-rewind": "^0.5.2",
"@mapbox/jsonlint-lines-primitives": "^2.0.2",

View File

@ -922,7 +922,7 @@ export class Photo extends RestModel {
return info.join(", ");
});
// Example: EOS 700D, CR2, 5184 × 3456, 20.5 MB
// Example: Apple iPhone 12 Pro Max, DNG, 4032 × 3024, 32.9 MB
getPhotoInfo = () => {
let file = this.originalFile() || this.videoFile();
return this.generatePhotoInfo(this.Camera, this.CameraMake, this.CameraModel, file);
@ -937,12 +937,14 @@ export class Photo extends RestModel {
} else {
info.push(camera.Make + " " + camera.Model);
}
} else if (cameraModel && cameraMake) {
if (cameraModel.length > 7) {
} else if (cameraMake && cameraModel) {
if ((cameraMake + cameraModel).length > 21) {
info.push(cameraModel);
} else {
info.push(cameraMake + " " + cameraModel);
}
} else if (cameraModel) {
info.push(cameraModel);
}
if (file && file.Width && file.Codec) {
@ -958,7 +960,7 @@ export class Photo extends RestModel {
return info.join(", ");
});
// Example: EF24-105mm f/4L IS USM, 50mm, ƒ/8, ISO200, 1/125
// Example: iPhone 12 Pro Max 5.1mm ƒ/1.6, 26mm, ISO32, 1/4525
getLensInfo = () => {
return this.generateLensInfo(
this.Lens,
@ -984,22 +986,18 @@ export class Photo extends RestModel {
} else {
info.push(lens.Make + " " + lens.Model);
}
} else if (lensModel && lensId > 1) {
if (
cameraModel &&
lensModel.startsWith(cameraModel + " ") &&
cameraModel.length < lensModel.length + 5
) {
lensModel = Util.ucFirst(lensModel.substring(cameraModel.length + 1));
}
if (lensModel.length > 45) {
return lensModel;
} else if (lensId > 1) {
if (!lensModel && !!lensMake) {
info.push(lensMake);
} else {
info.push(lensModel);
lensModel = lensModel.replace("f/", "ƒ/");
if (lensModel.length > 45) {
return lensModel;
} else {
info.push(lensModel);
}
}
} else if (lensMake) {
info.push(lensMake);
}
if (focalLength) {
@ -1010,7 +1008,7 @@ export class Photo extends RestModel {
info.push("ƒ/" + fNumber);
}
if (iso) {
if (iso && lensModel.length < 27) {
info.push("ISO" + iso);
}

View File

@ -47,10 +47,10 @@ func CreateUnknownCamera() {
UnknownCamera = *FirstOrCreateCamera(&UnknownCamera)
}
// NewCamera creates a camera entity from a model name and a make name.
func NewCamera(modelName string, makeName string) *Camera {
modelName = strings.TrimSpace(modelName)
// NewCamera creates a new camera entity from make and model names.
func NewCamera(makeName string, modelName string) *Camera {
makeName = strings.TrimSpace(makeName)
modelName = strings.TrimSpace(modelName)
if modelName == "" && makeName == "" {
return &UnknownCamera

View File

@ -11,7 +11,7 @@ func (m CameraMap) Get(name string) Camera {
return result
}
return *NewCamera(name, "")
return *NewCamera("", name)
}
func (m CameraMap) Pointer(name string) *Camera {
@ -19,7 +19,7 @@ func (m CameraMap) Pointer(name string) *Camera {
return &result
}
return NewCamera(name, "")
return NewCamera("", name)
}
var CameraFixtures = CameraMap{

View File

@ -25,7 +25,7 @@ func TestFirstOrCreateCamera(t *testing.T) {
assert.Equal(t, UnknownID, result.CameraSlug)
})
t.Run("existing camera", func(t *testing.T) {
camera := NewCamera("iPhone SE", "Apple")
camera := NewCamera("Apple", "iPhone SE")
result := FirstOrCreateCamera(camera)
@ -49,13 +49,13 @@ func TestFirstOrCreateCamera(t *testing.T) {
}
func TestNewCamera(t *testing.T) {
t.Run("unknown camera", func(t *testing.T) {
t.Run("Unknown", func(t *testing.T) {
camera := NewCamera("", "")
assert.Equal(t, &UnknownCamera, camera)
})
t.Run("model EOS 6D make Canon", func(t *testing.T) {
camera := NewCamera("EOS 6D", "Canon")
t.Run("CanonEOS6D", func(t *testing.T) {
camera := NewCamera("Canon", "EOS 6D")
expected := &Camera{
CameraSlug: "canon-eos-6d",
@ -66,8 +66,8 @@ func TestNewCamera(t *testing.T) {
assert.Equal(t, expected, camera)
})
t.Run("model with prefix make Panasonic", func(t *testing.T) {
camera := NewCamera("Panasonic Lumix", "Panasonic")
t.Run("PanasonicLumix", func(t *testing.T) {
camera := NewCamera("Panasonic", "Panasonic Lumix")
expected := &Camera{
CameraSlug: "panasonic-lumix",
@ -78,8 +78,8 @@ func TestNewCamera(t *testing.T) {
assert.Equal(t, expected, camera)
})
t.Run("model TG-4 make Unknown", func(t *testing.T) {
camera := NewCamera("TG-4", "")
t.Run("TG4", func(t *testing.T) {
camera := NewCamera("", "TG-4")
expected := &Camera{
CameraSlug: "tg-4",
@ -90,23 +90,16 @@ func TestNewCamera(t *testing.T) {
assert.Equal(t, expected, camera)
})
t.Run("model Unknown make Unknown", func(t *testing.T) {
camera := NewCamera("", "")
assert.Equal(t, &UnknownCamera, camera)
})
t.Run("OLYMPUS", func(t *testing.T) {
camera := NewCamera("", "OLYMPUS OPTICAL CO.,LTD")
t.Run("Olympus", func(t *testing.T) {
camera := NewCamera("OLYMPUS OPTICAL CO.,LTD", "")
assert.Equal(t, "olympus", camera.CameraSlug)
assert.Equal(t, "Olympus", camera.CameraName)
assert.Equal(t, "Olympus", camera.CameraMake)
assert.Equal(t, "", camera.CameraModel)
})
t.Run("P30", func(t *testing.T) {
camera := NewCamera("ELE-AL00", "Huawei")
t.Run("HuaweiP30", func(t *testing.T) {
camera := NewCamera("Huawei", "ELE-AL00")
assert.Equal(t, "huawei-p30", camera.CameraSlug)
assert.Equal(t, "HUAWEI P30", camera.CameraName)
@ -116,43 +109,43 @@ func TestNewCamera(t *testing.T) {
}
func TestCamera_String(t *testing.T) {
t.Run("model XXX make Nikon", func(t *testing.T) {
camera := NewCamera("XXX", "Nikon")
cameraString := camera.String()
assert.Equal(t, "'NIKON XXX'", cameraString)
})
t.Run("model XXX make Unknown", func(t *testing.T) {
camera := NewCamera("XXX", "")
cameraString := camera.String()
assert.Equal(t, "XXX", cameraString)
})
t.Run("model Unknown make XXX", func(t *testing.T) {
camera := NewCamera("", "test")
cameraString := camera.String()
assert.Equal(t, "test", cameraString)
})
t.Run("model Unknown make Unknown", func(t *testing.T) {
t.Run("Unknown", func(t *testing.T) {
camera := NewCamera("", "")
cameraString := camera.String()
assert.Equal(t, "Unknown", cameraString)
})
t.Run("Nikon", func(t *testing.T) {
camera := NewCamera("Nikon", "foo")
cameraString := camera.String()
assert.Equal(t, "'NIKON foo'", cameraString)
})
t.Run("Foo", func(t *testing.T) {
camera := NewCamera("", "Foo")
cameraString := camera.String()
assert.Equal(t, "Foo", cameraString)
})
t.Run("Test", func(t *testing.T) {
camera := NewCamera("test", "")
cameraString := camera.String()
assert.Equal(t, "test", cameraString)
})
}
func TestCamera_Scanner(t *testing.T) {
t.Run("model XXX make Nikon", func(t *testing.T) {
camera := NewCamera("XXX", "Nikon")
assert.False(t, camera.Scanner())
})
t.Run("MS Scanner", func(t *testing.T) {
camera := NewCamera("MS Scanner", "")
assert.True(t, camera.Scanner())
})
t.Run("model Unknown make XXX", func(t *testing.T) {
camera := NewCamera("", "test")
assert.False(t, camera.Scanner())
})
t.Run("model Unknown make Unknown", func(t *testing.T) {
t.Run("Unknown", func(t *testing.T) {
camera := NewCamera("", "")
assert.False(t, camera.Scanner())
})
t.Run("Foo", func(t *testing.T) {
camera := NewCamera("foo", "")
assert.False(t, camera.Scanner())
})
t.Run("NikonFoo", func(t *testing.T) {
camera := NewCamera("Nikon", "Foo")
assert.False(t, camera.Scanner())
})
t.Run("MSScanner", func(t *testing.T) {
camera := NewCamera("", "MS Scanner")
assert.True(t, camera.Scanner())
})
}

View File

@ -47,10 +47,10 @@ func CreateUnknownLens() {
UnknownLens = *FirstOrCreateLens(&UnknownLens)
}
// NewLens creates a new lens in database
func NewLens(modelName string, makeName string) *Lens {
modelName = strings.TrimSpace(modelName)
// NewLens creates a new camera lens entity from make and model names.
func NewLens(makeName string, modelName string) *Lens {
makeName = strings.TrimSpace(makeName)
modelName = strings.TrimSpace(modelName)
if modelName == "" && makeName == "" {
return &UnknownLens
@ -63,10 +63,14 @@ func NewLens(modelName string, makeName string) *Lens {
makeName = n
}
// Remove duplicate make from model name.
if strings.HasPrefix(modelName, makeName) {
modelName = strings.TrimSpace(modelName[len(makeName):])
}
// Remove ignored substrings from model name.
modelName = LensModelIgnore.ReplaceAllString(modelName, " ")
var name []string
if makeName != "" {

View File

@ -11,7 +11,7 @@ func (m LensMap) Get(name string) Lens {
return result
}
return *NewLens(name, "")
return *NewLens("", name)
}
func (m LensMap) Pointer(name string) *Lens {
@ -19,7 +19,7 @@ func (m LensMap) Pointer(name string) *Lens {
return &result
}
return NewLens(name, "")
return NewLens("", name)
}
var LensFixtures = LensMap{

View File

@ -0,0 +1,6 @@
package entity
import "regexp"
// LensModelIgnore is a regular expression that matches lens model substrings to be ignored.
var LensModelIgnore = regexp.MustCompile(`(?i)\sback.*camera\s`)

View File

@ -7,14 +7,7 @@ import (
)
func TestNewLens(t *testing.T) {
t.Run("name F500-99 make Canon", func(t *testing.T) {
lens := NewLens("F500-99", "Canon")
assert.Equal(t, "canon-f500-99", lens.LensSlug)
assert.Equal(t, "Canon F500-99", lens.LensName)
assert.Equal(t, "F500-99", lens.LensModel)
assert.Equal(t, "Canon", lens.LensMake)
})
t.Run("name Unknown make Unknown", func(t *testing.T) {
t.Run("Unknown", func(t *testing.T) {
lens := NewLens("", "")
assert.Equal(t, UnknownID, lens.LensSlug)
assert.Equal(t, "Unknown", lens.LensName)
@ -23,22 +16,58 @@ func TestNewLens(t *testing.T) {
assert.Equal(t, UnknownLens.LensSlug, lens.LensSlug)
assert.Equal(t, &UnknownLens, lens)
})
t.Run("Canon", func(t *testing.T) {
lens := NewLens("Canon", "F500-99")
assert.Equal(t, "canon-f500-99", lens.LensSlug)
assert.Equal(t, "Canon F500-99", lens.LensName)
assert.Equal(t, "F500-99", lens.LensModel)
assert.Equal(t, "Canon", lens.LensMake)
})
t.Run("iPhoneXS", func(t *testing.T) {
lens := NewLens("Apple", "iPhone XS back camera 4.25mm f/1.8")
assert.Equal(t, "apple-iphone-xs-4-25mm-f-1-8", lens.LensSlug)
assert.Equal(t, "Apple iPhone XS 4.25mm f/1.8", lens.LensName)
assert.Equal(t, "iPhone XS 4.25mm f/1.8", lens.LensModel)
assert.Equal(t, "Apple", lens.LensMake)
})
t.Run("iPhone12mini", func(t *testing.T) {
lens := NewLens("Apple", "iPhone 12 mini back dual wide camera 4.2mm f/1.6")
assert.Equal(t, "apple-iphone-12-mini-4-2mm-f-1-6", lens.LensSlug)
assert.Equal(t, "Apple iPhone 12 mini 4.2mm f/1.6", lens.LensName)
assert.Equal(t, "iPhone 12 mini 4.2mm f/1.6", lens.LensModel)
assert.Equal(t, "Apple", lens.LensMake)
})
t.Run("iPhone12UltraWide", func(t *testing.T) {
lens := NewLens("Apple", "iPhone 12 back dual wide camera 1.55mm f/2.4")
assert.Equal(t, "apple-iphone-12-1-55mm-f-2-4", lens.LensSlug)
assert.Equal(t, "Apple iPhone 12 1.55mm f/2.4", lens.LensName)
assert.Equal(t, "iPhone 12 1.55mm f/2.4", lens.LensModel)
assert.Equal(t, "Apple", lens.LensMake)
})
t.Run("iPhone14ProMax", func(t *testing.T) {
lens := NewLens("Apple", "iPhone 14 Pro Max back triple camera 9mm f/2.8")
assert.Equal(t, "apple-iphone-14-pro-max-9mm-f-2-8", lens.LensSlug)
assert.Equal(t, "Apple iPhone 14 Pro Max 9mm f/2.8", lens.LensName)
assert.Equal(t, "iPhone 14 Pro Max 9mm f/2.8", lens.LensModel)
assert.Equal(t, "Apple", lens.LensMake)
assert.Equal(t, "apple-iphone-14-pro-max-9mm-f-2-8", lens.LensSlug)
})
}
func TestLens_TableName(t *testing.T) {
lens := NewLens("F500-99", "Canon")
lens := NewLens("Canon", "F500-99")
tableName := lens.TableName()
assert.Equal(t, "lenses", tableName)
}
func TestLens_String(t *testing.T) {
lens := NewLens("F500-99", "samsung")
lens := NewLens("samsung", "F500-99")
assert.Equal(t, "'Samsung F500-99'", lens.String())
}
func TestFirstOrCreateLens(t *testing.T) {
t.Run("existing lens", func(t *testing.T) {
lens := NewLens("iPhone SE", "Apple")
lens := NewLens("Apple", "iPhone SE")
result := FirstOrCreateLens(lens)

View File

@ -542,8 +542,8 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
photo.PhotoResolution = res
}
photo.SetCamera(entity.FirstOrCreateCamera(entity.NewCamera(m.CameraModel(), m.CameraMake())), entity.SrcMeta)
photo.SetLens(entity.FirstOrCreateLens(entity.NewLens(m.LensModel(), m.LensMake())), entity.SrcMeta)
photo.SetCamera(entity.FirstOrCreateCamera(entity.NewCamera(m.CameraMake(), m.CameraModel())), entity.SrcMeta)
photo.SetLens(entity.FirstOrCreateLens(entity.NewLens(m.LensMake(), m.LensModel())), entity.SrcMeta)
photo.SetExposure(m.FocalLength(), m.FNumber(), m.Iso(), m.Exposure(), entity.SrcMeta)
}
@ -677,8 +677,8 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
photo.PhotoDuration = file.FileDuration
}
photo.SetCamera(entity.FirstOrCreateCamera(entity.NewCamera(m.CameraModel(), m.CameraMake())), entity.SrcMeta)
photo.SetLens(entity.FirstOrCreateLens(entity.NewLens(m.LensModel(), m.LensMake())), entity.SrcMeta)
photo.SetCamera(entity.FirstOrCreateCamera(entity.NewCamera(m.CameraMake(), m.CameraModel())), entity.SrcMeta)
photo.SetLens(entity.FirstOrCreateLens(entity.NewLens(m.LensMake(), m.LensModel())), entity.SrcMeta)
photo.SetExposure(m.FocalLength(), m.FNumber(), m.Iso(), m.Exposure(), entity.SrcMeta)
}
@ -771,8 +771,8 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
}
}
photo.SetCamera(entity.FirstOrCreateCamera(entity.NewCamera(m.CameraModel(), m.CameraMake())), entity.SrcMeta)
photo.SetLens(entity.FirstOrCreateLens(entity.NewLens(m.LensModel(), m.LensMake())), entity.SrcMeta)
photo.SetCamera(entity.FirstOrCreateCamera(entity.NewCamera(m.CameraMake(), m.CameraModel())), entity.SrcMeta)
photo.SetLens(entity.FirstOrCreateLens(entity.NewLens(m.LensMake(), m.LensModel())), entity.SrcMeta)
photo.SetExposure(m.FocalLength(), m.FNumber(), m.Iso(), m.Exposure(), entity.SrcMeta)
var locLabels classify.Labels