From f80ac62e6bbd71e9d50ae6c1a352cde222849dec Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Sun, 6 Jul 2025 11:07:25 +0200 Subject: [PATCH] Live Photos: Only flag actual Live and Motion Photos as "Live" #5089 Signed-off-by: Michael Mayer --- internal/entity/photo.go | 48 ++++ internal/entity/photo_fixtures.go | 68 ++--- internal/entity/photo_test.go | 105 +++++-- internal/entity/src.go | 2 + internal/photoprism/index_mediafile.go | 82 +++--- internal/photoprism/mediafile.go | 15 +- internal/photoprism/mediafile_test.go | 380 ++++++++++++++----------- pkg/fs/file_type.go | 20 +- pkg/media/filename.go | 2 +- pkg/media/type.go | 11 +- pkg/media/type_test.go | 27 +- pkg/media/types.go | 31 +- 12 files changed, 499 insertions(+), 292 deletions(-) diff --git a/internal/entity/photo.go b/internal/entity/photo.go index babb45dd4..14eeb9fd2 100644 --- a/internal/entity/photo.go +++ b/internal/entity/photo.go @@ -245,6 +245,54 @@ func (m *Photo) GetUID() string { return m.PhotoUID } +// MediaType returns the current PhotoType as media.Type. +func (m *Photo) MediaType() media.Type { + return media.Type(m.PhotoType) +} + +// HasMediaType checks if the photo has any of the specified media types. +func (m *Photo) HasMediaType(types ...media.Type) bool { + mediaType := m.MediaType() + + for _, t := range types { + if mediaType == t { + return true + } + } + + return false +} + +// SetMediaType sets a new media type if its priority is higher than that of the current type. +func (m *Photo) SetMediaType(newType media.Type, typeSrc string) { + // Only allow a new main media type to be set. + if !newType.IsMain() || newType.Equal(m.PhotoType) { + return + } + + // Get current media type. + currentType := m.MediaType() + + // Do not change the type if the source priority is lower than the current one. + if SrcPriority[typeSrc] < SrcPriority[m.TypeSrc] && currentType.IsMain() { + return + } + + // Do not automatically change a higher priority type to a lower one. + if SrcPriority[typeSrc] <= SrcPriority[SrcFile] && media.Priority[newType] < media.Priority[currentType] { + return + } + + // Set new type and type source. + m.PhotoType = newType.String() + m.TypeSrc = typeSrc + + // Write a debug log containing the old and new media type. + log.Debugf("photo: changed type of %s from %s to %s", m.String(), currentType.String(), newType.String()) + + return +} + // String returns the id or name as string. func (m *Photo) String() string { if m == nil { diff --git a/internal/entity/photo_fixtures.go b/internal/entity/photo_fixtures.go index 571f8e236..dcf6155ce 100644 --- a/internal/entity/photo_fixtures.go +++ b/internal/entity/photo_fixtures.go @@ -33,13 +33,13 @@ var PhotoFixtures = PhotoMap{ PhotoUID: "ps6sg6be2lvl0yh7", TakenAt: time.Date(2008, 7, 1, 10, 0, 0, 0, time.UTC), TakenAtLocal: time.Date(2008, 7, 1, 12, 0, 0, 0, time.UTC), - TakenSrc: "meta", - PhotoType: "image", - TypeSrc: "", + TakenSrc: SrcMeta, + PhotoType: MediaImage, + TypeSrc: SrcAuto, PhotoTitle: "Lake / 2790", - TitleSrc: "", + TitleSrc: SrcAuto, PhotoCaption: "photo caption lake", - CaptionSrc: "meta", + CaptionSrc: SrcMeta, PhotoPath: "2790/07", PhotoName: "27900704_070228_D6D51B6C", OriginalName: "Vacation/exampleFileNameOriginal", @@ -71,7 +71,7 @@ var PhotoFixtures = PhotoMap{ Camera: CameraFixtures.Pointer("canon-eos-6d"), CameraID: CameraFixtures.Pointer("canon-eos-6d").ID, CameraSerial: "", - CameraSrc: "meta", + CameraSrc: SrcMeta, Lens: LensFixtures.Pointer("lens-f-380"), LensID: LensFixtures.Pointer("lens-f-380").ID, Details: DetailsFixtures.Pointer("lake", 1000000), @@ -100,13 +100,13 @@ var PhotoFixtures = PhotoMap{ PhotoUID: "ps6sg6be2lvl0yh8", TakenAt: time.Date(2006, 1, 1, 2, 0, 0, 0, time.UTC), TakenAtLocal: time.Date(2006, 1, 1, 2, 0, 0, 0, time.UTC), - TakenSrc: "meta", - PhotoType: "raw", - TypeSrc: "", + TakenSrc: SrcMeta, + PhotoType: MediaRaw, + TypeSrc: SrcAuto, PhotoTitle: "", - TitleSrc: "", + TitleSrc: SrcAuto, PhotoCaption: "photo caption non-photographic", - CaptionSrc: "", + CaptionSrc: SrcAuto, PhotoPath: "2790/02", PhotoName: "Photo01", OriginalName: "", @@ -117,7 +117,7 @@ var PhotoFixtures = PhotoMap{ TimeZone: "Europe/Berlin", Place: PlaceFixtures.Pointer("Germany"), PlaceID: PlaceFixtures.Pointer("Germany").ID, - PlaceSrc: "manual", + PlaceSrc: SrcManual, Cell: CellFixtures.Pointer("Neckarbrücke"), CellID: CellFixtures.Pointer("Neckarbrücke").ID, CellAccuracy: 0, @@ -161,13 +161,13 @@ var PhotoFixtures = PhotoMap{ PhotoUID: "ps6sg6be2lvl0yh9", TakenAt: time.Date(1990, 3, 2, 0, 0, 0, 0, time.UTC), TakenAtLocal: time.Date(1990, 3, 2, 0, 0, 0, 0, time.UTC), - TakenSrc: "manual", - PhotoType: "image", - TypeSrc: "", + TakenSrc: SrcManual, + PhotoType: MediaImage, + TypeSrc: SrcAuto, PhotoTitle: "", - TitleSrc: "", + TitleSrc: SrcAuto, PhotoCaption: "", - CaptionSrc: "", + CaptionSrc: SrcAuto, PhotoPath: "London", PhotoName: "bridge1", OriginalName: "", @@ -178,7 +178,7 @@ var PhotoFixtures = PhotoMap{ TimeZone: "Local", Place: &UnknownPlace, PlaceID: UnknownPlace.ID, - PlaceSrc: "", + PlaceSrc: SrcAuto, Cell: &UnknownLocation, CellID: UnknownLocation.ID, CellAccuracy: 0, @@ -198,7 +198,7 @@ var PhotoFixtures = PhotoMap{ Camera: CameraFixtures.Pointer("canon-eos-6d"), CameraID: CameraFixtures.Pointer("canon-eos-6d").ID, CameraSerial: "", - CameraSrc: "", + CameraSrc: SrcAuto, Lens: LensFixtures.Pointer("lens-f-380"), LensID: LensFixtures.Pointer("lens-f-380").ID, Details: DetailsFixtures.Pointer("lake", 1000002), @@ -220,8 +220,8 @@ var PhotoFixtures = PhotoMap{ PhotoUID: "ps6sg6be2lvl0yh0", TakenAt: time.Date(1990, 4, 18, 1, 0, 0, 0, time.UTC), TakenAtLocal: time.Date(1990, 4, 18, 1, 0, 0, 0, time.UTC), - TakenSrc: "meta", - PhotoType: "video", + TakenSrc: SrcMeta, + PhotoType: MediaVideo, PhotoDuration: time.Hour * 2, TypeSrc: "", PhotoTitle: "", @@ -238,7 +238,7 @@ var PhotoFixtures = PhotoMap{ TimeZone: "Local", Place: CellFixtures.Pointer("caravan park").Place, PlaceID: CellFixtures.Pointer("caravan park").Place.ID, - PlaceSrc: "meta", + PlaceSrc: SrcMeta, Cell: CellFixtures.Pointer("caravan park"), CellID: CellFixtures.Pointer("caravan park").ID, CellAccuracy: 0, @@ -1358,8 +1358,8 @@ var PhotoFixtures = PhotoMap{ TakenAt: time.Date(2001, 1, 1, 7, 0, 0, 0, time.UTC), TakenAtLocal: time.Date(2001, 1, 1, 7, 0, 0, 0, time.UTC), TakenSrc: "", - PhotoType: "image", - TypeSrc: "", + PhotoType: MediaImage, + TypeSrc: SrcAuto, PhotoTitle: "Lake / 2001", TitleSrc: "", PhotoCaption: "", @@ -1417,8 +1417,8 @@ var PhotoFixtures = PhotoMap{ TakenAt: time.Date(2020, 11, 11, 9, 7, 18, 0, time.UTC), TakenAtLocal: time.Date(2020, 11, 11, 9, 7, 18, 0, time.UTC), TakenSrc: SrcMeta, - PhotoType: "image", - TypeSrc: "", + PhotoType: MediaImage, + TypeSrc: SrcAuto, PhotoTitle: "ForMerge", TitleSrc: SrcManual, PhotoCaption: "", @@ -1478,8 +1478,8 @@ var PhotoFixtures = PhotoMap{ TakenAt: time.Date(2020, 11, 11, 9, 7, 18, 0, time.UTC), TakenAtLocal: time.Date(2020, 11, 11, 9, 7, 18, 0, time.UTC), TakenSrc: SrcMeta, - PhotoType: "raw", - TypeSrc: "", + PhotoType: MediaRaw, + TypeSrc: SrcAuto, PhotoTitle: "ForMerge2", TitleSrc: SrcManual, PhotoCaption: "", @@ -1539,8 +1539,8 @@ var PhotoFixtures = PhotoMap{ TakenAt: time.Date(2007, 1, 11, 9, 7, 18, 0, time.UTC), TakenAtLocal: time.Date(2007, 1, 11, 9, 7, 18, 0, time.UTC), TakenSrc: SrcMeta, - PhotoType: "image", - TypeSrc: "", + PhotoType: MediaImage, + TypeSrc: SrcAuto, PhotoTitle: "photowitheditedatdate", TitleSrc: SrcManual, PhotoCaption: "", @@ -1602,8 +1602,8 @@ var PhotoFixtures = PhotoMap{ TakenAt: time.Date(2007, 1, 11, 9, 7, 18, 0, time.UTC), TakenAtLocal: time.Date(2007, 1, 11, 9, 7, 18, 0, time.UTC), TakenSrc: SrcMeta, - PhotoType: "image", - TypeSrc: "", + PhotoType: MediaImage, + TypeSrc: SrcAuto, PhotoTitle: "phototobebatchapproved", TitleSrc: SrcAuto, PhotoCaption: "", @@ -1663,8 +1663,8 @@ var PhotoFixtures = PhotoMap{ TakenAt: time.Date(2000, 12, 11, 9, 7, 18, 0, time.UTC), TakenAtLocal: time.Date(2000, 12, 11, 4, 7, 18, 0, time.UTC), TakenSrc: SrcMeta, - PhotoType: "live", - TypeSrc: "", + PhotoType: MediaLive, + TypeSrc: SrcFile, PhotoTitle: "phototobebatchapproved2", TitleSrc: SrcName, PhotoCaption: "", diff --git a/internal/entity/photo_test.go b/internal/entity/photo_test.go index 58b4bc1f3..0ba08096a 100644 --- a/internal/entity/photo_test.go +++ b/internal/entity/photo_test.go @@ -9,6 +9,7 @@ import ( "github.com/photoprism/photoprism/internal/ai/classify" "github.com/photoprism/photoprism/internal/form" + "github.com/photoprism/photoprism/pkg/media" "github.com/photoprism/photoprism/pkg/time/tz" ) @@ -72,6 +73,90 @@ func TestSavePhotoForm(t *testing.T) { }) } +func TestPhoto_HasUID(t *testing.T) { + t.Run("True", func(t *testing.T) { + m := PhotoFixtures.Get("Photo01") + assert.True(t, m.HasID()) + assert.True(t, m.HasUID()) + }) + t.Run("False", func(t *testing.T) { + m := Photo{} + assert.False(t, m.HasID()) + assert.False(t, m.HasUID()) + }) +} + +func TestPhoto_GetID(t *testing.T) { + t.Run("Success", func(t *testing.T) { + m := PhotoFixtures.Get("Photo01") + assert.Equal(t, uint(1000001), m.GetID()) + }) +} + +func TestPhoto_MediaType(t *testing.T) { + t.Run("Image", func(t *testing.T) { + m := PhotoFixtures.Get("19800101_000002_D640C559") + assert.Equal(t, media.Image, m.MediaType()) + }) + t.Run("Live", func(t *testing.T) { + m := PhotoFixtures.Get("Photo27") + assert.Equal(t, media.Live, m.MediaType()) + }) +} + +func TestPhoto_HasMediaType(t *testing.T) { + t.Run("Image", func(t *testing.T) { + m := PhotoFixtures.Get("19800101_000002_D640C559") + assert.True(t, m.HasMediaType(media.Image)) + assert.True(t, m.HasMediaType(media.Image, media.Video, media.Live)) + assert.False(t, m.HasMediaType(media.Video, media.Live)) + assert.False(t, m.HasMediaType()) + }) + t.Run("Live", func(t *testing.T) { + m := PhotoFixtures.Get("Photo27") + assert.True(t, m.HasMediaType(media.Live)) + assert.True(t, m.HasMediaType(media.Image, media.Video, media.Live)) + assert.False(t, m.HasMediaType(media.Image, media.Animated)) + assert.False(t, m.HasMediaType()) + }) +} + +func TestPhoto_SetMediaType(t *testing.T) { + t.Run("Image", func(t *testing.T) { + m := PhotoFixtures.Get("19800101_000002_D640C559") + assert.Equal(t, media.Image, m.MediaType()) + assert.Equal(t, SrcAuto, m.TypeSrc) + m.SetMediaType(media.Video, SrcAuto) + assert.Equal(t, media.Video, m.MediaType()) + assert.Equal(t, SrcAuto, m.TypeSrc) + m.SetMediaType(media.Live, SrcAuto) + assert.Equal(t, media.Live, m.MediaType()) + assert.Equal(t, SrcAuto, m.TypeSrc) + m.SetMediaType(media.Video, SrcAuto) + assert.Equal(t, media.Live, m.MediaType()) + assert.Equal(t, SrcAuto, m.TypeSrc) + m.SetMediaType(media.Image, SrcAuto) + assert.Equal(t, media.Live, m.MediaType()) + assert.Equal(t, SrcAuto, m.TypeSrc) + m.SetMediaType("", SrcAuto) + assert.Equal(t, media.Live, m.MediaType()) + assert.Equal(t, SrcAuto, m.TypeSrc) + m.SetMediaType(media.Video, SrcManual) + assert.Equal(t, media.Video, m.MediaType()) + assert.Equal(t, SrcManual, m.TypeSrc) + }) + t.Run("Live", func(t *testing.T) { + m := PhotoFixtures.Get("Photo27") + assert.Equal(t, media.Live, m.MediaType()) + m.SetMediaType(media.Image, SrcAuto) + assert.Equal(t, media.Live, m.MediaType()) + assert.Equal(t, SrcFile, m.TypeSrc) + m.SetMediaType(media.Image, SrcManual) + assert.Equal(t, media.Image, m.MediaType()) + assert.Equal(t, SrcManual, m.TypeSrc) + }) +} + func TestPhoto_SaveLabels(t *testing.T) { t.Run("NewPhoto", func(t *testing.T) { photo := Photo{ @@ -125,26 +210,6 @@ func TestPhoto_SaveLabels(t *testing.T) { }) } -func TestPhoto_HasUID(t *testing.T) { - t.Run("True", func(t *testing.T) { - m := PhotoFixtures.Get("Photo01") - assert.True(t, m.HasID()) - assert.True(t, m.HasUID()) - }) - t.Run("False", func(t *testing.T) { - m := Photo{} - assert.False(t, m.HasID()) - assert.False(t, m.HasUID()) - }) -} - -func TestPhoto_GetID(t *testing.T) { - t.Run("Success", func(t *testing.T) { - m := PhotoFixtures.Get("Photo01") - assert.Equal(t, uint(1000001), m.GetID()) - }) -} - func TestPhoto_ClassifyLabels(t *testing.T) { t.Run("NewPhoto", func(t *testing.T) { m := PhotoFixtures.Get("Photo19") diff --git a/internal/entity/src.go b/internal/entity/src.go index 326b25fa7..612c5e465 100644 --- a/internal/entity/src.go +++ b/internal/entity/src.go @@ -9,6 +9,7 @@ const ( SrcAuto = "" // Prio 1 SrcDefault = "default" // Prio 1 SrcEstimate = "estimate" // Prio 2 + SrcFile = "file" // Prio 2 SrcName = "name" // Prio 4 SrcYaml = "yaml" // Prio 8 SrcOIDC = "oidc" // Prio 8 @@ -40,6 +41,7 @@ var SrcPriority = Priorities{ SrcAuto: 1, SrcDefault: 1, SrcEstimate: 2, + SrcFile: 2, SrcName: 4, SrcYaml: 8, SrcOIDC: 8, diff --git a/internal/photoprism/index_mediafile.go b/internal/photoprism/index_mediafile.go index a915a5628..8a7e93b7e 100644 --- a/internal/photoprism/index_mediafile.go +++ b/internal/photoprism/index_mediafile.go @@ -17,7 +17,6 @@ import ( "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/media" - "github.com/photoprism/photoprism/pkg/media/video" "github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/time/tz" "github.com/photoprism/photoprism/pkg/txt" @@ -433,12 +432,9 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot // Change file and photo type to "live" if the file has a video embedded. file.FileVideo = true file.MediaType = entity.MediaLive - if photo.TypeSrc == entity.SrcAuto { - photo.PhotoType = entity.MediaLive - } - } else if photo.TypeSrc == entity.SrcAuto && photo.PhotoType == entity.MediaLive { - // Image does not include a compatible video. - photo.PhotoType = entity.MediaImage + + // Set photo media type to "live". + photo.SetMediaType(media.Live, entity.SrcFile) } if file.OriginalName == "" && filepath.Base(file.FileName) != data.FileName { @@ -459,9 +455,14 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot } } - // Change the photo type to animated if it is an animated PNG. - if photo.TypeSrc == entity.SrcAuto && photo.PhotoType == entity.MediaImage && m.IsAnimatedImage() { - photo.PhotoType = entity.MediaAnimated + // If the photo contains an animation or has a video, + // change the photo type from image to animated or live. + if photo.HasMediaType(media.Image) { + if m.IsAnimatedImage() { + photo.SetMediaType(media.Animated, entity.SrcAuto) + } else if m.IsLive() { + photo.SetMediaType(media.Live, entity.SrcAuto) + } } case m.IsXMP(): if data, dataErr := meta.XMP(m.FileName()); dataErr == nil { @@ -554,12 +555,12 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot // Change file and photo type to "live" if the file has a video embedded. file.FileVideo = true file.MediaType = entity.MediaLive - if photo.TypeSrc == entity.SrcAuto { - photo.PhotoType = entity.MediaLive - } - } else if photo.TypeSrc == entity.SrcAuto && photo.PhotoType == entity.MediaLive { - // HEIC does not include a compatible video. - photo.PhotoType = entity.MediaImage + + // If the file also contains a video, set photo media type to "live". + photo.SetMediaType(media.Live, entity.SrcFile) + } else { + // If the file does not contain a video, set the media type to "image". + photo.SetMediaType(media.Image, entity.SrcAuto) } // Set photo resolution based on the largest media file. @@ -573,15 +574,15 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot } // Update photo type if an image and not manually modified. - if photo.TypeSrc == entity.SrcAuto && photo.PhotoType == entity.MediaImage { + if photo.HasMediaType(media.Image) { if m.IsAnimatedImage() { - photo.PhotoType = entity.MediaAnimated + photo.SetMediaType(media.Animated, entity.SrcAuto) } else if m.IsRaw() { - photo.PhotoType = entity.MediaRaw + photo.SetMediaType(media.Raw, entity.SrcAuto) } else if m.IsLive() { - photo.PhotoType = entity.MediaLive + photo.SetMediaType(media.Live, entity.SrcAuto) } else if m.IsVector() { - photo.PhotoType = entity.MediaVector + photo.SetMediaType(media.Vector, entity.SrcAuto) } } case m.IsVector(): @@ -637,10 +638,8 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot } } - // Update photo type if not manually modified. - if photo.TypeSrc == entity.SrcAuto { - photo.PhotoType = entity.MediaVector - } + // Set photo media type to "vector". + photo.SetMediaType(media.Vector, entity.SrcAuto) case m.IsDocument(): if data := m.MetaData(); data.Error == nil { photo.SetTitle(data.Title, entity.SrcMeta) @@ -691,10 +690,8 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot } } - // Update photo type if not manually modified. - if photo.TypeSrc == entity.SrcAuto { - photo.PhotoType = entity.MediaDocument - } + // Set photo media type to "document". + photo.SetMediaType(media.Document, entity.SrcAuto) case m.IsVideo(): if data := m.MetaData(); data.Error == nil { photo.SetTitle(data.Title, entity.SrcMeta) @@ -759,13 +756,11 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot photo.SetExposure(m.FocalLength(), m.FNumber(), m.Iso(), m.Exposure(), entity.SrcMeta) } - if photo.TypeSrc == entity.SrcAuto { - // Update photo type only if not manually modified. - if file.FileDuration == 0 || file.FileDuration > video.LiveDuration { - photo.PhotoType = entity.MediaVideo - } else { - photo.PhotoType = entity.MediaLive - } + // Set photo media type to "live" or "video". + if m.IsLive() { + photo.SetMediaType(media.Live, entity.SrcAuto) + } else { + photo.SetMediaType(media.Video, entity.SrcAuto) } // Set the video dimensions from the primary image if it could not be determined from the video metadata. @@ -910,27 +905,28 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot }) } - if photo.PhotoType == entity.MediaAnimated { + switch photo.MediaType() { + case media.Animated: event.Publish("count.animated", event.Data{ "count": 1, }) - } else if photo.PhotoType == entity.MediaLive { + case media.Live: event.Publish("count.live", event.Data{ "count": 1, }) - } else if photo.PhotoType == entity.MediaAudio { + case media.Audio: event.Publish("count.audio", event.Data{ "count": 1, }) - } else if photo.PhotoType == entity.MediaVideo { + case media.Video: event.Publish("count.videos", event.Data{ "count": 1, }) - } else if photo.PhotoType == entity.MediaDocument { + case media.Document: event.Publish("count.documents", event.Data{ "count": 1, }) - } else { + default: event.Publish("count.photos", event.Data{ "count": 1, }) @@ -1038,7 +1034,7 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot } // Update related video files so they are properly grouped with the primary image in search results. - if (photo.PhotoType == entity.MediaVideo || photo.PhotoType == entity.MediaLive) && file.FilePrimary { + if photo.HasMediaType(media.Video, media.Live) && file.FilePrimary { if updateErr := file.UpdateVideoInfos(); updateErr != nil { log.Errorf("index: %s in %s (update video infos)", updateErr, logName) } diff --git a/internal/photoprism/mediafile.go b/internal/photoprism/mediafile.go index 5a88600f4..f7e4e4f08 100644 --- a/internal/photoprism/mediafile.go +++ b/internal/photoprism/mediafile.go @@ -994,7 +994,7 @@ func (m *MediaFile) IsVideo() bool { // IsSidecar checks if the file is a metadata sidecar file, independent of the storage location. func (m *MediaFile) IsSidecar() bool { - return !m.Media().Main() + return !m.Media().IsMain() } // IsArchive returns true if this is an archive file. @@ -1064,12 +1064,19 @@ func (m *MediaFile) IsImageNative() bool { // IsLive checks if the file is a live photo. func (m *MediaFile) IsLive() bool { - if m.IsHeic() { - return fs.VideoMov.FindFirst(m.FileName(), []string{}, Config().OriginalsPath(), false) != "" + if !m.InOriginals() { + return false } if m.IsVideo() { - return fs.ImageHeic.FindFirst(m.FileName(), []string{}, Config().OriginalsPath(), false) != "" + if fs.ImageHeic.FindFirst(m.FileName(), []string{}, Config().OriginalsPath(), false) != "" || + fs.ImageJpeg.FindFirst(m.FileName(), []string{}, Config().OriginalsPath(), false) != "" { + return true + } + } else if m.IsHeic() || m.IsJpeg() { + if fs.VideoMov.FindFirst(m.FileName(), []string{}, Config().OriginalsPath(), false) != "" { + return true + } } return m.MetaData().MediaType == media.Live && m.VideoInfo().Compatible diff --git a/internal/photoprism/mediafile_test.go b/internal/photoprism/mediafile_test.go index 0e590dd29..561f0de80 100644 --- a/internal/photoprism/mediafile_test.go +++ b/internal/photoprism/mediafile_test.go @@ -214,7 +214,7 @@ func TestMediaFile_HasTimeAndPlace(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.HasTimeAndPlace()) + assert.True(t, mediaFile.HasTimeAndPlace()) }) t.Run("/peacock_blue.jpg", func(t *testing.T) { conf := config.TestConfig() @@ -223,7 +223,7 @@ func TestMediaFile_HasTimeAndPlace(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.HasTimeAndPlace()) + assert.False(t, mediaFile.HasTimeAndPlace()) }) } func TestMediaFile_CameraModel(t *testing.T) { @@ -715,7 +715,7 @@ func TestMediaFile_MimeType(t *testing.T) { assert.True(t, fs.SameType(header.ContentTypeText, mediaFile.BaseType())) assert.Equal(t, "text/plain", mediaFile.BaseType()) assert.Equal(t, "text/plain; charset=utf-8", mediaFile.MimeType()) - assert.Equal(t, true, mediaFile.HasMimeType("text/plain")) + assert.True(t, mediaFile.HasMimeType("text/plain")) }) t.Run("iphone_7.json", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.json") @@ -749,7 +749,7 @@ func TestMediaFile_MimeType(t *testing.T) { assert.True(t, fs.SameType(header.ContentTypeXml, mediaFile.BaseType())) assert.Equal(t, "text/xml", mediaFile.BaseType()) assert.Equal(t, "text/xml; charset=utf-8", mediaFile.MimeType()) - assert.Equal(t, true, mediaFile.HasMimeType("text/xml")) + assert.True(t, mediaFile.HasMimeType("text/xml")) }) t.Run("earth.mov", func(t *testing.T) { if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "earth.mov")); err != nil { @@ -819,16 +819,16 @@ func TestMediaFile_Exists(t *testing.T) { assert.NotNil(t, exists) assert.True(t, exists.Exists()) - assert.Equal(t, true, exists.Ok()) - assert.Equal(t, false, exists.Empty()) + assert.True(t, exists.Ok()) + assert.False(t, exists.Empty()) missing, err := NewMediaFile(c.ExamplesPath() + "/xxz.jpg") assert.NotNil(t, missing) assert.Error(t, err) assert.Equal(t, int64(-1), missing.FileSize()) - assert.Equal(t, false, missing.Ok()) - assert.Equal(t, true, missing.Empty()) + assert.False(t, missing.Ok()) + assert.True(t, missing.Empty()) } func TestMediaFile_SetModTime(t *testing.T) { @@ -967,28 +967,28 @@ func TestMediaFile_IsJpeg(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsJpeg()) + assert.False(t, mediaFile.IsJpeg()) }) t.Run("iphone_7.heic", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.heic") if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsJpeg()) + assert.False(t, mediaFile.IsJpeg()) }) t.Run("canon_eos_6d.dng", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/canon_eos_6d.dng") if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsJpeg()) + assert.False(t, mediaFile.IsJpeg()) }) t.Run("elephants.jpg", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg") if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsJpeg()) + assert.True(t, mediaFile.IsJpeg()) }) } @@ -1000,28 +1000,28 @@ func TestMediaFile_HasType(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.HasFileType("jpg")) + assert.False(t, mediaFile.HasFileType("jpg")) }) t.Run("fox.profile0.8bpc.yuv420.avif", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/fox.profile0.8bpc.yuv420.avif") if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.HasFileType("avif")) + assert.True(t, mediaFile.HasFileType("avif")) }) t.Run("iphone_7.heic", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.heic") if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.HasFileType("heic")) + assert.True(t, mediaFile.HasFileType("heic")) }) t.Run("iphone_7.xmp", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.xmp") if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.HasFileType("xmp")) + assert.True(t, mediaFile.HasFileType("xmp")) }) } @@ -1033,28 +1033,28 @@ func TestMediaFile_IsHeic(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsHeic()) + assert.False(t, mediaFile.IsHeic()) }) t.Run("iphone_7.heic", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.heic") if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsHeic()) + assert.True(t, mediaFile.IsHeic()) }) t.Run("canon_eos_6d.dng", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/canon_eos_6d.dng") if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsHeic()) + assert.False(t, mediaFile.IsHeic()) }) t.Run("elephants.jpg", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg") if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsHeic()) + assert.False(t, mediaFile.IsHeic()) }) } @@ -1066,14 +1066,14 @@ func TestMediaFile_IsRaw(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsRaw()) + assert.False(t, mediaFile.IsRaw()) }) t.Run("iphone_7.heic", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.heic") if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsRaw()) + assert.False(t, mediaFile.IsRaw()) }) t.Run("canon_eos_6d.dng", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/canon_eos_6d.dng") @@ -1081,14 +1081,14 @@ func TestMediaFile_IsRaw(t *testing.T) { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsRaw()) + assert.True(t, mediaFile.IsRaw()) }) t.Run("elephants.jpg", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg") if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsRaw()) + assert.False(t, mediaFile.IsRaw()) }) } @@ -1100,7 +1100,7 @@ func TestMediaFile_IsPng(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsPng()) + assert.False(t, mediaFile.IsPng()) }) t.Run("tweethog.png", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/tweethog.png") @@ -1111,7 +1111,7 @@ func TestMediaFile_IsPng(t *testing.T) { assert.Equal(t, fs.ImagePng, mediaFile.FileType()) assert.Equal(t, "image/png", mediaFile.MimeType()) - assert.Equal(t, true, mediaFile.IsPng()) + assert.True(t, mediaFile.IsPng()) }) } @@ -1125,7 +1125,7 @@ func TestMediaFile_IsTiff(t *testing.T) { } assert.Equal(t, fs.SidecarJson, mediaFile.FileType()) assert.Equal(t, header.ContentTypeJson, mediaFile.MimeType()) - assert.Equal(t, false, mediaFile.IsTiff()) + assert.False(t, mediaFile.IsTiff()) }) t.Run("purple.tiff", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/purple.tiff") @@ -1134,7 +1134,7 @@ func TestMediaFile_IsTiff(t *testing.T) { } assert.Equal(t, fs.ImageTiff, mediaFile.FileType()) assert.Equal(t, "image/tiff", mediaFile.MimeType()) - assert.Equal(t, true, mediaFile.IsTiff()) + assert.True(t, mediaFile.IsTiff()) }) t.Run("example.tiff", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/example.tif") @@ -1143,7 +1143,7 @@ func TestMediaFile_IsTiff(t *testing.T) { } assert.Equal(t, fs.ImageTiff, mediaFile.FileType()) assert.Equal(t, "image/tiff", mediaFile.MimeType()) - assert.Equal(t, true, mediaFile.IsTiff()) + assert.True(t, mediaFile.IsTiff()) }) } @@ -1155,30 +1155,30 @@ func TestMediaFile_IsImageOther(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsImageOther()) + assert.False(t, mediaFile.IsImageOther()) }) t.Run("purple.tiff", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/purple.tiff") if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsImageOther()) + assert.True(t, mediaFile.IsImageOther()) }) t.Run("tweethog.png", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/tweethog.png") if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsJpeg()) - assert.Equal(t, false, mediaFile.IsGif()) - assert.Equal(t, true, mediaFile.IsPng()) - assert.Equal(t, false, mediaFile.IsBmp()) - assert.Equal(t, false, mediaFile.IsWebp()) - assert.Equal(t, true, mediaFile.IsImage()) - assert.Equal(t, true, mediaFile.IsImageNative()) - assert.Equal(t, true, mediaFile.IsImageOther()) - assert.Equal(t, false, mediaFile.IsVideo()) - assert.Equal(t, true, mediaFile.SkipTranscoding()) + assert.False(t, mediaFile.IsJpeg()) + assert.False(t, mediaFile.IsGif()) + assert.True(t, mediaFile.IsPng()) + assert.False(t, mediaFile.IsBmp()) + assert.False(t, mediaFile.IsWebp()) + assert.True(t, mediaFile.IsImage()) + assert.True(t, mediaFile.IsImageNative()) + assert.True(t, mediaFile.IsImageOther()) + assert.False(t, mediaFile.IsVideo()) + assert.True(t, mediaFile.SkipTranscoding()) }) t.Run("yellow_rose-small.bmp", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/yellow_rose-small.bmp") @@ -1187,15 +1187,15 @@ func TestMediaFile_IsImageOther(t *testing.T) { } assert.Equal(t, fs.ImageBmp, mediaFile.FileType()) assert.Equal(t, "image/bmp", mediaFile.MimeType()) - assert.Equal(t, false, mediaFile.IsJpeg()) - assert.Equal(t, false, mediaFile.IsGif()) - assert.Equal(t, true, mediaFile.IsBmp()) - assert.Equal(t, false, mediaFile.IsWebp()) - assert.Equal(t, true, mediaFile.IsImage()) - assert.Equal(t, true, mediaFile.IsImageNative()) - assert.Equal(t, true, mediaFile.IsImageOther()) - assert.Equal(t, false, mediaFile.IsVideo()) - assert.Equal(t, true, mediaFile.SkipTranscoding()) + assert.False(t, mediaFile.IsJpeg()) + assert.False(t, mediaFile.IsGif()) + assert.True(t, mediaFile.IsBmp()) + assert.False(t, mediaFile.IsWebp()) + assert.True(t, mediaFile.IsImage()) + assert.True(t, mediaFile.IsImageNative()) + assert.True(t, mediaFile.IsImageOther()) + assert.False(t, mediaFile.IsVideo()) + assert.True(t, mediaFile.SkipTranscoding()) }) t.Run("preloader.gif", func(t *testing.T) { mediaFile, err := NewMediaFile(c.ExamplesPath() + "/preloader.gif") @@ -1205,15 +1205,15 @@ func TestMediaFile_IsImageOther(t *testing.T) { assert.Equal(t, fs.ImageGif, mediaFile.FileType()) assert.Equal(t, "image/gif", mediaFile.MimeType()) - assert.Equal(t, false, mediaFile.IsJpeg()) - assert.Equal(t, true, mediaFile.IsGif()) - assert.Equal(t, false, mediaFile.IsBmp()) - assert.Equal(t, false, mediaFile.IsWebp()) - assert.Equal(t, true, mediaFile.IsImage()) - assert.Equal(t, true, mediaFile.IsImageNative()) - assert.Equal(t, true, mediaFile.IsImageOther()) - assert.Equal(t, false, mediaFile.IsVideo()) - assert.Equal(t, true, mediaFile.SkipTranscoding()) + assert.False(t, mediaFile.IsJpeg()) + assert.True(t, mediaFile.IsGif()) + assert.False(t, mediaFile.IsBmp()) + assert.False(t, mediaFile.IsWebp()) + assert.True(t, mediaFile.IsImage()) + assert.True(t, mediaFile.IsImageNative()) + assert.True(t, mediaFile.IsImageOther()) + assert.False(t, mediaFile.IsVideo()) + assert.True(t, mediaFile.SkipTranscoding()) }) t.Run("norway-kjetil-moe.webp", func(t *testing.T) { mediaFile, err := NewMediaFile("testdata/norway-kjetil-moe.webp") @@ -1224,15 +1224,15 @@ func TestMediaFile_IsImageOther(t *testing.T) { assert.Equal(t, fs.ImageWebp, mediaFile.FileType()) assert.Equal(t, header.ContentTypeWebp, mediaFile.MimeType()) - assert.Equal(t, false, mediaFile.IsJpeg()) - assert.Equal(t, false, mediaFile.IsGif()) - assert.Equal(t, false, mediaFile.IsBmp()) - assert.Equal(t, true, mediaFile.IsWebp()) - assert.Equal(t, true, mediaFile.IsImage()) - assert.Equal(t, true, mediaFile.IsImageNative()) - assert.Equal(t, true, mediaFile.IsImageOther()) - assert.Equal(t, false, mediaFile.IsVideo()) - assert.Equal(t, true, mediaFile.SkipTranscoding()) + assert.False(t, mediaFile.IsJpeg()) + assert.False(t, mediaFile.IsGif()) + assert.False(t, mediaFile.IsBmp()) + assert.True(t, mediaFile.IsWebp()) + assert.True(t, mediaFile.IsImage()) + assert.True(t, mediaFile.IsImageNative()) + assert.True(t, mediaFile.IsImageOther()) + assert.False(t, mediaFile.IsVideo()) + assert.True(t, mediaFile.SkipTranscoding()) }) } @@ -1318,19 +1318,19 @@ func TestMediaFile_IsSidecar(t *testing.T) { t.Run("example.zip", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/example.zip") assert.Nil(t, err) - assert.Equal(t, true, mediaFile.IsArchive()) + assert.True(t, mediaFile.IsArchive()) }) t.Run("iphone_7.xmp", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/iphone_7.xmp") assert.Nil(t, err) - assert.Equal(t, true, mediaFile.IsSidecar()) + assert.True(t, mediaFile.IsSidecar()) }) t.Run("IMG_4120.AAE", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/IMG_4120.AAE") if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsSidecar()) + assert.True(t, mediaFile.IsSidecar()) }) t.Run("test.xml", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/test.xml") @@ -1338,7 +1338,7 @@ func TestMediaFile_IsSidecar(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsSidecar()) + assert.True(t, mediaFile.IsSidecar()) }) t.Run("test.txt", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/test.txt") @@ -1346,7 +1346,7 @@ func TestMediaFile_IsSidecar(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsSidecar()) + assert.True(t, mediaFile.IsSidecar()) }) t.Run("test.yml", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/test.yml") @@ -1354,7 +1354,7 @@ func TestMediaFile_IsSidecar(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsSidecar()) + assert.True(t, mediaFile.IsSidecar()) }) t.Run("test.md", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/test.md") @@ -1362,7 +1362,7 @@ func TestMediaFile_IsSidecar(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, true, mediaFile.IsSidecar()) + assert.True(t, mediaFile.IsSidecar()) }) t.Run("canon_eos_6d.dng", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/canon_eos_6d.dng") @@ -1370,7 +1370,7 @@ func TestMediaFile_IsSidecar(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsSidecar()) + assert.False(t, mediaFile.IsSidecar()) }) } @@ -1379,19 +1379,19 @@ func TestMediaFile_IsArchive(t *testing.T) { t.Run("example.zip", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/example.zip") assert.Nil(t, err) - assert.Equal(t, true, mediaFile.IsArchive()) + assert.True(t, mediaFile.IsArchive()) }) t.Run("iphone_7.xmp", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/iphone_7.xmp") assert.Nil(t, err) - assert.Equal(t, false, mediaFile.IsArchive()) + assert.False(t, mediaFile.IsArchive()) }) t.Run("IMG_4120.AAE", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/IMG_4120.AAE") if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsArchive()) + assert.False(t, mediaFile.IsArchive()) }) t.Run("test.xml", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/test.xml") @@ -1399,7 +1399,7 @@ func TestMediaFile_IsArchive(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsArchive()) + assert.False(t, mediaFile.IsArchive()) }) t.Run("test.txt", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/test.txt") @@ -1407,7 +1407,7 @@ func TestMediaFile_IsArchive(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsArchive()) + assert.False(t, mediaFile.IsArchive()) }) t.Run("test.yml", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/test.yml") @@ -1415,7 +1415,7 @@ func TestMediaFile_IsArchive(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsArchive()) + assert.False(t, mediaFile.IsArchive()) }) t.Run("test.md", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/test.md") @@ -1423,7 +1423,7 @@ func TestMediaFile_IsArchive(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsArchive()) + assert.False(t, mediaFile.IsArchive()) }) t.Run("canon_eos_6d.dng", func(t *testing.T) { mediaFile, err := NewMediaFile(cfg.ExamplesPath() + "/canon_eos_6d.dng") @@ -1431,7 +1431,7 @@ func TestMediaFile_IsArchive(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, mediaFile.IsArchive()) + assert.False(t, mediaFile.IsArchive()) }) } func TestMediaFile_IsImage(t *testing.T) { @@ -1442,43 +1442,43 @@ func TestMediaFile_IsImage(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, false, f.IsImage()) - assert.Equal(t, false, f.IsRaw()) - assert.Equal(t, true, f.IsSidecar()) + assert.False(t, f.IsImage()) + assert.False(t, f.IsRaw()) + assert.True(t, f.IsSidecar()) }) t.Run("iphone_7.xmp", func(t *testing.T) { f, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.xmp") assert.Nil(t, err) - assert.Equal(t, false, f.IsImage()) - assert.Equal(t, false, f.IsRaw()) - assert.Equal(t, true, f.IsSidecar()) + assert.False(t, f.IsImage()) + assert.False(t, f.IsRaw()) + assert.True(t, f.IsSidecar()) }) t.Run("iphone_7.heic", func(t *testing.T) { f, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.heic") if err != nil { t.Fatal(err) } - assert.Equal(t, true, f.IsImage()) - assert.Equal(t, false, f.IsRaw()) - assert.Equal(t, false, f.IsSidecar()) + assert.True(t, f.IsImage()) + assert.False(t, f.IsRaw()) + assert.False(t, f.IsSidecar()) }) t.Run("canon_eos_6d.dng", func(t *testing.T) { f, err := NewMediaFile(c.ExamplesPath() + "/canon_eos_6d.dng") if err != nil { t.Fatal(err) } - assert.Equal(t, false, f.IsImage()) - assert.Equal(t, true, f.IsRaw()) - assert.Equal(t, false, f.IsSidecar()) + assert.False(t, f.IsImage()) + assert.True(t, f.IsRaw()) + assert.False(t, f.IsSidecar()) }) t.Run("elephants.jpg", func(t *testing.T) { f, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg") if err != nil { t.Fatal(err) } - assert.Equal(t, true, f.IsImage()) - assert.Equal(t, false, f.IsRaw()) - assert.Equal(t, false, f.IsSidecar()) + assert.True(t, f.IsImage()) + assert.False(t, f.IsRaw()) + assert.False(t, f.IsSidecar()) }) } @@ -1489,33 +1489,87 @@ func TestMediaFile_IsVideo(t *testing.T) { if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "christmas.mp4")); err != nil { t.Fatal(err) } else { - assert.Equal(t, false, f.IsRaw()) - assert.Equal(t, false, f.IsImage()) - assert.Equal(t, true, f.IsVideo()) - assert.Equal(t, false, f.IsJSON()) - assert.Equal(t, false, f.IsSidecar()) + assert.False(t, f.IsRaw()) + assert.False(t, f.IsImage()) + assert.True(t, f.IsVideo()) + assert.False(t, f.IsJSON()) + assert.False(t, f.IsSidecar()) } }) t.Run("canon_eos_6d.dng", func(t *testing.T) { if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "canon_eos_6d.dng")); err != nil { t.Fatal(err) } else { - assert.Equal(t, true, f.IsRaw()) - assert.Equal(t, false, f.IsImage()) - assert.Equal(t, false, f.IsVideo()) - assert.Equal(t, false, f.IsJSON()) - assert.Equal(t, false, f.IsSidecar()) + assert.True(t, f.IsRaw()) + assert.False(t, f.IsImage()) + assert.False(t, f.IsVideo()) + assert.False(t, f.IsJSON()) + assert.False(t, f.IsSidecar()) } }) t.Run("iphone_7.json", func(t *testing.T) { if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "iphone_7.json")); err != nil { t.Fatal(err) } else { - assert.Equal(t, false, f.IsRaw()) - assert.Equal(t, false, f.IsImage()) - assert.Equal(t, false, f.IsVideo()) - assert.Equal(t, true, f.IsJSON()) - assert.Equal(t, true, f.IsSidecar()) + assert.False(t, f.IsRaw()) + assert.False(t, f.IsImage()) + assert.False(t, f.IsVideo()) + assert.True(t, f.IsJSON()) + assert.True(t, f.IsSidecar()) + } + }) +} + +func TestMediaFile_IsLive(t *testing.T) { + c := config.TestConfig() + + t.Run("2018-04-12 19_24_49.jpg", func(t *testing.T) { + fileName := fs.Abs("testdata/2018-04-12 19_24_49.jpg") + if f, err := NewMediaFile(fileName); err != nil { + t.Fatal(err) + } else { + assert.False(t, f.IsLive()) // Image is not in originals path. + assert.False(t, f.IsRaw()) + assert.True(t, f.IsImage()) + assert.False(t, f.IsVideo()) + assert.False(t, f.IsJSON()) + assert.False(t, f.IsSidecar()) + } + }) + t.Run("christmas.mp4", func(t *testing.T) { + if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "christmas.mp4")); err != nil { + t.Fatal(err) + } else { + assert.False(t, f.IsLive()) + assert.False(t, f.IsRaw()) + assert.False(t, f.IsImage()) + assert.True(t, f.IsVideo()) + assert.False(t, f.IsJSON()) + assert.False(t, f.IsSidecar()) + } + }) + t.Run("canon_eos_6d.dng", func(t *testing.T) { + if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "canon_eos_6d.dng")); err != nil { + t.Fatal(err) + } else { + assert.False(t, f.IsLive()) + assert.True(t, f.IsRaw()) + assert.False(t, f.IsImage()) + assert.False(t, f.IsVideo()) + assert.False(t, f.IsJSON()) + assert.False(t, f.IsSidecar()) + } + }) + t.Run("iphone_7.json", func(t *testing.T) { + if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "iphone_7.json")); err != nil { + t.Fatal(err) + } else { + assert.False(t, f.IsLive()) + assert.False(t, f.IsRaw()) + assert.False(t, f.IsImage()) + assert.False(t, f.IsVideo()) + assert.True(t, f.IsJSON()) + assert.True(t, f.IsSidecar()) } }) } @@ -1526,77 +1580,77 @@ func TestMediaFile_IsAnimated(t *testing.T) { if f, err := NewMediaFile("testdata/star.avifs"); err != nil { t.Fatal(err) } else { - assert.Equal(t, true, f.IsImage()) - assert.Equal(t, true, f.IsAvifS()) - assert.Equal(t, true, f.IsAnimated()) - assert.Equal(t, false, f.NotAnimated()) - assert.Equal(t, true, f.IsAnimatedImage()) - assert.Equal(t, true, f.ExifSupported()) - assert.Equal(t, false, f.IsVideo()) - assert.Equal(t, false, f.IsGif()) - assert.Equal(t, false, f.IsWebp()) - assert.Equal(t, false, f.IsAvif()) - assert.Equal(t, false, f.IsHeic()) - assert.Equal(t, false, f.IsHeicS()) - assert.Equal(t, false, f.IsSidecar()) + assert.True(t, f.IsImage()) + assert.True(t, f.IsAvifS()) + assert.True(t, f.IsAnimated()) + assert.False(t, f.NotAnimated()) + assert.True(t, f.IsAnimatedImage()) + assert.True(t, f.ExifSupported()) + assert.False(t, f.IsVideo()) + assert.False(t, f.IsGif()) + assert.False(t, f.IsWebp()) + assert.False(t, f.IsAvif()) + assert.False(t, f.IsHeic()) + assert.False(t, f.IsHeicS()) + assert.False(t, f.IsSidecar()) } }) t.Run("windows95.webp", func(t *testing.T) { if f, err := NewMediaFile("testdata/windows95.webp"); err != nil { t.Fatal(err) } else { - assert.Equal(t, true, f.IsImage()) - assert.Equal(t, true, f.IsWebp()) - assert.Equal(t, true, f.IsAnimated()) - assert.Equal(t, false, f.NotAnimated()) - assert.Equal(t, true, f.IsAnimatedImage()) - assert.Equal(t, false, f.ExifSupported()) - assert.Equal(t, false, f.IsVideo()) - assert.Equal(t, false, f.IsGif()) - assert.Equal(t, false, f.IsAvif()) - assert.Equal(t, false, f.IsAvifS()) - assert.Equal(t, false, f.IsHeic()) - assert.Equal(t, false, f.IsHeicS()) - assert.Equal(t, false, f.IsSidecar()) + assert.True(t, f.IsImage()) + assert.True(t, f.IsWebp()) + assert.True(t, f.IsAnimated()) + assert.False(t, f.NotAnimated()) + assert.True(t, f.IsAnimatedImage()) + assert.False(t, f.ExifSupported()) + assert.False(t, f.IsVideo()) + assert.False(t, f.IsGif()) + assert.False(t, f.IsAvif()) + assert.False(t, f.IsAvifS()) + assert.False(t, f.IsHeic()) + assert.False(t, f.IsHeicS()) + assert.False(t, f.IsSidecar()) } }) t.Run("example.gif", func(t *testing.T) { if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "example.gif")); err != nil { t.Fatal(err) } else { - assert.Equal(t, true, f.IsImage()) - assert.Equal(t, false, f.IsVideo()) - assert.Equal(t, false, f.IsAnimated()) - assert.Equal(t, true, f.NotAnimated()) - assert.Equal(t, true, f.IsGif()) - assert.Equal(t, false, f.IsAnimatedImage()) - assert.Equal(t, false, f.IsSidecar()) + assert.True(t, f.IsImage()) + assert.False(t, f.IsVideo()) + assert.False(t, f.IsAnimated()) + assert.True(t, f.NotAnimated()) + assert.True(t, f.IsGif()) + assert.False(t, f.IsAnimatedImage()) + assert.False(t, f.IsSidecar()) } }) t.Run("pythagoras.gif", func(t *testing.T) { if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "pythagoras.gif")); err != nil { t.Fatal(err) } else { - assert.Equal(t, true, f.IsImage()) - assert.Equal(t, false, f.IsVideo()) - assert.Equal(t, true, f.IsAnimated()) - assert.Equal(t, false, f.NotAnimated()) - assert.Equal(t, true, f.IsGif()) - assert.Equal(t, true, f.IsAnimatedImage()) - assert.Equal(t, false, f.IsSidecar()) + assert.True(t, f.IsImage()) + assert.False(t, f.IsVideo()) + assert.True(t, f.IsAnimated()) + assert.False(t, f.NotAnimated()) + assert.True(t, f.IsGif()) + assert.True(t, f.IsAnimatedImage()) + assert.False(t, f.IsSidecar()) } }) t.Run("christmas.mp4", func(t *testing.T) { if f, err := NewMediaFile(filepath.Join(c.ExamplesPath(), "christmas.mp4")); err != nil { t.Fatal(err) } else { - assert.Equal(t, false, f.IsImage()) - assert.Equal(t, true, f.IsVideo()) - assert.Equal(t, true, f.IsAnimated()) - assert.Equal(t, false, f.NotAnimated()) - assert.Equal(t, false, f.IsGif()) - assert.Equal(t, false, f.IsAnimatedImage()) - assert.Equal(t, false, f.IsSidecar()) + assert.False(t, f.IsImage()) + assert.True(t, f.IsVideo()) + assert.True(t, f.IsAnimated()) + assert.False(t, f.NotAnimated()) + assert.False(t, f.IsGif()) + assert.False(t, f.IsAnimatedImage()) + assert.False(t, f.IsSidecar()) } }) } @@ -2222,7 +2276,7 @@ func TestMediaFile_FileType(t *testing.T) { assert.Equal(t, "png", string(m.FileType())) assert.Equal(t, "image/jpeg", m.MimeType()) assert.Equal(t, "image/jpeg", m.BaseType()) - assert.Equal(t, true, m.HasMimeType("image/jpeg")) + assert.True(t, m.HasMimeType("image/jpeg")) assert.Equal(t, fs.ImagePng, m.FileType()) assert.Equal(t, ".png", m.Extension()) }) @@ -2238,7 +2292,7 @@ func TestMediaFile_FileType(t *testing.T) { assert.False(t, m.IsPng()) assert.Equal(t, "thm", string(m.FileType())) assert.Equal(t, "image/jpeg", m.MimeType()) - assert.Equal(t, true, m.HasMimeType("image/jpeg")) + assert.True(t, m.HasMimeType("image/jpeg")) assert.Equal(t, fs.ImageThumb, m.FileType()) assert.Equal(t, ".thm", m.Extension()) }) diff --git a/pkg/fs/file_type.go b/pkg/fs/file_type.go index 44fdc4793..842424bbc 100644 --- a/pkg/fs/file_type.go +++ b/pkg/fs/file_type.go @@ -96,8 +96,8 @@ func (t Type) FindFirst(fileName string, dirs []string, baseDir string, stripSeq fileBaseLower := strings.ToLower(fileBasePrefix) fileBaseUpper := strings.ToUpper(fileBasePrefix) - fileDir := filepath.Dir(fileName) - search := append([]string{fileDir}, dirs...) + filePath := filepath.Dir(fileName) + search := append([]string{filePath}, dirs...) for _, ext := range FileTypes[t] { lastDir := "" @@ -109,11 +109,11 @@ func (t Type) FindFirst(fileName string, dirs []string, baseDir string, stripSeq lastDir = dir - if dir != fileDir { + if dir != filePath { if filepath.IsAbs(dir) { - dir = filepath.Join(dir, RelName(fileDir, baseDir)) + dir = filepath.Join(dir, RelName(filePath, baseDir)) } else { - dir = filepath.Join(fileDir, dir) + dir = filepath.Join(filePath, dir) } } @@ -145,8 +145,8 @@ func (t Type) FindAll(fileName string, dirs []string, baseDir string, stripSeque fileBaseLower := strings.ToLower(fileBasePrefix) fileBaseUpper := strings.ToUpper(fileBasePrefix) - fileDir := filepath.Dir(fileName) - search := append([]string{fileDir}, dirs...) + filePath := filepath.Dir(fileName) + search := append([]string{filePath}, dirs...) for _, ext := range FileTypes[t] { lastDir := "" @@ -158,11 +158,11 @@ func (t Type) FindAll(fileName string, dirs []string, baseDir string, stripSeque lastDir = dir - if dir != fileDir { + if dir != filePath { if filepath.IsAbs(dir) { - dir = filepath.Join(dir, RelName(fileDir, baseDir)) + dir = filepath.Join(dir, RelName(filePath, baseDir)) } else { - dir = filepath.Join(fileDir, dir) + dir = filepath.Join(filePath, dir) } } diff --git a/pkg/media/filename.go b/pkg/media/filename.go index 3104e8e79..8199fa86c 100644 --- a/pkg/media/filename.go +++ b/pkg/media/filename.go @@ -21,5 +21,5 @@ func FromName(fileName string) Type { // MainFile checks if the filename belongs to a main content type. func MainFile(fileName string) bool { - return FromName(fileName).Main() + return FromName(fileName).IsMain() } diff --git a/pkg/media/type.go b/pkg/media/type.go index 9fa2fcbd0..b7551a41c 100644 --- a/pkg/media/type.go +++ b/pkg/media/type.go @@ -22,14 +22,9 @@ func (t Type) NotEqual(s string) bool { return !t.Equal(s) } -// Main checks if this is a known main media content format. -func (t Type) Main() bool { - switch t { - case Animated, Audio, Document, Image, Live, Raw, Vector, Video: - return true - default: - return false - } +// IsMain checks whether this is a primary media type, such as an image or video. +func (t Type) IsMain() bool { + return Priority[t] >= PriorityMain } // Unknown checks if the type is unknown. diff --git a/pkg/media/type_test.go b/pkg/media/type_test.go index b46d85ffc..067cde4f9 100644 --- a/pkg/media/type_test.go +++ b/pkg/media/type_test.go @@ -6,18 +6,35 @@ import ( "github.com/stretchr/testify/assert" ) -func TestType_Main(t *testing.T) { +func TestType_IsMain(t *testing.T) { t.Run("Unknown", func(t *testing.T) { - assert.False(t, Unknown.Main()) + assert.False(t, Unknown.IsMain()) }) t.Run("Image", func(t *testing.T) { - assert.True(t, Image.Main()) + assert.True(t, Image.IsMain()) }) t.Run("Video", func(t *testing.T) { - assert.True(t, Video.Main()) + assert.True(t, Video.IsMain()) }) t.Run("Sidecar", func(t *testing.T) { - assert.False(t, Sidecar.Main()) + assert.False(t, Sidecar.IsMain()) + }) +} + +func TestType_Priority(t *testing.T) { + t.Run("Equal", func(t *testing.T) { + assert.Equal(t, Priority[Unknown], Priority["foo"]) + assert.Equal(t, Priority[Image], Priority[Image]) + assert.Equal(t, Priority[Live], Priority[Live]) + assert.Equal(t, Priority[Animated], Priority[Animated]) + assert.Equal(t, Priority[Animated], Priority[Audio]) + assert.Equal(t, Priority[Animated], Priority[Document]) + }) + t.Run("Less", func(t *testing.T) { + assert.Less(t, Priority[Unknown], Priority[Image]) + assert.Less(t, Priority[Unknown], Priority[Live]) + assert.Less(t, Priority[Image], Priority[Live]) + assert.Less(t, Priority[Video], Priority[Live]) }) } diff --git a/pkg/media/types.go b/pkg/media/types.go index 1debfec82..f3c6a8d78 100644 --- a/pkg/media/types.go +++ b/pkg/media/types.go @@ -1,15 +1,38 @@ package media +// Constants representing standard media types. const ( Unknown Type = "" Archive Type = "archive" + Sidecar Type = "sidecar" + Image Type = "image" + Video Type = "video" Animated Type = "animated" Audio Type = "audio" Document Type = "document" - Image Type = "image" Raw Type = "raw" - Sidecar Type = "sidecar" - Live Type = "live" Vector Type = "vector" - Video Type = "video" + Live Type = "live" ) + +// PriorityMain specifies the minimum priority for main media types, +// like Animated, Audio, Document, Image, Live, Raw, Vector, and Video. +const PriorityMain = 4 + +// Priorities maps media types to integer values that represent their relative importance. +type Priorities map[Type]int + +// Priority assigns a relative priority value to the media type constants defined above. +var Priority = Priorities{ + Unknown: 0, + Sidecar: 1, + Archive: 2, + Image: PriorityMain, + Video: 8, + Animated: 16, + Audio: 16, + Document: 16, + Raw: 32, + Vector: 32, + Live: 64, +}