Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40ed4b8b0e | ||
|
|
b43d08ca67 | ||
|
|
5e7e386108 | ||
|
|
4176df9940 | ||
|
|
de2ef959fe | ||
|
|
b53cfebac1 | ||
|
|
6895b9cc6b | ||
|
|
c60d6e46f1 |
+15
-1
@@ -5,6 +5,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.6.4-beta] - 2022-07-28
|
||||
### Fixed
|
||||
- Fix subtitle stream selection when subtitle language is different than audio language
|
||||
- Fix bug with unsupported AAC channel layouts
|
||||
- Fix NVIDIA second-gen maxwell capabilities detection
|
||||
- Return distinct search results for episodes and other videos that have the same title
|
||||
- For example, two other videos both named `Trailer` would previously have displayed as one item in search results
|
||||
- Fix schedules that would begin to repeat the same content in the same order after a couple of days
|
||||
|
||||
### Added
|
||||
- Add `640x480` resolution
|
||||
|
||||
## [0.6.3-beta] - 2022-07-04
|
||||
### Fixed
|
||||
- Maintain stream continuity when playout is rebuilt for a channel that is actively being streamed
|
||||
@@ -1264,7 +1276,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Initial release to facilitate testing outside of Docker.
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.2-beta...HEAD
|
||||
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.4-beta...HEAD
|
||||
[0.6.4-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.3-beta...v0.6.4-beta
|
||||
[0.6.3-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.2-beta...v0.6.3-beta
|
||||
[0.6.2-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.1-beta...v0.6.2-beta
|
||||
[0.6.1-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.0-beta...v0.6.1-beta
|
||||
[0.6.0-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.8-beta...v0.6.0-beta
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
@@ -17,11 +17,11 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
|
||||
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||
private readonly ILogger<MoveLocalLibraryPathHandler> _logger;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
public MoveLocalLibraryPathHandler(
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
ILogger<MoveLocalLibraryPathHandler> logger)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Locking;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Interfaces.Trakt;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
@@ -18,7 +18,7 @@ public class AddTraktListHandler : TraktCommandBase, IRequestHandler<AddTraktLis
|
||||
|
||||
public AddTraktListHandler(
|
||||
ITraktApiClient traktApiClient,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
ISearchIndex searchIndex,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Locking;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Interfaces.Trakt;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
@@ -17,11 +17,11 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
|
||||
private readonly IEntityLocker _entityLocker;
|
||||
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
public DeleteTraktListHandler(
|
||||
ITraktApiClient traktApiClient,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
ISearchIndex searchIndex,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Locking;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Interfaces.Trakt;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
@@ -19,7 +19,7 @@ public class MatchTraktListItemsHandler : TraktCommandBase,
|
||||
|
||||
public MatchTraktListItemsHandler(
|
||||
ITraktApiClient traktApiClient,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
ISearchIndex searchIndex,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Interfaces.Trakt;
|
||||
using ErsatzTV.Core.Trakt;
|
||||
@@ -17,11 +17,11 @@ public abstract class TraktCommandBase
|
||||
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
protected TraktCommandBase(
|
||||
ITraktApiClient traktApiClient,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
ISearchIndex searchIndex,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
ILogger logger)
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using Humanizer;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -16,11 +17,11 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly ILogger<RebuildSearchIndexHandler> _logger;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
public RebuildSearchIndexHandler(
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IConfigElementRepository configElementRepository,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ErsatzTV.Core\ErsatzTV.Core.csproj" />
|
||||
<ProjectReference Include="..\ErsatzTV.Infrastructure\ErsatzTV.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -585,7 +585,7 @@ public class TranscodingTests
|
||||
public Task<IHardwareCapabilities> GetHardwareCapabilities(
|
||||
string ffmpegPath,
|
||||
HardwareAccelerationMode hardwareAccelerationMode) =>
|
||||
Task.FromResult<IHardwareCapabilities>(new NvidiaHardwareCapabilities(61));
|
||||
Task.FromResult<IHardwareCapabilities>(new NvidiaHardwareCapabilities(61, string.Empty));
|
||||
}
|
||||
|
||||
private static string ExecutableName(string baseName) =>
|
||||
|
||||
@@ -5,6 +5,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using ErsatzTV.Core.Tests.Fakes;
|
||||
@@ -622,7 +623,7 @@ public class MovieFolderScannerTests
|
||||
new Mock<IMetadataRepository>().Object,
|
||||
_imageCache.Object,
|
||||
new Mock<ISearchIndex>().Object,
|
||||
new Mock<ISearchRepository>().Object,
|
||||
new Mock<ICachingSearchRepository>().Object,
|
||||
new Mock<IFallbackMetadataProvider>().Object,
|
||||
new Mock<ILibraryRepository>().Object,
|
||||
_mediaItemRepository.Object,
|
||||
@@ -643,7 +644,7 @@ public class MovieFolderScannerTests
|
||||
new Mock<IMetadataRepository>().Object,
|
||||
_imageCache.Object,
|
||||
new Mock<ISearchIndex>().Object,
|
||||
new Mock<ISearchRepository>().Object,
|
||||
new Mock<ICachingSearchRepository>().Object,
|
||||
new Mock<IFallbackMetadataProvider>().Object,
|
||||
new Mock<ILibraryRepository>().Object,
|
||||
_mediaItemRepository.Object,
|
||||
|
||||
@@ -2235,7 +2235,7 @@ public class PlayoutBuilderTests
|
||||
DateTimeOffset start2 = HoursAfterMidnight(0);
|
||||
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
|
||||
|
||||
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2);
|
||||
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2);
|
||||
|
||||
int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed;
|
||||
|
||||
@@ -2244,6 +2244,57 @@ public class PlayoutBuilderTests
|
||||
result2.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShuffleFlood_Should_MaintainRandomSeed_MultipleDays()
|
||||
{
|
||||
var mediaItems = new List<MediaItem>();
|
||||
for (int i = 1; i <= 25; i++)
|
||||
{
|
||||
mediaItems.Add(TestMovie(i, TimeSpan.FromMinutes(55), DateTime.Today.AddHours(i)));
|
||||
}
|
||||
|
||||
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Shuffle);
|
||||
DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5);
|
||||
DateTimeOffset finish = start + TimeSpan.FromDays(2);
|
||||
|
||||
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish);
|
||||
|
||||
result.Items.Count.Should().Be(53);
|
||||
result.ProgramScheduleAnchors.Count.Should().Be(2);
|
||||
|
||||
result.ProgramScheduleAnchors.All(x => x.AnchorDate is not null).Should().BeTrue();
|
||||
PlayoutProgramScheduleAnchor lastCheckpoint = result.ProgramScheduleAnchors
|
||||
.OrderByDescending(a => a.AnchorDate ?? DateTime.MinValue)
|
||||
.First();
|
||||
lastCheckpoint.EnumeratorState.Seed.Should().BeGreaterThan(0);
|
||||
lastCheckpoint.EnumeratorState.Index.Should().Be(3);
|
||||
|
||||
// we need to mess up the ordering to trigger the problematic behavior
|
||||
// this simulates the way the rows are loaded with EF
|
||||
PlayoutProgramScheduleAnchor oldest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate).Last();
|
||||
PlayoutProgramScheduleAnchor newest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate).First();
|
||||
|
||||
result.ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>
|
||||
{
|
||||
oldest,
|
||||
newest
|
||||
};
|
||||
|
||||
int firstSeedValue = lastCheckpoint.EnumeratorState.Seed;
|
||||
|
||||
DateTimeOffset start2 = start.AddHours(1);
|
||||
DateTimeOffset finish2 = start2 + TimeSpan.FromDays(2);
|
||||
|
||||
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2);
|
||||
|
||||
PlayoutProgramScheduleAnchor continueAnchor =
|
||||
result2.ProgramScheduleAnchors.First(x => x.AnchorDate is null);
|
||||
int secondSeedValue = continueAnchor.EnumeratorState.Seed;
|
||||
|
||||
// the continue anchor should have the same seed as the most recent (last) checkpoint from the first run
|
||||
firstSeedValue.Should().Be(secondSeedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FloodContent_Should_FloodWithFixedStartTime_FromAnchor()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
using Dapper;
|
||||
using Destructurama;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Data.Repositories;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using LanguageExt.UnsafeValueAccess;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.Scheduling;
|
||||
|
||||
[TestFixture]
|
||||
[Explicit]
|
||||
public class ScheduleIntegrationTests
|
||||
{
|
||||
public ScheduleIntegrationTests()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning)
|
||||
.WriteTo.Console()
|
||||
.Destructure.UsingAttributes()
|
||||
.CreateLogger();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
string dbFileName = Path.GetTempFileName() + ".sqlite3";
|
||||
|
||||
IServiceCollection services = new ServiceCollection()
|
||||
.AddLogging();
|
||||
|
||||
var connectionString = $"Data Source={dbFileName};foreign keys=true;";
|
||||
|
||||
services.AddDbContext<TvContext>(
|
||||
options => options.UseSqlite(
|
||||
connectionString,
|
||||
o =>
|
||||
{
|
||||
o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
|
||||
o.MigrationsAssembly("ErsatzTV.Infrastructure");
|
||||
}),
|
||||
ServiceLifetime.Scoped,
|
||||
ServiceLifetime.Singleton);
|
||||
|
||||
services.AddDbContextFactory<TvContext>(
|
||||
options => options.UseSqlite(
|
||||
connectionString,
|
||||
o =>
|
||||
{
|
||||
o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
|
||||
o.MigrationsAssembly("ErsatzTV.Infrastructure");
|
||||
}));
|
||||
|
||||
SqlMapper.AddTypeHandler(new DateTimeOffsetHandler());
|
||||
SqlMapper.AddTypeHandler(new GuidHandler());
|
||||
SqlMapper.AddTypeHandler(new TimeSpanHandler());
|
||||
|
||||
services.AddSingleton((Func<IServiceProvider, ILoggerFactory>)(_ => new SerilogLoggerFactory()));
|
||||
|
||||
ServiceProvider provider = services.BuildServiceProvider();
|
||||
|
||||
IDbContextFactory<TvContext> factory = provider.GetRequiredService<IDbContextFactory<TvContext>>();
|
||||
|
||||
ILogger<ScheduleIntegrationTests> logger = provider.GetRequiredService<ILogger<ScheduleIntegrationTests>>();
|
||||
logger.LogInformation("Database is at {File}", dbFileName);
|
||||
|
||||
await using TvContext dbContext = await factory.CreateDbContextAsync(CancellationToken.None);
|
||||
await dbContext.Database.MigrateAsync(CancellationToken.None);
|
||||
await DbInitializer.Initialize(dbContext, CancellationToken.None);
|
||||
|
||||
var path = new LibraryPath
|
||||
{
|
||||
Path = "Test LibraryPath"
|
||||
};
|
||||
|
||||
var library = new LocalLibrary
|
||||
{
|
||||
MediaKind = LibraryMediaKind.Movies,
|
||||
Paths = new List<LibraryPath> { path },
|
||||
MediaSource = new LocalMediaSource()
|
||||
};
|
||||
|
||||
await dbContext.Libraries.AddAsync(library);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var movies = new List<Movie>();
|
||||
for (var i = 1; i < 25; i++)
|
||||
{
|
||||
var movie = new Movie
|
||||
{
|
||||
MediaVersions = new List<MediaVersion>
|
||||
{
|
||||
new() { Duration = TimeSpan.FromMinutes(55) }
|
||||
},
|
||||
MovieMetadata = new List<MovieMetadata>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = $"Movie {i}",
|
||||
ReleaseDate = new DateTime(2000, 1, 1).AddDays(i)
|
||||
}
|
||||
},
|
||||
LibraryPath = path,
|
||||
LibraryPathId = path.Id
|
||||
};
|
||||
|
||||
movies.Add(movie);
|
||||
}
|
||||
|
||||
await dbContext.Movies.AddRangeAsync(movies);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var collection = new Collection
|
||||
{
|
||||
Name = "Test Collection",
|
||||
MediaItems = movies.Cast<MediaItem>().ToList()
|
||||
};
|
||||
|
||||
await dbContext.Collections.AddAsync(collection);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var scheduleItems = new List<ProgramScheduleItem>
|
||||
{
|
||||
new ProgramScheduleItemDuration
|
||||
{
|
||||
Collection = collection,
|
||||
CollectionId = collection.Id,
|
||||
PlayoutDuration = TimeSpan.FromHours(1),
|
||||
TailMode = TailMode.None, // immediately continue
|
||||
PlaybackOrder = PlaybackOrder.Shuffle
|
||||
}
|
||||
};
|
||||
|
||||
int playoutId = await AddTestData(dbContext, scheduleItems);
|
||||
|
||||
DateTimeOffset start = new DateTimeOffset(2022, 7, 26, 8, 0, 5, TimeSpan.FromHours(-5));
|
||||
DateTimeOffset finish = start.AddDays(2);
|
||||
|
||||
var builder = new PlayoutBuilder(
|
||||
new ConfigElementRepository(factory),
|
||||
new MediaCollectionRepository(new Mock<ISearchIndex>().Object, factory),
|
||||
new TelevisionRepository(factory),
|
||||
new ArtistRepository(factory),
|
||||
provider.GetRequiredService<ILogger<PlayoutBuilder>>());
|
||||
|
||||
for (var i = 0; i <= (24 * 4); i++)
|
||||
{
|
||||
await using TvContext context = await factory.CreateDbContextAsync();
|
||||
|
||||
Option<Playout> maybePlayout = await GetPlayout(context, playoutId);
|
||||
Playout playout = maybePlayout.ValueUnsafe();
|
||||
|
||||
await builder.Build(playout, PlayoutBuildMode.Continue, start.AddHours(i), finish.AddHours(i));
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<int> AddTestData(TvContext dbContext, List<ProgramScheduleItem> scheduleItems)
|
||||
{
|
||||
var ffmpegProfile = new FFmpegProfile
|
||||
{
|
||||
Name = "Test FFmpeg Profile"
|
||||
};
|
||||
|
||||
await dbContext.FFmpegProfiles.AddAsync(ffmpegProfile);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var channel = new Channel(Guid.Parse("00000000-0000-0000-0000-000000000001"))
|
||||
{
|
||||
Name = "Test Channel",
|
||||
FFmpegProfile = ffmpegProfile,
|
||||
FFmpegProfileId = ffmpegProfile.Id
|
||||
};
|
||||
|
||||
await dbContext.Channels.AddAsync(channel);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var schedule = new ProgramSchedule
|
||||
{
|
||||
Name = "Test Schedule",
|
||||
Items = scheduleItems
|
||||
};
|
||||
|
||||
await dbContext.ProgramSchedules.AddAsync(schedule);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var playout = new Playout
|
||||
{
|
||||
Channel = channel,
|
||||
ChannelId = channel.Id,
|
||||
ProgramSchedule = schedule,
|
||||
ProgramScheduleId = schedule.Id
|
||||
};
|
||||
|
||||
await dbContext.Playouts.AddAsync(playout);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
return playout.Id;
|
||||
}
|
||||
|
||||
private static async Task<Option<Playout>> GetPlayout(TvContext dbContext, int playoutId)
|
||||
{
|
||||
return await dbContext.Playouts
|
||||
.Include(p => p.Channel)
|
||||
.Include(p => p.Items)
|
||||
.Include(p => p.ProgramScheduleAnchors)
|
||||
.ThenInclude(a => a.MediaItem)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.Collection)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.MediaItem)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.PreRollFiller)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.MidRollFiller)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.PostRollFiller)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.TailFiller)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.FallbackFiller)
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == playoutId);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using ErsatzTV.Core.Interfaces.Emby;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -14,12 +15,12 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
|
||||
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||
private readonly ILogger<EmbyCollectionScanner> _logger;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
public EmbyCollectionScanner(
|
||||
IEmbyCollectionRepository embyCollectionRepository,
|
||||
IEmbyApiClient embyApiClient,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
ISearchIndex searchIndex,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
ILogger<EmbyCollectionScanner> logger)
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Extensions;
|
||||
using ErsatzTV.Core.Interfaces.Emby;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using MediatR;
|
||||
@@ -25,7 +26,7 @@ public class EmbyMovieLibraryScanner :
|
||||
IMediator mediator,
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IEmbyMovieRepository embyMovieRepository,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IEmbyPathReplacementService pathReplacementService,
|
||||
ILocalFileSystem localFileSystem,
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Extensions;
|
||||
using ErsatzTV.Core.Interfaces.Emby;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using MediatR;
|
||||
@@ -24,7 +25,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IEmbyTelevisionRepository televisionRepository,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IEmbyPathReplacementService pathReplacementService,
|
||||
ILocalFileSystem localFileSystem,
|
||||
|
||||
@@ -148,14 +148,11 @@ public class FFmpegPlaybackSettingsCalculator
|
||||
result.AudioBitrate = ffmpegProfile.AudioBitrate;
|
||||
result.AudioBufferSize = ffmpegProfile.AudioBufferSize;
|
||||
|
||||
audioStream.IfSome(
|
||||
stream =>
|
||||
{
|
||||
if (stream.Channels != ffmpegProfile.AudioChannels)
|
||||
{
|
||||
result.AudioChannels = ffmpegProfile.AudioChannels;
|
||||
}
|
||||
});
|
||||
foreach (MediaStream _ in audioStream)
|
||||
{
|
||||
// this can be optimized out later, depending on the audio codec
|
||||
result.AudioChannels = ffmpegProfile.AudioChannels;
|
||||
}
|
||||
|
||||
result.AudioSampleRate = ffmpegProfile.AudioSampleRate;
|
||||
result.AudioDuration = outPoint - inPoint;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
|
||||
public interface ICachingSearchRepository : ISearchRepository
|
||||
{
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Search;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Search;
|
||||
@@ -9,15 +10,15 @@ public interface ISearchIndex : IDisposable
|
||||
{
|
||||
public int Version { get; }
|
||||
Task<bool> Initialize(ILocalFileSystem localFileSystem, IConfigElementRepository configElementRepository);
|
||||
Task<Unit> Rebuild(ISearchRepository searchRepository, IFallbackMetadataProvider fallbackMetadataProvider);
|
||||
Task<Unit> Rebuild(ICachingSearchRepository searchRepository, IFallbackMetadataProvider fallbackMetadataProvider);
|
||||
|
||||
Task<Unit> RebuildItems(
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
List<int> itemIds);
|
||||
|
||||
Task<Unit> UpdateItems(
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
List<MediaItem> items);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using ErsatzTV.Core.Interfaces.Jellyfin;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -14,12 +15,12 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
|
||||
private readonly IJellyfinCollectionRepository _jellyfinCollectionRepository;
|
||||
private readonly ILogger<JellyfinCollectionScanner> _logger;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
public JellyfinCollectionScanner(
|
||||
IJellyfinCollectionRepository jellyfinCollectionRepository,
|
||||
IJellyfinApiClient jellyfinApiClient,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
ISearchIndex searchIndex,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
ILogger<JellyfinCollectionScanner> logger)
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Extensions;
|
||||
using ErsatzTV.Core.Interfaces.Jellyfin;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using MediatR;
|
||||
@@ -24,7 +25,7 @@ public class JellyfinMovieLibraryScanner :
|
||||
ISearchIndex searchIndex,
|
||||
IMediator mediator,
|
||||
IJellyfinMovieRepository jellyfinMovieRepository,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IJellyfinPathReplacementService pathReplacementService,
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Extensions;
|
||||
using ErsatzTV.Core.Interfaces.Jellyfin;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using MediatR;
|
||||
@@ -25,7 +26,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IJellyfinTelevisionRepository televisionRepository,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IJellyfinPathReplacementService pathReplacementService,
|
||||
ILocalFileSystem localFileSystem,
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Domain.MediaServer;
|
||||
using ErsatzTV.Core.Errors;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -22,7 +23,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
protected MediaServerMovieLibraryScanner(
|
||||
ILocalStatisticsProvider localStatisticsProvider,
|
||||
@@ -30,7 +31,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
||||
ILocalFileSystem localFileSystem,
|
||||
IMediator mediator,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
ILogger logger)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Domain.MediaServer;
|
||||
using ErsatzTV.Core.Errors;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -25,13 +26,13 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
protected MediaServerTelevisionLibraryScanner(
|
||||
ILocalStatisticsProvider localStatisticsProvider,
|
||||
ILocalSubtitlesProvider localSubtitlesProvider,
|
||||
ILocalFileSystem localFileSystem,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
ISearchIndex searchIndex,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IMediator mediator,
|
||||
|
||||
@@ -5,6 +5,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -24,7 +25,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
public MovieFolderScanner(
|
||||
ILocalFileSystem localFileSystem,
|
||||
@@ -35,7 +36,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
||||
IMetadataRepository metadataRepository,
|
||||
IImageCache imageCache,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
ILibraryRepository libraryRepository,
|
||||
IMediaItemRepository mediaItemRepository,
|
||||
|
||||
@@ -5,6 +5,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -24,7 +25,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IMusicVideoRepository _musicVideoRepository;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
public MusicVideoFolderScanner(
|
||||
ILocalFileSystem localFileSystem,
|
||||
@@ -34,7 +35,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
||||
IMetadataRepository metadataRepository,
|
||||
IImageCache imageCache,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IArtistRepository artistRepository,
|
||||
IMusicVideoRepository musicVideoRepository,
|
||||
|
||||
@@ -5,6 +5,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -23,7 +24,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IOtherVideoRepository _otherVideoRepository;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
|
||||
public OtherVideoFolderScanner(
|
||||
ILocalFileSystem localFileSystem,
|
||||
@@ -34,7 +35,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
|
||||
IImageCache imageCache,
|
||||
IMediator mediator,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IOtherVideoRepository otherVideoRepository,
|
||||
ILibraryRepository libraryRepository,
|
||||
|
||||
@@ -6,6 +6,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -22,7 +23,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
|
||||
private readonly ILogger<SongFolderScanner> _logger;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
private readonly ISongRepository _songRepository;
|
||||
|
||||
public SongFolderScanner(
|
||||
@@ -33,7 +34,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
|
||||
IImageCache imageCache,
|
||||
IMediator mediator,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
ISongRepository songRepository,
|
||||
ILibraryRepository libraryRepository,
|
||||
|
||||
@@ -5,6 +5,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -23,7 +24,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly ICachingSearchRepository _searchRepository;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public TelevisionFolderScanner(
|
||||
@@ -36,7 +37,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
||||
IMetadataRepository metadataRepository,
|
||||
IImageCache imageCache,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
ILibraryRepository libraryRepository,
|
||||
IMediaItemRepository mediaItemRepository,
|
||||
IMediator mediator,
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Extensions;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using MediatR;
|
||||
@@ -27,7 +28,7 @@ public class PlexMovieLibraryScanner :
|
||||
IMovieRepository movieRepository,
|
||||
IMetadataRepository metadataRepository,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IMediator mediator,
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Extensions;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using MediatR;
|
||||
@@ -27,7 +28,7 @@ public class PlexTelevisionLibraryScanner :
|
||||
ITelevisionRepository televisionRepository,
|
||||
IMetadataRepository metadataRepository,
|
||||
ISearchIndex searchIndex,
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
IMediator mediator,
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
|
||||
@@ -626,9 +626,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
&& a.MediaItemId == collectionKey.MediaItemId
|
||||
&& a.AnchorDate is null);
|
||||
|
||||
var maybeEnumeratorState = collectionEnumerators.GroupBy(e => e.Key, e => e.Value.State).ToDictionary(
|
||||
mcs => mcs.Key,
|
||||
mcs => mcs.Head());
|
||||
var maybeEnumeratorState = collectionEnumerators.ToDictionary(e => e.Key, e => e.Value.State);
|
||||
|
||||
PlayoutProgramScheduleAnchor scheduleAnchor = maybeExisting.Match(
|
||||
existing =>
|
||||
@@ -658,10 +656,10 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
result.Add(scheduleAnchor);
|
||||
}
|
||||
|
||||
foreach (PlayoutProgramScheduleAnchor continueAnchor in playout.ProgramScheduleAnchors.Where(
|
||||
foreach (PlayoutProgramScheduleAnchor checkpointAnchor in playout.ProgramScheduleAnchors.Where(
|
||||
a => a.AnchorDate is not null))
|
||||
{
|
||||
result.Add(continueAnchor);
|
||||
result.Add(checkpointAnchor);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -675,7 +673,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
bool randomStartPoint)
|
||||
{
|
||||
Option<PlayoutProgramScheduleAnchor> maybeAnchor = playout.ProgramScheduleAnchors
|
||||
.OrderByDescending(a => a.AnchorDate is null)
|
||||
.OrderByDescending(a => a.AnchorDate ?? DateTime.MaxValue)
|
||||
.FirstOrDefault(
|
||||
a => a.ProgramScheduleId == playout.ProgramScheduleId
|
||||
&& a.CollectionType == collectionKey.CollectionType
|
||||
|
||||
@@ -86,7 +86,77 @@ public class PipelineGeneratorTests
|
||||
|
||||
string command = PrintCommand(videoInputFile, audioInputFile, None, None, result);
|
||||
command.Should().Be(
|
||||
"-threads 1 -nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -re -i /tmp/whatever.mkv -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:a aac -ac 2 -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -f mpegts -mpegts_flags +initial_discontinuity pipe:1");
|
||||
"-threads 1 -nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -re -i /tmp/whatever.mkv -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:a aac -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -f mpegts -mpegts_flags +initial_discontinuity pipe:1");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Aac_6_Channel_Should_Specify_Audio_Channels()
|
||||
{
|
||||
var videoInputFile = new VideoInputFile(
|
||||
"/tmp/whatever.mkv",
|
||||
new List<VideoStream>
|
||||
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "24", false) });
|
||||
|
||||
var audioInputFile = new AudioInputFile(
|
||||
"/tmp/whatever.mkv",
|
||||
new List<AudioStream> { new(1, AudioFormat.Aac, 6) },
|
||||
new AudioState(
|
||||
AudioFormat.Aac,
|
||||
6,
|
||||
320,
|
||||
640,
|
||||
48,
|
||||
Option<TimeSpan>.None,
|
||||
false));
|
||||
|
||||
var desiredState = new FrameState(
|
||||
true,
|
||||
false,
|
||||
VideoFormat.Hevc,
|
||||
new PixelFormatYuv420P(),
|
||||
new FrameSize(1920, 1080),
|
||||
new FrameSize(1920, 1080),
|
||||
Option<int>.None,
|
||||
2000,
|
||||
4000,
|
||||
90_000,
|
||||
false);
|
||||
|
||||
var ffmpegState = new FFmpegState(
|
||||
false,
|
||||
HardwareAccelerationMode.None,
|
||||
HardwareAccelerationMode.None,
|
||||
Option<string>.None,
|
||||
Option<string>.None,
|
||||
TimeSpan.FromSeconds(1),
|
||||
Option<TimeSpan>.None,
|
||||
false,
|
||||
Option<string>.None,
|
||||
Option<string>.None,
|
||||
Option<string>.None,
|
||||
OutputFormatKind.MpegTs,
|
||||
Option<string>.None,
|
||||
Option<string>.None,
|
||||
0,
|
||||
Option<int>.None);
|
||||
|
||||
var builder = new PipelineBuilder(
|
||||
new DefaultHardwareCapabilities(),
|
||||
videoInputFile,
|
||||
audioInputFile,
|
||||
None,
|
||||
None,
|
||||
"",
|
||||
"",
|
||||
_logger);
|
||||
FFmpegPipeline result = builder.Build(ffmpegState, desiredState);
|
||||
|
||||
result.PipelineSteps.Should().HaveCountGreaterThan(0);
|
||||
result.PipelineSteps.Should().Contain(ps => ps is EncoderLibx265);
|
||||
|
||||
string command = PrintCommand(videoInputFile, audioInputFile, None, None, result);
|
||||
command.Should().Be(
|
||||
"-threads 1 -nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -re -i /tmp/whatever.mkv -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:a aac -ac 6 -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -f mpegts -mpegts_flags +initial_discontinuity pipe:1");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace ErsatzTV.FFmpeg.Capabilities;
|
||||
|
||||
public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
||||
{
|
||||
private const string CacheKey = "ffmpeg.hardware.nvidia.architecture";
|
||||
private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture";
|
||||
private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model";
|
||||
private readonly ILogger<HardwareCapabilitiesFactory> _logger;
|
||||
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
@@ -31,9 +32,10 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
||||
|
||||
private async Task<IHardwareCapabilities> GetNvidiaCapabilities(string ffmpegPath)
|
||||
{
|
||||
if (_memoryCache.TryGetValue(CacheKey, out int cachedArchitecture))
|
||||
if (_memoryCache.TryGetValue(ArchitectureCacheKey, out int cachedArchitecture)
|
||||
&& _memoryCache.TryGetValue(ModelCacheKey, out string cachedModel))
|
||||
{
|
||||
return new NvidiaHardwareCapabilities(cachedArchitecture);
|
||||
return new NvidiaHardwareCapabilities(cachedArchitecture, cachedModel);
|
||||
}
|
||||
|
||||
string[] arguments =
|
||||
@@ -57,13 +59,20 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
||||
Option<string> maybeLine = Optional(output.Split("\n").FirstOrDefault(x => x.Contains("GPU")));
|
||||
foreach (string line in maybeLine)
|
||||
{
|
||||
const string PATTERN = @"SM\s+(\d\.\d)";
|
||||
Match match = Regex.Match(line, PATTERN);
|
||||
const string ARCHITECTURE_PATTERN = @"SM\s+(\d\.\d)";
|
||||
Match match = Regex.Match(line, ARCHITECTURE_PATTERN);
|
||||
if (match.Success && int.TryParse(match.Groups[1].Value.Replace(".", string.Empty), out int architecture))
|
||||
{
|
||||
_logger.LogInformation("Detected NVIDIA GPU architecture SM {Architecture}", architecture);
|
||||
_memoryCache.Set(CacheKey, architecture);
|
||||
return new NvidiaHardwareCapabilities(architecture);
|
||||
const string MODEL_PATTERN = @"(GTX\s+[0-9a-zA-Z]+[\sTtIi]+)";
|
||||
Match modelMatch = Regex.Match(line, MODEL_PATTERN);
|
||||
string model = modelMatch.Success ? modelMatch.Groups[1].Value.Trim() : "unknown";
|
||||
_logger.LogInformation(
|
||||
"Detected NVIDIA GPU model {Model} architecture SM {Architecture}",
|
||||
model,
|
||||
architecture);
|
||||
_memoryCache.Set(ArchitectureCacheKey, architecture);
|
||||
_memoryCache.Set(ModelCacheKey, model);
|
||||
return new NvidiaHardwareCapabilities(architecture, model);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,14 @@ namespace ErsatzTV.FFmpeg.Capabilities;
|
||||
public class NvidiaHardwareCapabilities : IHardwareCapabilities
|
||||
{
|
||||
private readonly int _architecture;
|
||||
private readonly List<string> _maxwellGm206 = new() { "GTX 750", "GTX 950", "GTX 960", "GTX 965M" };
|
||||
private readonly string _model;
|
||||
|
||||
public NvidiaHardwareCapabilities(int architecture) => _architecture = architecture;
|
||||
public NvidiaHardwareCapabilities(int architecture, string model)
|
||||
{
|
||||
_architecture = architecture;
|
||||
_model = model;
|
||||
}
|
||||
|
||||
public bool CanDecode(string videoFormat, Option<IPixelFormat> maybePixelFormat)
|
||||
{
|
||||
@@ -14,14 +20,14 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
|
||||
|
||||
return videoFormat switch
|
||||
{
|
||||
// second gen maxwell is required to decode hevc
|
||||
VideoFormat.Hevc => _architecture >= 52,
|
||||
// some second gen maxwell can decode hevc, otherwise pascal is required
|
||||
VideoFormat.Hevc => _architecture == 52 && _maxwellGm206.Contains(_model) || _architecture >= 60,
|
||||
|
||||
// pascal is required to decode vp9 10-bit
|
||||
VideoFormat.Vp9 when bitDepth == 10 => _architecture >= 60,
|
||||
|
||||
// second gen maxwell is required to decode vp9
|
||||
VideoFormat.Vp9 => _architecture >= 52,
|
||||
// some second gen maxwell can decode vp9, otherwise pascal is required
|
||||
VideoFormat.Vp9 => _architecture == 52 && _maxwellGm206.Contains(_model) || _architecture >= 60,
|
||||
|
||||
_ => true
|
||||
};
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
namespace ErsatzTV.FFmpeg.Option;
|
||||
using ErsatzTV.FFmpeg.Format;
|
||||
|
||||
namespace ErsatzTV.FFmpeg.Option;
|
||||
|
||||
public class AudioChannelsOutputOption : OutputOption
|
||||
{
|
||||
private readonly int _channels;
|
||||
private readonly Option<string> _audioFormat;
|
||||
private readonly int _desiredChannels;
|
||||
private readonly int _sourceChannels;
|
||||
|
||||
public AudioChannelsOutputOption(int channels) => _channels = channels;
|
||||
public AudioChannelsOutputOption(Option<string> audioFormat, int sourceChannels, int desiredChannels)
|
||||
{
|
||||
_audioFormat = audioFormat;
|
||||
_sourceChannels = sourceChannels;
|
||||
_desiredChannels = desiredChannels;
|
||||
}
|
||||
|
||||
public override IList<string> OutputOptions => new List<string> { "-ac", _channels.ToString() };
|
||||
public override IList<string> OutputOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_sourceChannels != _desiredChannels || _audioFormat == Some(AudioFormat.Aac) && _desiredChannels > 2)
|
||||
{
|
||||
return new List<string>
|
||||
{
|
||||
"-ac", _desiredChannels.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,9 +538,16 @@ public class PipelineBuilder
|
||||
_pipelineSteps.Add(step);
|
||||
}
|
||||
|
||||
foreach (int desiredAudioChannels in audioInputFile.DesiredState.AudioChannels)
|
||||
foreach (AudioStream audioStream in audioInputFile.AudioStreams.HeadOrNone())
|
||||
{
|
||||
_pipelineSteps.Add(new AudioChannelsOutputOption(desiredAudioChannels));
|
||||
foreach (int desiredAudioChannels in audioInputFile.DesiredState.AudioChannels)
|
||||
{
|
||||
_pipelineSteps.Add(
|
||||
new AudioChannelsOutputOption(
|
||||
audioInputFile.DesiredState.AudioFormat,
|
||||
audioStream.Channels,
|
||||
desiredAudioChannels));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int desiredBitrate in audioInputFile.DesiredState.AudioBitrate)
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Infrastructure.Data.Repositories.Caching;
|
||||
using FluentAssertions;
|
||||
using LanguageExt;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Tests.Data.Repositories.Caching;
|
||||
|
||||
[TestFixture]
|
||||
public class CachingSearchRepositoryTests
|
||||
{
|
||||
[Test]
|
||||
public async Task GetAllLanguageCodes_Should_Cache_Languages_Separately()
|
||||
{
|
||||
var englishMediaCodes = new List<string> { "eng" };
|
||||
var frenchMediaCodes = new List<string> { "fre" };
|
||||
var englishResult = new List<string> { "english_result" };
|
||||
var frenchResult = new List<string> { "french_result" };
|
||||
|
||||
var searchRepo = new Mock<ISearchRepository>();
|
||||
searchRepo.Setup(x => x.GetAllLanguageCodes(englishMediaCodes)).Returns(englishResult.AsTask());
|
||||
searchRepo.Setup(x => x.GetAllLanguageCodes(frenchMediaCodes)).Returns(frenchResult.AsTask());
|
||||
|
||||
var repo = new CachingSearchRepository(searchRepo.Object);
|
||||
|
||||
List<string> result1 = await repo.GetAllLanguageCodes(englishMediaCodes);
|
||||
result1.Should().BeEquivalentTo(englishResult);
|
||||
|
||||
List<string> result2 = await repo.GetAllLanguageCodes(frenchMediaCodes);
|
||||
result2.Should().BeEquivalentTo(frenchResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ErsatzTV.Infrastructure\ErsatzTV.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Repositories.Caching;
|
||||
|
||||
public class CachingSearchRepository : ICachingSearchRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<List<string>, List<string>> _cache = new();
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
private readonly SemaphoreSlim _slim = new(1, 1);
|
||||
|
||||
public CachingSearchRepository(ISearchRepository searchRepository) => _searchRepository = searchRepository;
|
||||
|
||||
public Task<Option<MediaItem>> GetItemToIndex(int id) => _searchRepository.GetItemToIndex(id);
|
||||
|
||||
public Task<List<string>> GetLanguagesForShow(Show show) => _searchRepository.GetLanguagesForShow(show);
|
||||
|
||||
public Task<List<string>> GetLanguagesForSeason(Season season) => _searchRepository.GetLanguagesForSeason(season);
|
||||
|
||||
public Task<List<string>> GetLanguagesForArtist(Artist artist) => _searchRepository.GetLanguagesForArtist(artist);
|
||||
|
||||
public async Task<List<string>> GetAllLanguageCodes(List<string> mediaCodes)
|
||||
{
|
||||
if (!_cache.ContainsKey(mediaCodes))
|
||||
{
|
||||
await _slim.WaitAsync();
|
||||
try
|
||||
{
|
||||
_cache.TryAdd(mediaCodes, await _searchRepository.GetAllLanguageCodes(mediaCodes));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_slim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
return _cache[mediaCodes];
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<MediaItem> GetAllMediaItems() => _searchRepository.GetAllMediaItems();
|
||||
}
|
||||
@@ -9,8 +9,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories;
|
||||
public class SearchRepository : ISearchRepository
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly SemaphoreSlim _slim = new(1, 1);
|
||||
private List<string> _allLanguageCodes;
|
||||
|
||||
public SearchRepository(IDbContextFactory<TvContext> dbContextFactory) => _dbContextFactory = dbContextFactory;
|
||||
|
||||
@@ -164,26 +162,10 @@ public class SearchRepository : ISearchRepository
|
||||
new { ArtistId = artist.Id }).Map(result => result.ToList());
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetAllLanguageCodes(List<string> mediaCodes)
|
||||
public virtual async Task<List<string>> GetAllLanguageCodes(List<string> mediaCodes)
|
||||
{
|
||||
if (_allLanguageCodes == null)
|
||||
{
|
||||
await _slim.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (_allLanguageCodes == null)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
_allLanguageCodes = await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCodes);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_slim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
return _allLanguageCodes;
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCodes);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<MediaItem> GetAllMediaItems()
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
|
||||
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
|
||||
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.2.32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
+4294
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations;
|
||||
|
||||
public partial class Add_Resolution640480 : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("INSERT INTO Resolution (Id, Height, Width, Name) VALUES (0, 480, 640, '640x480')");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.5");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Search;
|
||||
using LanguageExt.UnsafeValueAccess;
|
||||
@@ -88,7 +89,7 @@ public sealed class SearchIndex : ISearchIndex
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
public int Version => 28;
|
||||
public int Version => 30;
|
||||
|
||||
public async Task<bool> Initialize(
|
||||
ILocalFileSystem localFileSystem,
|
||||
@@ -118,7 +119,7 @@ public sealed class SearchIndex : ISearchIndex
|
||||
}
|
||||
|
||||
public async Task<Unit> UpdateItems(
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
List<MediaItem> items)
|
||||
{
|
||||
@@ -219,7 +220,7 @@ public sealed class SearchIndex : ISearchIndex
|
||||
}
|
||||
|
||||
public async Task<Unit> Rebuild(
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider)
|
||||
{
|
||||
_writer.DeleteAll();
|
||||
@@ -235,7 +236,7 @@ public sealed class SearchIndex : ISearchIndex
|
||||
}
|
||||
|
||||
public async Task<Unit> RebuildItems(
|
||||
ISearchRepository searchRepository,
|
||||
ICachingSearchRepository searchRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
List<int> itemIds)
|
||||
{
|
||||
@@ -1141,9 +1142,9 @@ public sealed class SearchIndex : ISearchIndex
|
||||
metadata switch
|
||||
{
|
||||
EpisodeMetadata em =>
|
||||
$"{em.Title}_{em.Year}_{em.Episode.Season.SeasonNumber}_{em.EpisodeNumber}_{em.Episode.State}"
|
||||
$"{em.Title}_{em.Episode.Season.Show.ShowMetadata.Head().Title}_{em.Year}_{em.Episode.Season.SeasonNumber}_{em.EpisodeNumber}_{em.Episode.State}"
|
||||
.ToLowerInvariant(),
|
||||
OtherVideoMetadata ovm => $"{ovm.Title}_{ovm.Year}_{ovm.OtherVideo.State}".ToLowerInvariant(),
|
||||
OtherVideoMetadata ovm => $"{ovm.OriginalTitle}_{ovm.Year}_{ovm.OtherVideo.State}".ToLowerInvariant(),
|
||||
SongMetadata sm => $"{sm.Title}_{sm.Year}_{sm.Song.State}".ToLowerInvariant(),
|
||||
MovieMetadata mm => $"{mm.Title}_{mm.Year}_{mm.Movie.State}".ToLowerInvariant(),
|
||||
ArtistMetadata am => $"{am.Title}_{am.Year}_{am.Artist.State}".ToLowerInvariant(),
|
||||
|
||||
@@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErsatzTV.FFmpeg", "ErsatzTV
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErsatzTV.FFmpeg.Tests", "ErsatzTV.FFmpeg.Tests\ErsatzTV.FFmpeg.Tests.csproj", "{1C892530-CF92-4F43-8C64-BCEEF958D726}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErsatzTV.Infrastructure.Tests", "ErsatzTV.Infrastructure.Tests\ErsatzTV.Infrastructure.Tests.csproj", "{591FB3F4-4DD8-441B-B7C8-F2A42BF69992}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -48,6 +50,10 @@ Global
|
||||
{1C892530-CF92-4F43-8C64-BCEEF958D726}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1C892530-CF92-4F43-8C64-BCEEF958D726}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1C892530-CF92-4F43-8C64-BCEEF958D726}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{591FB3F4-4DD8-441B-B7C8-F2A42BF69992}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{591FB3F4-4DD8-441B-B7C8-F2A42BF69992}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{591FB3F4-4DD8-441B-B7C8-F2A42BF69992}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{591FB3F4-4DD8-441B-B7C8-F2A42BF69992}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
EndGlobalSection
|
||||
|
||||
@@ -57,14 +57,14 @@
|
||||
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
|
||||
<PackageReference Include="FluentValidation" Version="11.1.0" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.1.2" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="7.1.512" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="7.1.542" />
|
||||
<PackageReference Include="LanguageExt.Core" Version="4.2.9" />
|
||||
<PackageReference Include="Markdig" Version="0.30.2" />
|
||||
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
|
||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -72,12 +72,12 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MudBlazor" Version="6.0.11" />
|
||||
<PackageReference Include="MudBlazor" Version="6.0.12" />
|
||||
<PackageReference Include="NaturalSort.Extension" Version="3.2.0" />
|
||||
<PackageReference Include="PPioli.FluentValidation.Blazor" Version="5.0.0" />
|
||||
<PackageReference Include="PPioli.FluentValidation.Blazor" Version="11.1.0" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="6.3.2" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
|
||||
<PackageReference Include="Serilog.Sinks.SQLite" Version="6.0.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||
|
||||
@@ -22,6 +22,7 @@ using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Metadata.Nfo;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories.Caching;
|
||||
using ErsatzTV.Core.Interfaces.Runtime;
|
||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
@@ -36,6 +37,7 @@ using ErsatzTV.FFmpeg.Capabilities;
|
||||
using ErsatzTV.Formatters;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Data.Repositories;
|
||||
using ErsatzTV.Infrastructure.Data.Repositories.Caching;
|
||||
using ErsatzTV.Infrastructure.Emby;
|
||||
using ErsatzTV.Infrastructure.GitHub;
|
||||
using ErsatzTV.Infrastructure.Health;
|
||||
@@ -352,6 +354,7 @@ public class Startup
|
||||
services.AddScoped<IConfigElementRepository, ConfigElementRepository>();
|
||||
services.AddScoped<ITelevisionRepository, TelevisionRepository>();
|
||||
services.AddScoped<ISearchRepository, SearchRepository>();
|
||||
services.AddScoped<ICachingSearchRepository, CachingSearchRepository>();
|
||||
services.AddScoped<IMovieRepository, MovieRepository>();
|
||||
services.AddScoped<IArtistRepository, ArtistRepository>();
|
||||
services.AddScoped<IMusicVideoRepository, MusicVideoRepository>();
|
||||
|
||||
Reference in New Issue
Block a user