From 2bde7e5696ea7f617b08d083b5389dda5828023c Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Fri, 3 Sep 2021 19:02:26 +0200 Subject: [PATCH] Panoramas: Increase projection type string limit to 32 characters #1508 --- internal/entity/const.go | 104 +++++++++++++++---------- internal/entity/file.go | 14 +++- internal/entity/file_test.go | 39 +++++++++- internal/entity/values.go | 39 +++++++++- internal/entity/values_test.go | 22 ++++++ internal/photoprism/index_mediafile.go | 6 +- pkg/txt/clip.go | 3 +- 7 files changed, 178 insertions(+), 49 deletions(-) create mode 100644 internal/entity/values_test.go diff --git a/internal/entity/const.go b/internal/entity/const.go index 6122a012b..be9a62a9e 100644 --- a/internal/entity/const.go +++ b/internal/entity/const.go @@ -1,7 +1,69 @@ package entity +// Panorama Projection Types +// TODO: Move to separate package. + +const ( + ProjDefault = "" + ProjEquirectangular = "equirectangular" + ProjCubestrip = "cubestrip" + ProjCylindrical = "cylindrical" + ProjTransverseCylindrical = "transverse-cylindrical" + ProjPseudocylindricalCompromise = "pseudocylindrical-compromise" +) + +// Content Types + +const ( + TypeDefault = "" + TypeImage = "image" + TypeLive = "live" + TypeVideo = "video" + TypeRaw = "raw" + TypeText = "text" +) + +// Root Directories Types + +const ( + RootUnknown = "" + RootOriginals = "/" + RootExamples = "examples" + RootSidecar = "sidecar" + RootImport = "import" + RootPath = "/" +) + +// Unknown Values + +const ( + UnknownYear = -1 + UnknownMonth = -1 + UnknownDay = -1 + UnknownName = "Unknown" + UnknownTitle = UnknownName + UnknownID = "zz" +) + +// Event Types + +const ( + Updated = "updated" + Created = "created" + Deleted = "deleted" +) + +// Photo Stacks + +const ( + IsStacked int8 = 1 + IsStackable int8 = 0 + IsUnstacked int8 = -1 +) + +// Sort Orders + const ( - // Sort orders: SortOrderAdded = "added" SortOrderNewest = "newest" SortOrderOldest = "oldest" @@ -9,44 +71,4 @@ const ( SortOrderSimilar = "similar" SortOrderRelevance = "relevance" SortOrderEdited = "edited" - - // Unknown values: - UnknownYear = -1 - UnknownMonth = -1 - UnknownDay = -1 - UnknownName = "Unknown" - UnknownTitle = UnknownName - UnknownID = "zz" - - // Content types: - TypeDefault = "" - TypeImage = "image" - TypeLive = "live" - TypeVideo = "video" - TypeRaw = "raw" - TypeText = "text" - - // Root directories: - RootUnknown = "" - RootOriginals = "/" - RootExamples = "examples" - RootSidecar = "sidecar" - RootImport = "import" - RootPath = "/" - - // Panorama projections: - ProjectionDefault = "" - ProjectionEquirectangular = "equirectangular" - ProjectionCubestrip = "cubestrip" - ProjectionCylindrical = "cylindrical" - - // Event names: - Updated = "updated" - Created = "created" - Deleted = "deleted" - - // Photo stacks: - IsStacked int8 = 1 - IsStackable int8 = 0 - IsUnstacked int8 = -1 ) diff --git a/internal/entity/file.go b/internal/entity/file.go index ed29d6499..8bd95ffeb 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -53,7 +53,7 @@ type File struct { FileWidth int `json:"Width" yaml:"Width,omitempty"` FileHeight int `json:"Height" yaml:"Height,omitempty"` FileOrientation int `json:"Orientation" yaml:"Orientation,omitempty"` - FileProjection string `gorm:"type:VARBINARY(16);" json:"Projection,omitempty" yaml:"Projection,omitempty"` + FileProjection string `gorm:"type:VARBINARY(32);" json:"Projection,omitempty" yaml:"Projection,omitempty"` FileAspectRatio float32 `gorm:"type:FLOAT;" json:"AspectRatio" yaml:"AspectRatio,omitempty"` FileMainColor string `gorm:"type:VARBINARY(16);index;" json:"MainColor" yaml:"MainColor,omitempty"` FileColors string `gorm:"type:VARBINARY(9);" json:"Colors" yaml:"Colors,omitempty"` @@ -394,7 +394,17 @@ func (m *File) Panorama() bool { return false } - return m.FileProjection != ProjectionDefault || (m.FileWidth/m.FileHeight) >= 2 + return m.Projection() != ProjDefault || (m.FileWidth/m.FileHeight) >= 2 +} + +// Projection returns the panorama projection type string. +func (m *File) Projection() string { + return SanitizeTypeString(m.FileProjection) +} + +// SetProjection sets the panorama projection type string. +func (m *File) SetProjection(projType string) { + m.FileProjection = SanitizeTypeString(projType) } // AddFaces adds face markers to the file. diff --git a/internal/entity/file_test.go b/internal/entity/file_test.go index 367943abf..81b4460c1 100644 --- a/internal/entity/file_test.go +++ b/internal/entity/file_test.go @@ -289,7 +289,11 @@ func TestFile_Panorama(t *testing.T) { assert.False(t, file.Panorama()) }) t.Run("equirectangular", func(t *testing.T) { - file := &File{Photo: nil, FileType: "jpg", FileSidecar: false, FileWidth: 1500, FileHeight: 1000, FileProjection: ProjectionEquirectangular} + file := &File{Photo: nil, FileType: "jpg", FileSidecar: false, FileWidth: 1500, FileHeight: 1000, FileProjection: ProjEquirectangular} + assert.True(t, file.Panorama()) + }) + t.Run("transverse-cylindrical", func(t *testing.T) { + file := &File{Photo: nil, FileType: "jpg", FileSidecar: false, FileWidth: 1500, FileHeight: 1000, FileProjection: ProjTransverseCylindrical} assert.True(t, file.Panorama()) }) t.Run("sidecar", func(t *testing.T) { @@ -298,6 +302,39 @@ func TestFile_Panorama(t *testing.T) { }) } +func TestFile_SetProjection(t *testing.T) { + t.Run(ProjDefault, func(t *testing.T) { + m := &File{} + m.SetProjection(ProjDefault) + assert.Equal(t, ProjDefault, m.FileProjection) + }) + t.Run(ProjCubestrip, func(t *testing.T) { + m := &File{} + m.SetProjection(ProjCubestrip) + assert.Equal(t, ProjCubestrip, m.FileProjection) + }) + t.Run(ProjCylindrical, func(t *testing.T) { + m := &File{} + m.SetProjection(ProjCylindrical) + assert.Equal(t, ProjCylindrical, m.FileProjection) + }) + t.Run(ProjTransverseCylindrical, func(t *testing.T) { + m := &File{} + m.SetProjection(ProjTransverseCylindrical) + assert.Equal(t, ProjTransverseCylindrical, m.FileProjection) + }) + t.Run(ProjPseudocylindricalCompromise, func(t *testing.T) { + m := &File{} + m.SetProjection(ProjPseudocylindricalCompromise) + assert.Equal(t, ProjPseudocylindricalCompromise, m.FileProjection) + }) + t.Run("Sanitize", func(t *testing.T) { + m := &File{} + m.SetProjection(" 幸福 Hanzi are logograms developed for the writing of Chinese! ") + assert.Equal(t, "hanzi are logograms developed for the writing of chinese", m.FileProjection) + }) +} + func TestFile_Delete(t *testing.T) { t.Run("permanently", func(t *testing.T) { file := &File{FileType: "jpg", FileSize: 500, FileName: "ToBePermanentlyDeleted", FileRoot: "", PhotoID: 5678} diff --git a/internal/entity/values.go b/internal/entity/values.go index be556d7eb..543eb1823 100644 --- a/internal/entity/values.go +++ b/internal/entity/values.go @@ -1,6 +1,13 @@ package entity -import "reflect" +import ( + "reflect" + "strings" +) + +const ( + TrimTypeString = 32 +) // Values is a shortcut for map[string]interface{} type Values map[string]interface{} @@ -34,3 +41,33 @@ func GetValues(m interface{}, omit ...string) (result Values) { return result } + +// ToASCII removes all non-ascii characters from a string and returns it. +func ToASCII(s string) string { + result := make([]rune, 0, len(s)) + + for _, r := range s { + if r <= 127 { + result = append(result, r) + } + } + + return string(result) +} + +// Trim shortens a string to the given number of characters, and removes all leading and trailing white space. +func Trim(s string, maxLen int) string { + s = strings.TrimSpace(s) + l := len(s) + + if l <= maxLen { + return s + } else { + return s[:l-1] + } +} + +// SanitizeTypeString converts a type string to lowercase, omits invalid runes, and shortens it if needed. +func SanitizeTypeString(s string) string { + return Trim(ToASCII(strings.ToLower(s)), TrimTypeString) +} diff --git a/internal/entity/values_test.go b/internal/entity/values_test.go new file mode 100644 index 000000000..5372e9a3b --- /dev/null +++ b/internal/entity/values_test.go @@ -0,0 +1,22 @@ +package entity + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToASCII(t *testing.T) { + result := ToASCII("幸福 = Happiness.") + assert.Equal(t, " = Happiness.", result) +} + +func TestTrim(t *testing.T) { + result := Trim(" 幸福 Hanzi are logograms developed for the writing of Chinese! ", 16) + assert.Equal(t, "幸福 Hanzi are logograms developed for the writing of Chinese", result) +} + +func TestSanitizeTypeString(t *testing.T) { + result := SanitizeTypeString(" 幸福 Hanzi are logograms developed for the writing of Chinese! ") + assert.Equal(t, "hanzi are logograms developed for the writing of chinese", result) +} diff --git a/internal/photoprism/index_mediafile.go b/internal/photoprism/index_mediafile.go index 54511aaed..70e9e899e 100644 --- a/internal/photoprism/index_mediafile.go +++ b/internal/photoprism/index_mediafile.go @@ -321,7 +321,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) ( if metaData := m.MetaData(); metaData.Error == nil { file.FileCodec = metaData.Codec - file.FileProjection = metaData.Projection + file.SetProjection(metaData.Projection) if metaData.HasInstanceID() { log.Infof("index: %s has instance_id %s", logName, txt.Quote(metaData.InstanceID)) @@ -379,7 +379,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) ( file.FileHeight = m.Height() file.FileAspectRatio = m.AspectRatio() file.FilePortrait = m.Portrait() - file.FileProjection = metaData.Projection + file.SetProjection(metaData.Projection) if res := m.Megapixels(); res > photo.PhotoResolution { photo.PhotoResolution = res @@ -429,7 +429,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) ( file.FileAspectRatio = m.AspectRatio() file.FilePortrait = m.Portrait() file.FileDuration = metaData.Duration - file.FileProjection = metaData.Projection + file.SetProjection(metaData.Projection) if res := m.Megapixels(); res > photo.PhotoResolution { photo.PhotoResolution = res diff --git a/pkg/txt/clip.go b/pkg/txt/clip.go index 79a696400..b1d8721b4 100644 --- a/pkg/txt/clip.go +++ b/pkg/txt/clip.go @@ -4,13 +4,14 @@ import "strings" const ( ClipDefault = 160 - ClipSlug = 80 ClipKeyword = 40 + ClipSlug = 80 ClipVarchar = 255 ClipQuery = 1000 ClipDescription = 16000 ) +// Clip shortens a string to the given number of runes, and removes all leading and trailing white space. func Clip(s string, size int) string { s = strings.TrimSpace(s)