diff --git a/ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs b/ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs index 54977293..20c62705 100644 --- a/ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs +++ b/ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs @@ -39,6 +39,7 @@ public void Setup() _personAssetsPool = new TestablePersonAssetsPool(_mockApiCache.Object, _mockImmichApi.Object, _mockAccountSettings.Object); _mockAccountSettings.SetupGet(s => s.People).Returns(new List()); + _mockAccountSettings.SetupGet(s => s.ExcludedPeople).Returns(new List()); } private AssetResponseDto CreateAsset(string id, AssetTypeEnum type = AssetTypeEnum.IMAGE) => new AssetResponseDto { Id = id, Type = type }; diff --git a/ImmichFrame.Core/Interfaces/IServerSettings.cs b/ImmichFrame.Core/Interfaces/IServerSettings.cs index fea6c442..15829a0b 100644 --- a/ImmichFrame.Core/Interfaces/IServerSettings.cs +++ b/ImmichFrame.Core/Interfaces/IServerSettings.cs @@ -24,6 +24,7 @@ public interface IAccountSettings public List ExcludedAlbums { get; } public List People { get; } public List Tags { get; } + public List ExcludedPeople { get; } public int? Rating { get; } public void ValidateAndInitialize(); @@ -59,6 +60,8 @@ public interface IGeneralSettings public string Style { get; } public string? BaseFontSize { get; } public bool ShowWeatherDescription { get; } + public bool ShowTemperatureUnit { get; } + public int TemperatureDecimalDigits { get; } public string? WeatherIconUrl { get; } public bool ImageZoom { get; } public bool ImagePan { get; } diff --git a/ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs b/ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs index 8aa52bd8..929aa6fa 100644 --- a/ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs +++ b/ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs @@ -1,4 +1,5 @@ using ImmichFrame.Core.Api; +using ImmichFrame.Core.Helpers; using ImmichFrame.Core.Interfaces; namespace ImmichFrame.Core.Logic.Pool; @@ -10,41 +11,53 @@ protected override async Task> LoadAssets(Cancella var personAssets = new List(); var people = accountSettings.People; - if (people == null) + if (people == null || people.Count == 0) { return personAssets; } - + foreach (var personId in people) { - int page = 1; - int batchSize = 1000; - int total; - do - { - var metadataBody = new MetadataSearchDto - { - Page = page, - Size = batchSize, - PersonIds = [personId], - WithExif = true, - WithPeople = true - }; - - if (!accountSettings.ShowVideos) - { - metadataBody.Type = AssetTypeEnum.IMAGE; - } - - var personInfo = await immichApi.SearchAssetsAsync(metadataBody, ct); - - total = personInfo.Assets.Total; - - personAssets.AddRange(personInfo.Assets.Items); - page++; - } while (total == batchSize); + personAssets.AddRange(await LoadAssetsForPerson(personId, ct)); + } + + var excludedPersonAssets = new List(); + + foreach (var personId in accountSettings.ExcludedPeople) + { + excludedPersonAssets.AddRange(await LoadAssetsForPerson(personId, ct)); } - return personAssets; + return personAssets.WhereExcludes(excludedPersonAssets, t => t.Id); + } + + private async Task> LoadAssetsForPerson(Guid personId, CancellationToken ct) + { + var assets = new List(); + int page = 1; + int batchSize = 1000; + int lastPageCount; + + do + { + var metadataBody = new MetadataSearchDto + { + Page = page, + Size = batchSize, + PersonIds = [personId], + Type = AssetTypeEnum.IMAGE, + WithExif = true, + WithPeople = true + }; + + var personInfo = await immichApi.SearchAssetsAsync(metadataBody, ct); + + lastPageCount = personInfo.Assets.Items.Count; + + assets.AddRange(personInfo.Assets.Items); + page++; + } while (lastPageCount == batchSize); + + return assets; } } \ No newline at end of file diff --git a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs index 15c3254b..be9dd5d8 100644 --- a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs +++ b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs @@ -70,7 +70,7 @@ public void TestLoadConfigV2Yaml() private void VerifyConfig(IServerSettings serverSettings, bool usePrefix, bool expectNullApiKeyFile) { - VerifyProperties(serverSettings.GeneralSettings); + VerifyProperties(serverSettings.GeneralSettings, "", expectNullApiKeyFile); VerifyAccounts(serverSettings.Accounts, usePrefix, expectNullApiKeyFile); } @@ -120,7 +120,15 @@ private void VerifyProperties(object o, string? prefix = "", bool expectNullApiK Assert.That(value, Is.EqualTo(true), prop.Name); break; case var t when t == typeof(int): - Assert.That(value, Is.EqualTo(7), prop.Name); + // V1 config doesn't support TemperatureDecimalDigits, so it defaults to 1 + if (prop.Name.Equals("TemperatureDecimalDigits") && expectNullApiKeyFile) + { + Assert.That(value, Is.EqualTo(1), prop.Name); + } + else + { + Assert.That(value, Is.EqualTo(prop.Name.Equals("TemperatureDecimalDigits") ? 2 : 7), prop.Name); + } break; case var t when t == typeof(double): Assert.That(value, Is.EqualTo(7.7d), prop.Name); diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2.json b/ImmichFrame.WebApi.Tests/Resources/TestV2.json index 4d603dc9..81699a0d 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2.json @@ -30,6 +30,8 @@ "Style": "Style_TEST", "BaseFontSize": "BaseFontSize_TEST", "ShowWeatherDescription": true, + "ShowTemperatureUnit": true, + "TemperatureDecimalDigits": 2, "WeatherIconUrl": "WeatherIconUrl_TEST", "ImageZoom": true, "ImagePan": true, @@ -61,6 +63,9 @@ ], "Tags": [ "Account1.Tags_TEST" + ], + "ExcludedPeople": [ + "00000000-0000-0000-0000-000000000001" ] }, { @@ -86,6 +91,9 @@ ], "Tags": [ "Account2.Tags_TEST" + ], + "ExcludedPeople": [ + "00000000-0000-0000-0000-000000000001" ] } ] diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2.yml b/ImmichFrame.WebApi.Tests/Resources/TestV2.yml index 47f45947..933ea51d 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2.yml +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2.yml @@ -29,6 +29,8 @@ General: Style: Style_TEST BaseFontSize: BaseFontSize_TEST ShowWeatherDescription: true + ShowTemperatureUnit: true + TemperatureDecimalDigits: 2 WeatherIconUrl: WeatherIconUrl_TEST ImageZoom: true ImagePan: true @@ -55,6 +57,8 @@ Accounts: - 00000000-0000-0000-0000-000000000001 Tags: - Account1.Tags_TEST + ExcludedPeople: + - 00000000-0000-0000-0000-000000000001 - ImmichServerUrl: Account2.ImmichServerUrl_TEST ApiKey: Account2.ApiKey_TEST ApiKeyFile: Account2.ApiKeyFile_TEST @@ -74,3 +78,5 @@ Accounts: - 00000000-0000-0000-0000-000000000001 Tags: - Account2.Tags_TEST + ExcludedPeople: + - 00000000-0000-0000-0000-000000000001 diff --git a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs index 076f36da..7d098d2f 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs @@ -22,6 +22,7 @@ public class ServerSettingsV1 : IConfigSettable public List ExcludedAlbums { get; set; } = new List(); public List People { get; set; } = new List(); public List Tags { get; set; } = new List(); + public List ExcludedPeople { get; set; } = new List(); public int? Rating { get; set; } public List Webcalendars { get; set; } = new List(); public int RefreshAlbumPeopleInterval { get; set; } = 12; @@ -93,6 +94,7 @@ class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings public List ExcludedAlbums => _delegate.ExcludedAlbums; public List People => _delegate.People; public List Tags => _delegate.Tags; + public List ExcludedPeople => _delegate.ExcludedPeople; public int? Rating => _delegate.Rating; public void ValidateAndInitialize() { } @@ -128,6 +130,8 @@ class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings public string Style => _delegate.Style; public string? BaseFontSize => _delegate.BaseFontSize; public bool ShowWeatherDescription => _delegate.ShowWeatherDescription; + public bool ShowTemperatureUnit => true; + public int TemperatureDecimalDigits => 1; public string? WeatherIconUrl => _delegate.WeatherIconUrl; public bool ImageZoom => _delegate.ImageZoom; public bool ImagePan => _delegate.ImagePan; @@ -136,6 +140,14 @@ class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings public string Layout => _delegate.Layout; public string Language => _delegate.Language; - public void Validate() { } + public void Validate() + { + if (TemperatureDecimalDigits < 0 || TemperatureDecimalDigits > 2) + { + throw new ArgumentOutOfRangeException(nameof(TemperatureDecimalDigits), + TemperatureDecimalDigits, + "TemperatureDecimalDigits must be between 0 and 2."); + } + } } } diff --git a/ImmichFrame.WebApi/Models/ClientSettingsDto.cs b/ImmichFrame.WebApi/Models/ClientSettingsDto.cs index ff0f9e75..116ecf2f 100644 --- a/ImmichFrame.WebApi/Models/ClientSettingsDto.cs +++ b/ImmichFrame.WebApi/Models/ClientSettingsDto.cs @@ -25,6 +25,8 @@ public class ClientSettingsDto public string Style { get; set; } public string? BaseFontSize { get; set; } public bool ShowWeatherDescription { get; set; } + public bool ShowTemperatureUnit { get; set; } + public int TemperatureDecimalDigits { get; set; } public string? WeatherIconUrl { get; set; } public bool ImageZoom { get; set; } public bool ImagePan { get; set; } @@ -57,6 +59,8 @@ public static ClientSettingsDto FromGeneralSettings(IGeneralSettings generalSett dto.Style = generalSettings.Style; dto.BaseFontSize = generalSettings.BaseFontSize; dto.ShowWeatherDescription = generalSettings.ShowWeatherDescription; + dto.ShowTemperatureUnit = generalSettings.ShowTemperatureUnit; + dto.TemperatureDecimalDigits = generalSettings.TemperatureDecimalDigits; dto.WeatherIconUrl = generalSettings.WeatherIconUrl; dto.ImageZoom = generalSettings.ImageZoom; dto.ImagePan = generalSettings.ImagePan; diff --git a/ImmichFrame.WebApi/Models/ServerSettings.cs b/ImmichFrame.WebApi/Models/ServerSettings.cs index 74d0fb8e..cb659d8d 100644 --- a/ImmichFrame.WebApi/Models/ServerSettings.cs +++ b/ImmichFrame.WebApi/Models/ServerSettings.cs @@ -58,6 +58,8 @@ public class GeneralSettings : IGeneralSettings, IConfigSettable public string Style { get; set; } = "none"; public string? BaseFontSize { get; set; } public bool ShowWeatherDescription { get; set; } = true; + public bool ShowTemperatureUnit { get; set; } = true; + public int TemperatureDecimalDigits { get; set; } = 1; public string? WeatherIconUrl { get; set; } = "https://openweathermap.org/img/wn/{IconId}.png"; public bool ImageZoom { get; set; } = true; public bool ImagePan { get; set; } = false; @@ -73,7 +75,15 @@ public class GeneralSettings : IGeneralSettings, IConfigSettable public string? Webhook { get; set; } public string? AuthenticationSecret { get; set; } - public void Validate() { } + public void Validate() + { + if (TemperatureDecimalDigits < 0 || TemperatureDecimalDigits > 2) + { + throw new ArgumentOutOfRangeException(nameof(TemperatureDecimalDigits), + TemperatureDecimalDigits, + "TemperatureDecimalDigits must be between 0 and 2."); + } + } } public class ServerAccountSettings : IAccountSettings, IConfigSettable @@ -93,6 +103,7 @@ public class ServerAccountSettings : IAccountSettings, IConfigSettable public List ExcludedAlbums { get; set; } = new(); public List People { get; set; } = new(); public List Tags { get; set; } = new(); + public List ExcludedPeople { get; set; } = new(); public int? Rating { get; set; } public void ValidateAndInitialize() diff --git a/docker/Settings.example.json b/docker/Settings.example.json index a86a4d00..4c42d11c 100644 --- a/docker/Settings.example.json +++ b/docker/Settings.example.json @@ -62,6 +62,9 @@ "Tags": [ "Vacation", "Travel/Europe" + ], + "ExcludedPeople": [ + "UUID" ] } ] diff --git a/docker/Settings.example.yml b/docker/Settings.example.yml index 173b31a5..a5c2582b 100644 --- a/docker/Settings.example.yml +++ b/docker/Settings.example.yml @@ -57,3 +57,5 @@ Accounts: Tags: - Vacation - Travel/Europe + ExcludedPeople: + - UUID diff --git a/docker/example.env b/docker/example.env index 51d80ed5..7db77563 100644 --- a/docker/example.env +++ b/docker/example.env @@ -24,6 +24,7 @@ ApiKey=KEY # Albums=ALBUM1,ALBUM2 # ExcludedAlbums=ALBUM3,ALBUM4 # People=PERSON1,PERSON2 +# ExcludedPeople=PERSON3,PERSON4 # Webcalendars=https://calendar.google.com/calendar/ical/XXXXXX/public/basic.ics,https://user:pass@calendar.immichframe.dev/dav/calendars/basic.ics # RefreshAlbumPeopleInterval=12 # ShowClock=true diff --git a/immichFrame.Web/src/lib/components/elements/clock.svelte b/immichFrame.Web/src/lib/components/elements/clock.svelte index a19ab602..f0c4e1be 100644 --- a/immichFrame.Web/src/lib/components/elements/clock.svelte +++ b/immichFrame.Web/src/lib/components/elements/clock.svelte @@ -82,18 +82,18 @@ class="text-xl sm:text-xl md:text-2xl lg:text-3xl font-semibold text-shadow-sm weather-info" > {#if $configStore.weatherIconUrl && primaryIconId()} - {weather.description} {/if} - +
{weather.location},
-
{weather.temperature?.toFixed(1)}
-
{weather.unit}
+
{(weather.temperature ?? 0).toFixed($configStore.temperatureDecimalDigits ?? 1)}
+
{$configStore.showTemperatureUnit === false ? '°' : (weather.unit ?? '°')}
- + {#if $configStore.showWeatherDescription}

{weather.description} diff --git a/immichFrame.Web/src/lib/immichFrameApi.ts b/immichFrame.Web/src/lib/immichFrameApi.ts index e8dae934..5a4f6352 100644 --- a/immichFrame.Web/src/lib/immichFrameApi.ts +++ b/immichFrame.Web/src/lib/immichFrameApi.ts @@ -207,6 +207,8 @@ export type ClientSettingsDto = { style?: string | null; baseFontSize?: string | null; showWeatherDescription?: boolean; + showTemperatureUnit?: boolean; + temperatureDecimalDigits?: number; weatherIconUrl?: string | null; imageZoom?: boolean; imagePan?: boolean;