diff --git a/ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs b/ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs index be102aba..1ad3f8e7 100644 --- a/ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs +++ b/ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs @@ -31,6 +31,7 @@ public void Setup() _mockAccountSettings.SetupGet(s => s.Albums).Returns(new List()); _mockAccountSettings.SetupGet(s => s.ExcludedAlbums).Returns(new List()); + _mockAccountSettings.SetupGet(s => s.ShowOnlyAssetsInAlbums).Returns(false); } private AssetResponseDto CreateAsset(string id) => new AssetResponseDto { Id = FixtureHelpers.GuidFor(id), Type = AssetTypeEnum.IMAGE }; @@ -94,6 +95,58 @@ public async Task LoadAssets_NoExcludedAlbums_ReturnsAlbums() Assert.That(result.Any(a => a.Id == FixtureHelpers.GuidFor("A"))); } + [Test] + public async Task LoadAssets_ShowOnlyAssetsInAlbums_LoadsAssetsFromAllAlbums() + { + var album1Id = Guid.NewGuid(); + var album2Id = Guid.NewGuid(); + + _mockAccountSettings.SetupGet(s => s.ShowOnlyAssetsInAlbums).Returns(true); + _mockAccountSettings.SetupGet(s => s.Albums).Returns(new List()); + _mockImmichApi.Setup(api => api.GetAllAlbumsAsync(null, null, null, null, null, It.IsAny())) + .ReturnsAsync(new List + { + new() { Id = album1Id, AlbumName = "One" }, + new() { Id = album2Id, AlbumName = "Two" }, + }); + _mockImmichApi.Setup(api => api.SearchAssetsAsync(It.IsAny(), It.IsAny(), It.Is(d => d.AlbumIds.Contains(album1Id)), It.IsAny())) + .ReturnsAsync(new SearchResponseDto { Assets = new SearchAssetResponseDto { Items = new List { CreateAsset("A") }, Total = 1 } }); + _mockImmichApi.Setup(api => api.SearchAssetsAsync(It.IsAny(), It.IsAny(), It.Is(d => d.AlbumIds.Contains(album2Id)), It.IsAny())) + .ReturnsAsync(new SearchResponseDto { Assets = new SearchAssetResponseDto { Items = new List { CreateAsset("B") }, Total = 1 } }); + + var result = (await _albumAssetsPool.GetAssets(25)).ToList(); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result.Any(a => a.Id == FixtureHelpers.GuidFor("A"))); + Assert.That(result.Any(a => a.Id == FixtureHelpers.GuidFor("B"))); + _mockImmichApi.Verify(api => api.GetAllAlbumsAsync(null, null, null, null, null, It.IsAny()), Times.Once); + } + + [Test] + public async Task LoadAssets_ShowOnlyAssetsInAlbums_DeduplicatesAssetsInMultipleAlbums() + { + var album1Id = Guid.NewGuid(); + var album2Id = Guid.NewGuid(); + var sharedAsset = CreateAsset("shared"); + + _mockAccountSettings.SetupGet(s => s.ShowOnlyAssetsInAlbums).Returns(true); + _mockImmichApi.Setup(api => api.GetAllAlbumsAsync(null, null, null, null, null, It.IsAny())) + .ReturnsAsync(new List + { + new() { Id = album1Id, AlbumName = "One" }, + new() { Id = album2Id, AlbumName = "Two" }, + }); + _mockImmichApi.Setup(api => api.SearchAssetsAsync(It.IsAny(), It.IsAny(), It.Is(d => d.AlbumIds.Contains(album1Id)), It.IsAny())) + .ReturnsAsync(new SearchResponseDto { Assets = new SearchAssetResponseDto { Items = new List { sharedAsset }, Total = 1 } }); + _mockImmichApi.Setup(api => api.SearchAssetsAsync(It.IsAny(), It.IsAny(), It.Is(d => d.AlbumIds.Contains(album2Id)), It.IsAny())) + .ReturnsAsync(new SearchResponseDto { Assets = new SearchAssetResponseDto { Items = new List { sharedAsset }, Total = 1 } }); + + var result = (await _albumAssetsPool.GetAssets(25)).ToList(); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.Single().Id, Is.EqualTo(sharedAsset.Id)); + } + [Test] public async Task LoadAssets_NullAlbums_ReturnsEmpty() { diff --git a/ImmichFrame.Core/Interfaces/IServerSettings.cs b/ImmichFrame.Core/Interfaces/IServerSettings.cs index 9c12a3bd..f0c3830a 100644 --- a/ImmichFrame.Core/Interfaces/IServerSettings.cs +++ b/ImmichFrame.Core/Interfaces/IServerSettings.cs @@ -17,6 +17,7 @@ public interface IAccountSettings public bool ShowFavorites { get; } public bool ShowArchived { get; } public bool ShowVideos { get; } + public bool ShowOnlyAssetsInAlbums { get; } public int? ImagesFromDays { get; } public DateTime? ImagesFromDate { get; } public DateTime? ImagesUntilDate { get; } diff --git a/ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs b/ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs index 836077e9..c80936c8 100644 --- a/ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs +++ b/ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs @@ -9,7 +9,7 @@ protected override async Task> LoadAssets(Cancella { var albumAssets = new List(); - var albums = accountSettings.Albums; + var albums = await GetAlbumIds(ct); if (albums != null) { foreach (var albumId in albums) @@ -37,6 +37,17 @@ protected override async Task> LoadAssets(Cancella } } - return albumAssets; + return albumAssets.DistinctBy(asset => asset.Id); } -} \ No newline at end of file + + private async Task?> GetAlbumIds(CancellationToken ct) + { + if (!accountSettings.ShowOnlyAssetsInAlbums) + { + return accountSettings.Albums; + } + + var albums = await immichApi.GetAllAlbumsAsync(null, null, null, null, null, ct); + return albums.Select(album => album.Id); + } +} diff --git a/ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs b/ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs index 6e2a845c..859285b7 100644 --- a/ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs +++ b/ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs @@ -36,6 +36,11 @@ private static TimeSpan RefreshInterval(int hours) private IAssetPool BuildPool(IAccountSettings accountSettings) { + if (accountSettings.ShowOnlyAssetsInAlbums) + { + return new AlbumAssetsPool(_apiCache, _immichApi, accountSettings); + } + var hasAlbums = accountSettings.Albums?.Any() ?? false; var hasPeople = accountSettings.People?.Any() ?? false; var hasTags = accountSettings.Tags?.Any() ?? false; diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV1.json b/ImmichFrame.WebApi.Tests/Resources/TestV1.json index e6c49102..ca2cfa82 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV1.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV1.json @@ -14,6 +14,7 @@ "ShowFavorites": true, "ShowArchived": true, "ShowVideos": true, + "ShowOnlyAssetsInAlbums": true, "ImagesFromDays": 7, "ImagesFromDate": "2020-01-02", "ImagesUntilDate": "2020-01-02", diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2.json b/ImmichFrame.WebApi.Tests/Resources/TestV2.json index 4d603dc9..c1bdbaeb 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2.json @@ -47,6 +47,7 @@ "ShowFavorites": true, "ShowArchived": true, "ShowVideos": true, + "ShowOnlyAssetsInAlbums": true, "ImagesFromDays": 7, "ImagesUntilDate": "2020-01-02", "Rating": 7, @@ -72,6 +73,7 @@ "ShowFavorites": true, "ShowArchived": true, "ShowVideos": true, + "ShowOnlyAssetsInAlbums": true, "ImagesFromDays": 7, "ImagesUntilDate": "2020-01-02", "Rating": 7, diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2.yml b/ImmichFrame.WebApi.Tests/Resources/TestV2.yml index 47f45947..0f419132 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2.yml +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2.yml @@ -44,6 +44,7 @@ Accounts: ShowFavorites: true ShowArchived: true ShowVideos: true + ShowOnlyAssetsInAlbums: true ImagesFromDays: 7 ImagesUntilDate: '2020-01-02' Rating: 7 @@ -63,6 +64,7 @@ Accounts: ShowFavorites: true ShowArchived: true ShowVideos: true + ShowOnlyAssetsInAlbums: true ImagesFromDays: 7 ImagesUntilDate: '2020-01-02' Rating: 7 diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2_NoGeneral.json b/ImmichFrame.WebApi.Tests/Resources/TestV2_NoGeneral.json index 87279ffe..c74e4086 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2_NoGeneral.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2_NoGeneral.json @@ -7,6 +7,7 @@ "ShowMemories": true, "ShowFavorites": true, "ShowArchived": true, + "ShowOnlyAssetsInAlbums": true, "ImagesFromDays": 7, "ImagesUntilDate": "2020-01-02", "Rating": 7, @@ -28,6 +29,7 @@ "ShowFavorites": true, "ShowArchived": true, "ShowVideos": true, + "ShowOnlyAssetsInAlbums": true, "ImagesFromDays": 7, "ImagesUntilDate": "2020-01-02", "Rating": 7, @@ -42,4 +44,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs index 076f36da..9ef529f7 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs @@ -13,6 +13,7 @@ public class ServerSettingsV1 : IConfigSettable public bool ShowFavorites { get; set; } = false; public bool ShowArchived { get; set; } = false; public bool ShowVideos { get; set; } = false; + public bool ShowOnlyAssetsInAlbums { get; set; } = false; public bool DownloadImages { get; set; } = false; public int RenewImagesDuration { get; set; } = 30; public int? ImagesFromDays { get; set; } @@ -85,6 +86,7 @@ class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings public bool ShowFavorites => _delegate.ShowFavorites; public bool ShowArchived => _delegate.ShowArchived; public bool ShowVideos => _delegate.ShowVideos; + public bool ShowOnlyAssetsInAlbums => _delegate.ShowOnlyAssetsInAlbums; public bool PlayAudio => _delegate.PlayAudio; public int? ImagesFromDays => _delegate.ImagesFromDays; public DateTime? ImagesFromDate => _delegate.ImagesFromDate; diff --git a/ImmichFrame.WebApi/Models/ServerSettings.cs b/ImmichFrame.WebApi/Models/ServerSettings.cs index 74d0fb8e..e773be05 100644 --- a/ImmichFrame.WebApi/Models/ServerSettings.cs +++ b/ImmichFrame.WebApi/Models/ServerSettings.cs @@ -85,6 +85,7 @@ public class ServerAccountSettings : IAccountSettings, IConfigSettable public bool ShowFavorites { get; set; } = false; public bool ShowArchived { get; set; } = false; public bool ShowVideos { get; set; } = false; + public bool ShowOnlyAssetsInAlbums { get; set; } = false; public int? ImagesFromDays { get; set; } public DateTime? ImagesFromDate { get; set; } diff --git a/docs/docs/getting-started/configuration.md b/docs/docs/getting-started/configuration.md index 7378c7d1..a31edbb4 100644 --- a/docs/docs/getting-started/configuration.md +++ b/docs/docs/getting-started/configuration.md @@ -123,6 +123,8 @@ Accounts: ShowArchived: false # boolean # If this is set, video assets are included in the slideshow. ShowVideos: false # boolean + # If this is set, only assets that are part of any album are displayed. + ShowOnlyAssetsInAlbums: false # boolean # Show images from the last X days, e.g., 365 -> show images from the last year ImagesFromDays: null # int # Show images before date. @@ -152,6 +154,8 @@ If this is enabled, the web api required the `Authorization`-Header with `Bearer ### Filtering on Albums or People You can get the UUIDs from the URL of the album/person. For this URL: `https://demo.immich.app/albums/85c85b29-c95d-4a8b-90f7-c87da1d518ba` this is the UUID: `85c85b29-c95d-4a8b-90f7-c87da1d518ba` +Set `ShowOnlyAssetsInAlbums` to `true` to include assets from all albums without listing each album UUID. When enabled, only album assets are considered and `ExcludedAlbums` still applies. + ### Filtering on Tags For tags, use the full hierarchical path (the `value` field) as it appears in Immich. Tags in Immich support hierarchical structures using forward slashes (e.g., `Parent/Child`). Matching is case-sensitive, and the full path will be automatically resolved to the tag ID. diff --git a/docs/docs/getting-started/configurationV1.md b/docs/docs/getting-started/configurationV1.md index bf9ca932..130fee1d 100644 --- a/docs/docs/getting-started/configurationV1.md +++ b/docs/docs/getting-started/configurationV1.md @@ -18,6 +18,7 @@ sidebar_position: 4 | [Filtering](#filtering) | ShowMemories | boolean | false | If this is set, memories are displayed. | | [Filtering](#filtering) | ShowFavorites | boolean | false | If this is set, favorites are displayed. | | [Filtering](#filtering) | ShowArchived | boolean | false | If this is set, assets marked archived are displayed. | +| [Filtering](#filtering) | ShowOnlyAssetsInAlbums | boolean | false | If this is set, assets from all albums are displayed and `Albums` is ignored. `ExcludedAlbums` still applies. | | [Filtering](#filtering) | ImagesFromDays | int | | Show images from the last X days. e.g 365 -> show images from the last year | | [Filtering](#filtering) | ImagesFromDate | Date | | Show images after date. Overwrites the `ImagesFromDays`-Setting | | [Filtering](#filtering) | ImagesUntilDate | Date | | Show images before date. | @@ -111,4 +112,4 @@ volumes: ``` [openweathermap-url]: https://openweathermap.org/appid -[immich-api-url]: https://immich.app/docs/features/command-line-interface#obtain-the-api-key \ No newline at end of file +[immich-api-url]: https://immich.app/docs/features/command-line-interface#obtain-the-api-key