Skip to content

Commit ceddf3e

Browse files
authored
Merge pull request #499 from immichFrame/video_playback
Feat: [Experimental] Video support
2 parents 362a18f + 1efde05 commit ceddf3e

47 files changed

Lines changed: 1292 additions & 379 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,22 @@ public class AlbumAssetsPoolTests
1212
private Mock<IApiCache> _mockApiCache;
1313
private Mock<ImmichApi> _mockImmichApi;
1414
private Mock<IAccountSettings> _mockAccountSettings;
15-
private TestableAlbumAssetsPool _albumAssetsPool;
16-
17-
private class TestableAlbumAssetsPool(IApiCache apiCache, ImmichApi immichApi, IAccountSettings accountSettings)
18-
: AlbumAssetsPool(apiCache, immichApi, accountSettings)
19-
{
20-
// Expose LoadAssets for testing
21-
public Task<IEnumerable<AssetResponseDto>> TestLoadAssets(CancellationToken ct = default) => base.LoadAssets(ct);
22-
}
15+
private AlbumAssetsPool _albumAssetsPool;
2316

2417
[SetUp]
2518
public void Setup()
2619
{
2720
_mockApiCache = new Mock<IApiCache>();
21+
22+
_mockApiCache
23+
.Setup(m => m.GetOrAddAsync(
24+
It.IsAny<string>(),
25+
It.IsAny<Func<Task<IEnumerable<AssetResponseDto>>>>()))
26+
.Returns<string, Func<Task<IEnumerable<AssetResponseDto>>>>((_, factory) => factory());
27+
2828
_mockImmichApi = new Mock<ImmichApi>("", null);
2929
_mockAccountSettings = new Mock<IAccountSettings>();
30-
_albumAssetsPool = new TestableAlbumAssetsPool(_mockApiCache.Object, _mockImmichApi.Object, _mockAccountSettings.Object);
30+
_albumAssetsPool = new AlbumAssetsPool(_mockApiCache.Object, _mockImmichApi.Object, _mockAccountSettings.Object);
3131

3232
_mockAccountSettings.SetupGet(s => s.Albums).Returns(new List<Guid>());
3333
_mockAccountSettings.SetupGet(s => s.ExcludedAlbums).Returns(new List<Guid>());
@@ -45,7 +45,7 @@ public async Task LoadAssets_ReturnsAssetsPresentIIncludedNotExcludedAlbums()
4545
var assetA = CreateAsset("A"); // In album1
4646
var assetB = CreateAsset("B"); // In album1 and excludedAlbum
4747
var assetC = CreateAsset("C"); // In excludedAlbum only
48-
var assetD = CreateAsset("D"); // In album1 only (but not B)
48+
var assetD = CreateAsset("D"); // In album1 only
4949

5050
_mockAccountSettings.SetupGet(s => s.Albums).Returns(new List<Guid> { album1Id });
5151
_mockAccountSettings.SetupGet(s => s.ExcludedAlbums).Returns(new List<Guid> { excludedAlbumId });
@@ -56,7 +56,7 @@ public async Task LoadAssets_ReturnsAssetsPresentIIncludedNotExcludedAlbums()
5656
.ReturnsAsync(new AlbumResponseDto { Assets = new List<AssetResponseDto> { assetB, assetC } });
5757

5858
// Act
59-
var result = (await _albumAssetsPool.TestLoadAssets()).ToList();
59+
var result = (await _albumAssetsPool.GetAssets(25)).ToList();
6060

6161
// Assert
6262
Assert.That(result.Count, Is.EqualTo(2));
@@ -75,7 +75,7 @@ public async Task LoadAssets_NoIncludedAlbums_ReturnsEmpty()
7575
.ReturnsAsync(new AlbumResponseDto { Assets = new List<AssetResponseDto> { CreateAsset("excluded_only") } });
7676

7777

78-
var result = (await _albumAssetsPool.TestLoadAssets()).ToList();
78+
var result = (await _albumAssetsPool.GetAssets(25)).ToList();
7979
Assert.That(result, Is.Empty);
8080
}
8181

@@ -89,7 +89,7 @@ public async Task LoadAssets_NoExcludedAlbums_ReturnsAlbums()
8989
_mockImmichApi.Setup(api => api.GetAlbumInfoAsync(album1Id, null, null, It.IsAny<CancellationToken>()))
9090
.ReturnsAsync(new AlbumResponseDto { Assets = new List<AssetResponseDto> { CreateAsset("A") } });
9191

92-
var result = (await _albumAssetsPool.TestLoadAssets()).ToList();
92+
var result = (await _albumAssetsPool.GetAssets(25)).ToList();
9393
Assert.That(result.Count, Is.EqualTo(1));
9494
Assert.That(result.Any(a => a.Id == "A"));
9595
}
@@ -99,7 +99,7 @@ public async Task LoadAssets_NullAlbums_ReturnsEmpty()
9999
{
100100
_mockAccountSettings.SetupGet(s => s.Albums).Returns((List<Guid>)null);
101101

102-
var result = (await _albumAssetsPool.TestLoadAssets()).ToList();
102+
var result = (await _albumAssetsPool.GetAssets(25)).ToList();
103103
Assert.That(result, Is.Empty);
104104

105105
// the absence of an error, whereas before a null pointer exception would be thrown, indicates success.
@@ -110,7 +110,7 @@ public async Task LoadAssets_NullExcludedAlbums_Succeeds()
110110
{
111111
_mockAccountSettings.SetupGet(s => s.ExcludedAlbums).Returns((List<Guid>)null);
112112

113-
var result = (await _albumAssetsPool.TestLoadAssets()).ToList();
113+
var result = (await _albumAssetsPool.GetAssets(25)).ToList();
114114
Assert.That(result, Is.Empty);
115115

116116
// the absence of an error, whereas before a null pointer exception would be thrown, indicates success.

ImmichFrame.Core.Tests/Logic/Pool/AllAssetsPoolTests.cs

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,36 @@ public void Setup()
3737
It.IsAny<Func<Task<AssetStatsResponseDto>>>() // For GetAssetCount
3838
))
3939
.Returns<string, Func<Task<AssetStatsResponseDto>>>(async (key, factory) => await factory());
40+
41+
_mockApiCache.Setup(c => c.GetOrAddAsync(
42+
It.IsAny<string>(),
43+
It.IsAny<Func<Task<IEnumerable<AssetResponseDto>>>>()
44+
))
45+
.Returns<string, Func<Task<IEnumerable<AssetResponseDto>>>>(async (key, factory) => await factory());
4046
}
4147

42-
private List<AssetResponseDto> CreateSampleAssets(int count, string idPrefix = "asset")
48+
private List<AssetResponseDto> CreateSampleAssets(int count, string idPrefix, AssetTypeEnum type, int? rating = null)
4349
{
4450
return Enumerable.Range(0, count)
45-
.Select(i => new AssetResponseDto { Id = $"{idPrefix}{i}", Type = AssetTypeEnum.IMAGE })
51+
.Select(i => new AssetResponseDto { Id = $"{idPrefix}{i}", Type = type, ExifInfo = new ExifResponseDto { Rating = rating } })
4652
.ToList();
4753
}
4854

55+
private List<AssetResponseDto> CreateSampleImageAssets(int count, string idPrefix = "asset", int? rating = null)
56+
{
57+
return CreateSampleAssets(count, idPrefix, AssetTypeEnum.IMAGE, rating);
58+
}
59+
60+
private List<AssetResponseDto> CreateSampleVideoAssets(int count, string idPrefix = "asset", int? rating = null)
61+
{
62+
return CreateSampleAssets(count, idPrefix, AssetTypeEnum.VIDEO, rating);
63+
}
64+
4965
[Test]
50-
public async Task GetAssetCount_CallsApiAndCache()
66+
public async Task GetAssetCount_CallsApiAndCache_OnlyImages()
5167
{
5268
// Arrange
53-
var stats = new AssetStatsResponseDto { Images = 100 };
69+
var stats = new AssetStatsResponseDto { Images = 100, Videos = 40 };
5470
_mockImmichApi.Setup(api => api.GetAssetStatisticsAsync(null, false, null, It.IsAny<CancellationToken>())).ReturnsAsync(stats);
5571

5672
// Act
@@ -63,29 +79,81 @@ public async Task GetAssetCount_CallsApiAndCache()
6379
}
6480

6581
[Test]
66-
public async Task GetAssets_CallsSearchRandomAsync_WithCorrectParameters()
82+
public async Task GetAssetCount_CallsApiAndCache_WithVideos()
83+
{
84+
// Arrange
85+
var stats = new AssetStatsResponseDto { Images = 100, Videos = 40 };
86+
_mockImmichApi.Setup(api => api.GetAssetStatisticsAsync(null, false, null, It.IsAny<CancellationToken>())).ReturnsAsync(stats);
87+
88+
_mockAccountSettings.SetupGet(s => s.ShowVideos).Returns(true);
89+
90+
// Act
91+
var count = await _allAssetsPool.GetAssetCount();
92+
93+
// Assert
94+
Assert.That(count, Is.EqualTo(140));
95+
_mockImmichApi.Verify(api => api.GetAssetStatisticsAsync(null, false, null, It.IsAny<CancellationToken>()), Times.Once);
96+
_mockApiCache.Verify(cache => cache.GetOrAddAsync(nameof(AllAssetsPool), It.IsAny<Func<Task<AssetStatsResponseDto>>>()), Times.Once);
97+
}
98+
99+
[Test]
100+
public async Task GetAssets_CallsSearchRandomAsync_WithCorrectParameters_OnlyImages()
67101
{
68102
// Arrange
69-
var requestedCount = 5;
103+
var requestedImageCount = 5;
104+
var requestedVideoCount = 8;
105+
var rating = 3;
70106
_mockAccountSettings.SetupGet(s => s.ShowArchived).Returns(true);
71107
_mockAccountSettings.SetupGet(s => s.Rating).Returns(3);
72-
var returnedAssets = CreateSampleAssets(requestedCount);
108+
var returnedAssets = CreateSampleImageAssets(requestedImageCount, rating: rating);
109+
returnedAssets.AddRange(CreateSampleVideoAssets(requestedVideoCount, rating: rating));
73110
_mockImmichApi.Setup(api => api.SearchRandomAsync(It.IsAny<RandomSearchDto>(), It.IsAny<CancellationToken>()))
74-
.ReturnsAsync(returnedAssets);
111+
.ReturnsAsync(returnedAssets.Where(a => a.Type == AssetTypeEnum.IMAGE).ToList());
75112

76113
// Act
77-
var assets = await _allAssetsPool.GetAssets(requestedCount);
114+
var assets = await _allAssetsPool.GetAssets(requestedImageCount);
78115

79116
// Assert
80-
Assert.That(assets.Count(), Is.EqualTo(requestedCount));
117+
Assert.That(assets.Count(), Is.EqualTo(requestedImageCount));
81118
_mockImmichApi.Verify(api => api.SearchRandomAsync(
82119
It.Is<RandomSearchDto>(dto =>
83-
dto.Size == requestedCount &&
120+
dto.Size == requestedImageCount &&
84121
dto.Type == AssetTypeEnum.IMAGE &&
85122
dto.WithExif == true &&
86123
dto.WithPeople == true &&
87124
dto.Visibility == AssetVisibility.Archive && // ShowArchived = true
88-
dto.Rating == 3
125+
dto.Rating == rating
126+
), It.IsAny<CancellationToken>()), Times.Once);
127+
}
128+
129+
[Test]
130+
public async Task GetAssets_CallsSearchRandomAsync_WithCorrectParameters_ImagesAndVideos()
131+
{
132+
// Arrange
133+
var requestedImageCount = 5;
134+
var requestedVideoCount = 8;
135+
var rating = 3;
136+
_mockAccountSettings.SetupGet(s => s.ShowArchived).Returns(true);
137+
_mockAccountSettings.SetupGet(s => s.ShowVideos).Returns(true);
138+
_mockAccountSettings.SetupGet(s => s.Rating).Returns(3);
139+
var returnedAssets = CreateSampleImageAssets(requestedImageCount, rating: rating);
140+
returnedAssets.AddRange(CreateSampleVideoAssets(requestedVideoCount, rating: rating));
141+
_mockImmichApi.Setup(api => api.SearchRandomAsync(It.IsAny<RandomSearchDto>(), It.IsAny<CancellationToken>()))
142+
.ReturnsAsync(returnedAssets.ToList());
143+
144+
// Act
145+
var assets = await _allAssetsPool.GetAssets(requestedImageCount + requestedVideoCount);
146+
147+
// Assert
148+
Assert.That(assets.Count(), Is.EqualTo(requestedImageCount + requestedVideoCount));
149+
_mockImmichApi.Verify(api => api.SearchRandomAsync(
150+
It.Is<RandomSearchDto>(dto =>
151+
dto.Size == (requestedImageCount + requestedVideoCount) &&
152+
dto.Type == null &&
153+
dto.WithExif == true &&
154+
dto.WithPeople == true &&
155+
dto.Visibility == AssetVisibility.Archive && // ShowArchived = true
156+
dto.Rating == rating
89157
), It.IsAny<CancellationToken>()), Times.Once);
90158
}
91159

@@ -108,7 +176,7 @@ public async Task GetAssets_AppliesDateFilters_FromDays()
108176
public async Task GetAssets_ExcludesAssetsFromExcludedAlbums()
109177
{
110178
// Arrange
111-
var mainAssets = CreateSampleAssets(3, "main"); // main0, main1, main2
179+
var mainAssets = CreateSampleImageAssets(3, "main"); // main0, main1, main2
112180
var excludedAsset = new AssetResponseDto { Id = "excluded1", Type = AssetTypeEnum.IMAGE };
113181
var assetsToReturnFromSearch = new List<AssetResponseDto>(mainAssets) { excludedAsset };
114182

@@ -136,7 +204,7 @@ public async Task GetAssets_NullExcludedAlbums_Succeeds()
136204
_mockAccountSettings.SetupGet(s => s.ExcludedAlbums).Returns((List<Guid>)null);
137205

138206
// Create a set of assets to verify that the code was actually exercised (minimize risk of false positives)
139-
var allAssets = CreateSampleAssets(5, "asset");
207+
var allAssets = CreateSampleImageAssets(5, "asset");
140208

141209
_mockImmichApi.Setup(api => api.SearchRandomAsync(It.IsAny<RandomSearchDto>(), It.IsAny<CancellationToken>()))
142210
.ReturnsAsync(allAssets);

0 commit comments

Comments
 (0)