dependencies and code cleanup (#2117)
* fix validation in new form layout * pin mediatr to last oss version * update dependencies * cleanup code in core * cleanup code in ffmpeg * cleanup code in infra * cleanup code in scanner * cleanup code in application * cleanup main code * cleanup test code * solution-wide code cleanup
This commit is contained in:
@@ -3,10 +3,11 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"jetbrains.resharper.globaltools": {
|
"jetbrains.resharper.globaltools": {
|
||||||
"version": "2024.1.1",
|
"version": "2025.1.4",
|
||||||
"commands": [
|
"commands": [
|
||||||
"jb"
|
"jb"
|
||||||
]
|
],
|
||||||
|
"rollForward": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,9 +29,10 @@ internal static class Mapper
|
|||||||
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
||||||
|
|
||||||
return languages
|
return languages
|
||||||
.Map(
|
.Map(lang => allCultures.Filter(ci => string.Equals(
|
||||||
lang => allCultures.Filter(
|
ci.ThreeLetterISOLanguageName,
|
||||||
ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase)))
|
lang,
|
||||||
|
StringComparison.OrdinalIgnoreCase)))
|
||||||
.Flatten()
|
.Flatten()
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.Artworks;
|
namespace ErsatzTV.Application.Artworks;
|
||||||
|
|
||||||
public class GetArtworkHandler(IDbContextFactory<TvContext> dbContextFactory) : IRequestHandler<GetArtwork, Either<BaseError, Artwork>>
|
public class GetArtworkHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetArtwork, Either<BaseError, Artwork>>
|
||||||
{
|
{
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory = dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory = dbContextFactory;
|
||||||
|
|
||||||
@@ -14,7 +15,8 @@ public class GetArtworkHandler(IDbContextFactory<TvContext> dbContextFactory) :
|
|||||||
GetArtwork request,
|
GetArtwork request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
Option<Artwork> artwork = await dbContext.Artwork
|
Option<Artwork> artwork = await dbContext.Artwork
|
||||||
@@ -23,7 +25,6 @@ public class GetArtworkHandler(IDbContextFactory<TvContext> dbContextFactory) :
|
|||||||
.MapT(Project);
|
.MapT(Project);
|
||||||
|
|
||||||
return artwork.ToEither(BaseError.New("Artwork not found"));
|
return artwork.ToEither(BaseError.New("Artwork not found"));
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -31,12 +32,11 @@ public class GetArtworkHandler(IDbContextFactory<TvContext> dbContextFactory) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Artwork Project(Artwork artwork)
|
private static Artwork Project(Artwork artwork) =>
|
||||||
|
new()
|
||||||
{
|
{
|
||||||
return new Artwork {
|
|
||||||
Id = artwork.Id,
|
Id = artwork.Id,
|
||||||
Path = artwork.Path,
|
Path = artwork.Path,
|
||||||
ArtworkKind = artwork.ArtworkKind
|
ArtworkKind = artwork.ArtworkKind
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using ErsatzTV.Application.Artworks;
|
using ErsatzTV.Application.Artworks;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
namespace ErsatzTV.Application.Channels;
|
namespace ErsatzTV.Application.Channels;
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ public class CreateChannelHandler(
|
|||||||
await FFmpegProfileMustExist(dbContext, request),
|
await FFmpegProfileMustExist(dbContext, request),
|
||||||
await WatermarkMustExist(dbContext, request),
|
await WatermarkMustExist(dbContext, request),
|
||||||
await FillerPresetMustExist(dbContext, request))
|
await FillerPresetMustExist(dbContext, request))
|
||||||
.Apply(
|
.Apply((
|
||||||
(
|
|
||||||
name,
|
name,
|
||||||
number,
|
number,
|
||||||
ffmpegProfileId,
|
ffmpegProfileId,
|
||||||
@@ -62,7 +61,9 @@ public class CreateChannelHandler(
|
|||||||
{
|
{
|
||||||
Path = logo,
|
Path = logo,
|
||||||
ArtworkKind = ArtworkKind.Logo,
|
ArtworkKind = ArtworkKind.Logo,
|
||||||
OriginalContentType = !string.IsNullOrEmpty(request.Logo.ContentType) ? request.Logo.ContentType : null,
|
OriginalContentType = !string.IsNullOrEmpty(request.Logo.ContentType)
|
||||||
|
? request.Logo.ContentType
|
||||||
|
: null,
|
||||||
DateAdded = DateTime.UtcNow,
|
DateAdded = DateTime.UtcNow,
|
||||||
DateUpdated = DateTime.UtcNow
|
DateUpdated = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
@@ -168,8 +169,7 @@ public class CreateChannelHandler(
|
|||||||
.Map(Optional)
|
.Map(Optional)
|
||||||
.Filter(c => c > 0)
|
.Filter(c => c > 0)
|
||||||
.MapT(_ => Optional(createChannel.FallbackFillerId))
|
.MapT(_ => Optional(createChannel.FallbackFillerId))
|
||||||
.Map(
|
.Map(o => o.ToValidation<BaseError>(
|
||||||
o => o.ToValidation<BaseError>(
|
|
||||||
$"Fallback filler {createChannel.FallbackFillerId} does not exist."));
|
$"Fallback filler {createChannel.FallbackFillerId} does not exist."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,7 +296,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
|||||||
int finishIndex = j;
|
int finishIndex = j;
|
||||||
while (finishIndex + 1 < sorted.Count && (sorted[finishIndex + 1].GuideGroup == startItem.GuideGroup
|
while (finishIndex + 1 < sorted.Count && (sorted[finishIndex + 1].GuideGroup == startItem.GuideGroup
|
||||||
|| sorted[finishIndex + 1].FillerKind is FillerKind.GuideMode
|
|| sorted[finishIndex + 1].FillerKind is FillerKind.GuideMode
|
||||||
or FillerKind.PostRoll or FillerKind.Tail or FillerKind.Fallback or FillerKind.DecoDefault))
|
or FillerKind.PostRoll or FillerKind.Tail
|
||||||
|
or FillerKind.Fallback or FillerKind.DecoDefault))
|
||||||
{
|
{
|
||||||
finishIndex++;
|
finishIndex++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ public class GetChannelLineupHandler : IRequestHandler<GetChannelLineup, List<Li
|
|||||||
|
|
||||||
public Task<List<LineupItem>> Handle(GetChannelLineup request, CancellationToken cancellationToken) =>
|
public Task<List<LineupItem>> Handle(GetChannelLineup request, CancellationToken cancellationToken) =>
|
||||||
_channelRepository.GetAll()
|
_channelRepository.GetAll()
|
||||||
.Map(channels => channels.Where(c => c.ActiveMode is ChannelActiveMode.Active).Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList());
|
.Map(channels => channels.Where(c => c.ActiveMode is ChannelActiveMode.Active)
|
||||||
|
.Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha
|
|||||||
public Task<ChannelPlaylist> Handle(GetChannelPlaylist request, CancellationToken cancellationToken) =>
|
public Task<ChannelPlaylist> Handle(GetChannelPlaylist request, CancellationToken cancellationToken) =>
|
||||||
_channelRepository.GetAll()
|
_channelRepository.GetAll()
|
||||||
.Map(channels => EnsureMode(channels, request.Mode))
|
.Map(channels => EnsureMode(channels, request.Mode))
|
||||||
.Map(
|
.Map(channels => new ChannelPlaylist(
|
||||||
channels => new ChannelPlaylist(
|
|
||||||
request.Scheme,
|
request.Scheme,
|
||||||
request.Host,
|
request.Host,
|
||||||
request.BaseUrl,
|
request.BaseUrl,
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ public class UpdateLibraryRefreshIntervalHandler :
|
|||||||
UpdateLibraryRefreshInterval request,
|
UpdateLibraryRefreshInterval request,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
Validate(request)
|
Validate(request)
|
||||||
.MapT(
|
.MapT(_ => _configElementRepository.Upsert(
|
||||||
_ => _configElementRepository.Upsert(
|
|
||||||
ConfigElementKey.LibraryRefreshInterval,
|
ConfigElementKey.LibraryRefreshInterval,
|
||||||
request.LibraryRefreshInterval))
|
request.LibraryRefreshInterval))
|
||||||
.Bind(v => v.ToEitherAsync());
|
.Bind(v => v.ToEitherAsync());
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ public class UpdateEmbyPathReplacementsHandler : IRequestHandler<UpdateEmbyPathR
|
|||||||
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist(
|
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist(
|
||||||
UpdateEmbyPathReplacements request) =>
|
UpdateEmbyPathReplacements request) =>
|
||||||
_mediaSourceRepository.GetEmby(request.EmbyMediaSourceId)
|
_mediaSourceRepository.GetEmby(request.EmbyMediaSourceId)
|
||||||
.Map(
|
.Map(v => v.ToValidation<BaseError>(
|
||||||
v => v.ToValidation<BaseError>(
|
|
||||||
$"Emby media source {request.EmbyMediaSourceId} does not exist."));
|
$"Emby media source {request.EmbyMediaSourceId} does not exist."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ public class GetEmbyConnectionParametersHandler : IRequestHandler<GetEmbyConnect
|
|||||||
|
|
||||||
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist() =>
|
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist() =>
|
||||||
_mediaSourceRepository.GetAllEmby().Map(list => list.HeadOrNone())
|
_mediaSourceRepository.GetAllEmby().Map(list => list.HeadOrNone())
|
||||||
.Map(
|
.Map(v => v.ToValidation<BaseError>(
|
||||||
v => v.ToValidation<BaseError>(
|
|
||||||
"Emby media source does not exist."));
|
"Emby media source does not exist."));
|
||||||
|
|
||||||
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
|
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<PackageReference Include="Bugsnag" Version="4.0.0" />
|
<PackageReference Include="Bugsnag" Version="4.0.0" />
|
||||||
<PackageReference Include="CliWrap" Version="3.9.0" />
|
<PackageReference Include="CliWrap" Version="3.9.0" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="MediatR" Version="12.5.0" />
|
<PackageReference Include="MediatR" Version="[12.5.0]" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.6" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.15">
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.15">
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ public class CreateFFmpegProfileHandler :
|
|||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateFFmpegProfile request) =>
|
CreateFFmpegProfile request) =>
|
||||||
(ValidateName(request), ValidateThreadCount(request), await ResolutionMustExist(dbContext, request))
|
(ValidateName(request), ValidateThreadCount(request), await ResolutionMustExist(dbContext, request))
|
||||||
.Apply(
|
.Apply((name, threadCount, resolutionId) => new FFmpegProfile
|
||||||
(name, threadCount, resolutionId) => new FFmpegProfile
|
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
ThreadCount = threadCount,
|
ThreadCount = threadCount,
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ public class UpdateHDHRTunerCountHandler : IRequestHandler<UpdateHDHRTunerCount,
|
|||||||
UpdateHDHRTunerCount request,
|
UpdateHDHRTunerCount request,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
Validate(request)
|
Validate(request)
|
||||||
.MapT(
|
.MapT(_ => _configElementRepository.Upsert(
|
||||||
_ => _configElementRepository.Upsert(
|
|
||||||
ConfigElementKey.HDHRTunerCount,
|
ConfigElementKey.HDHRTunerCount,
|
||||||
request.TunerCount.ToString(CultureInfo.InvariantCulture)))
|
request.TunerCount.ToString(CultureInfo.InvariantCulture)))
|
||||||
.Bind(v => v.ToEitherAsync());
|
.Bind(v => v.ToEitherAsync());
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ public class GetHDHRUUIDHandler : IRequestHandler<GetHDHRUUID, Guid>
|
|||||||
public async Task<Guid> Handle(GetHDHRUUID request, CancellationToken cancellationToken)
|
public async Task<Guid> Handle(GetHDHRUUID request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Option<Guid> maybeGuid = await _configElementRepository.GetValue<Guid>(ConfigElementKey.HDHRUUID);
|
Option<Guid> maybeGuid = await _configElementRepository.GetValue<Guid>(ConfigElementKey.HDHRUUID);
|
||||||
return await maybeGuid.IfNoneAsync(
|
return await maybeGuid.IfNoneAsync(async () =>
|
||||||
async () =>
|
|
||||||
{
|
{
|
||||||
Guid guid = Guid.NewGuid();
|
var guid = Guid.NewGuid();
|
||||||
await _configElementRepository.Upsert(ConfigElementKey.HDHRUUID, guid);
|
await _configElementRepository.Upsert(ConfigElementKey.HDHRUUID, guid);
|
||||||
return guid;
|
return guid;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ using ErsatzTV.Core.Domain;
|
|||||||
namespace ErsatzTV.Application.Images;
|
namespace ErsatzTV.Application.Images;
|
||||||
|
|
||||||
// ReSharper disable once SuggestBaseTypeForParameter
|
// ReSharper disable once SuggestBaseTypeForParameter
|
||||||
public record SaveArtworkToDisk(Stream Stream, ArtworkKind ArtworkKind, string ContentType) : IRequest<Either<BaseError, string>>;
|
public record SaveArtworkToDisk(Stream Stream, ArtworkKind ArtworkKind, string ContentType)
|
||||||
|
: IRequest<Either<BaseError, string>>;
|
||||||
|
|||||||
@@ -97,9 +97,8 @@ public class UpdateImageFolderDurationHandler(IDbContextFactory<TvContext> dbCon
|
|||||||
|
|
||||||
// update all images in this folder
|
// update all images in this folder
|
||||||
await dbContext.ImageMetadata
|
await dbContext.ImageMetadata
|
||||||
.Filter(
|
.Filter(im =>
|
||||||
im => im.Image.MediaVersions.Any(
|
im.Image.MediaVersions.Any(mv => mv.MediaFiles.Any(mf => mf.LibraryFolderId == currentFolder.Id)))
|
||||||
mv => mv.MediaFiles.Any(mf => mf.LibraryFolderId == currentFolder.Id)))
|
|
||||||
.ExecuteUpdateAsync(
|
.ExecuteUpdateAsync(
|
||||||
setters => setters.SetProperty(im => im.DurationSeconds, effectiveDuration),
|
setters => setters.SetProperty(im => im.DurationSeconds, effectiveDuration),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ using ErsatzTV.Core.Domain;
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.Images;
|
namespace ErsatzTV.Application.Images;
|
||||||
|
|
||||||
public record GetCachedImagePath(string FileName, ArtworkKind ArtworkKind, string ContentType, int? MaxHeight = null) : IRequest<
|
public record GetCachedImagePath(string FileName, ArtworkKind ArtworkKind, string ContentType, int? MaxHeight = null)
|
||||||
|
: IRequest<
|
||||||
Either<BaseError, CachedImagePathViewModel>>;
|
Either<BaseError, CachedImagePathViewModel>>;
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ public class UpdateJellyfinPathReplacementsHandler : IRequestHandler<UpdateJelly
|
|||||||
private Task<Validation<BaseError, JellyfinMediaSource>> JellyfinMediaSourceMustExist(
|
private Task<Validation<BaseError, JellyfinMediaSource>> JellyfinMediaSourceMustExist(
|
||||||
UpdateJellyfinPathReplacements request) =>
|
UpdateJellyfinPathReplacements request) =>
|
||||||
_mediaSourceRepository.GetJellyfin(request.JellyfinMediaSourceId)
|
_mediaSourceRepository.GetJellyfin(request.JellyfinMediaSourceId)
|
||||||
.Map(
|
.Map(v => v.ToValidation<BaseError>(
|
||||||
v => v.ToValidation<BaseError>(
|
|
||||||
$"Jellyfin media source {request.JellyfinMediaSourceId} does not exist."));
|
$"Jellyfin media source {request.JellyfinMediaSourceId} does not exist."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ public class GetJellyfinConnectionParametersHandler : IRequestHandler<GetJellyfi
|
|||||||
|
|
||||||
private Task<Validation<BaseError, JellyfinMediaSource>> JellyfinMediaSourceMustExist() =>
|
private Task<Validation<BaseError, JellyfinMediaSource>> JellyfinMediaSourceMustExist() =>
|
||||||
_mediaSourceRepository.GetAllJellyfin().Map(list => list.HeadOrNone())
|
_mediaSourceRepository.GetAllJellyfin().Map(list => list.HeadOrNone())
|
||||||
.Map(
|
.Map(v => v.ToValidation<BaseError>(
|
||||||
v => v.ToValidation<BaseError>(
|
|
||||||
"Jellyfin media source does not exist."));
|
"Jellyfin media source does not exist."));
|
||||||
|
|
||||||
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
|
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ public abstract class CallLibraryScannerHandler<TRequest>
|
|||||||
{
|
{
|
||||||
using var forcefulCts = new CancellationTokenSource();
|
using var forcefulCts = new CancellationTokenSource();
|
||||||
|
|
||||||
await using CancellationTokenRegistration link = cancellationToken.Register(
|
await using CancellationTokenRegistration link =
|
||||||
() => forcefulCts.CancelAfter(TimeSpan.FromSeconds(10))
|
cancellationToken.Register(() => forcefulCts.CancelAfter(TimeSpan.FromSeconds(10))
|
||||||
);
|
);
|
||||||
|
|
||||||
CommandResult process = await Cli.Wrap(scanner)
|
CommandResult process = await Cli.Wrap(scanner)
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ public class CreateLocalLibraryHandler : LocalLibraryHandlerBase,
|
|||||||
.OrderBy(lms => lms.Id)
|
.OrderBy(lms => lms.Id)
|
||||||
.FirstOrDefaultAsync()
|
.FirstOrDefaultAsync()
|
||||||
.Map(Optional)
|
.Map(Optional)
|
||||||
.MapT(
|
.MapT(lms => new LocalLibrary
|
||||||
lms => new LocalLibrary
|
|
||||||
{
|
{
|
||||||
Name = request.Name,
|
Name = request.Name,
|
||||||
Paths = request.Paths.Map(p => new LibraryPath { Path = p }).ToList(),
|
Paths = request.Paths.Map(p => new LibraryPath { Path = p }).ToList(),
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ public abstract class LocalLibraryHandlerBase
|
|||||||
// Images and OtherVideos do not conflict
|
// Images and OtherVideos do not conflict
|
||||||
if (isConflict)
|
if (isConflict)
|
||||||
{
|
{
|
||||||
bool imagesAndOtherVideos = (path1.MediaKind is LibraryMediaKind.Images &&
|
bool imagesAndOtherVideos = path1.MediaKind is LibraryMediaKind.Images &&
|
||||||
path2.MediaKind is LibraryMediaKind.OtherVideos)
|
path2.MediaKind is LibraryMediaKind.OtherVideos
|
||||||
|| (path2.MediaKind is LibraryMediaKind.Images &&
|
|| path2.MediaKind is LibraryMediaKind.Images &&
|
||||||
path1.MediaKind is LibraryMediaKind.OtherVideos);
|
path1.MediaKind is LibraryMediaKind.OtherVideos;
|
||||||
|
|
||||||
if (imagesAndOtherVideos)
|
if (imagesAndOtherVideos)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,8 +102,7 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase,
|
|||||||
UpdateLocalLibrary request) =>
|
UpdateLocalLibrary request) =>
|
||||||
LocalLibraryMustExist(dbContext, request)
|
LocalLibraryMustExist(dbContext, request)
|
||||||
.BindT(parameters => NameMustBeValid(request, parameters.Incoming).MapT(_ => parameters))
|
.BindT(parameters => NameMustBeValid(request, parameters.Incoming).MapT(_ => parameters))
|
||||||
.BindT(
|
.BindT(parameters => PathsMustBeValid(dbContext, parameters.Incoming, parameters.Existing.Id)
|
||||||
parameters => PathsMustBeValid(dbContext, parameters.Incoming, parameters.Existing.Id)
|
|
||||||
.MapT(_ => parameters));
|
.MapT(_ => parameters));
|
||||||
|
|
||||||
private static Task<Validation<BaseError, Parameters>> LocalLibraryMustExist(
|
private static Task<Validation<BaseError, Parameters>> LocalLibraryMustExist(
|
||||||
@@ -112,8 +111,7 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase,
|
|||||||
dbContext.LocalLibraries
|
dbContext.LocalLibraries
|
||||||
.Include(ll => ll.Paths)
|
.Include(ll => ll.Paths)
|
||||||
.SelectOneAsync(ll => ll.Id, ll => ll.Id == request.Id)
|
.SelectOneAsync(ll => ll.Id, ll => ll.Id == request.Id)
|
||||||
.MapT(
|
.MapT(existing =>
|
||||||
existing =>
|
|
||||||
{
|
{
|
||||||
var incoming = new LocalLibrary
|
var incoming = new LocalLibrary
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ public class GetAllLocalLibrariesHandler : IRequestHandler<GetAllLocalLibraries,
|
|||||||
GetAllLocalLibraries request,
|
GetAllLocalLibraries request,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
_libraryRepository.GetAll()
|
_libraryRepository.GetAll()
|
||||||
.Map(
|
.Map(list => list
|
||||||
list => list
|
|
||||||
.OfType<LocalLibrary>()
|
.OfType<LocalLibrary>()
|
||||||
.OrderBy(l => l.MediaKind)
|
.OrderBy(l => l.MediaKind)
|
||||||
.Map(ProjectToViewModel)
|
.Map(ProjectToViewModel)
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ public class GetConfiguredLibrariesHandler : IRequestHandler<GetConfiguredLibrar
|
|||||||
GetConfiguredLibraries request,
|
GetConfiguredLibraries request,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
_libraryRepository.GetAll()
|
_libraryRepository.GetAll()
|
||||||
.Map(
|
.Map(list => list.Filter(ShouldIncludeLibrary)
|
||||||
list => list.Filter(ShouldIncludeLibrary)
|
|
||||||
.OrderBy(l => l.MediaSource is LocalMediaSource ? 0 : 1)
|
.OrderBy(l => l.MediaSource is LocalMediaSource ? 0 : 1)
|
||||||
.ThenBy(l => l.GetType().Name)
|
.ThenBy(l => l.GetType().Name)
|
||||||
.ThenBy(l => l.MediaKind)
|
.ThenBy(l => l.MediaKind)
|
||||||
|
|||||||
@@ -46,8 +46,13 @@ public class GetExternalCollectionsHandler : IRequestHandler<GetExternalCollecti
|
|||||||
.Map(jms => jms.Id)
|
.Map(jms => jms.Id)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
return jellyfinMediaSourceIds.Map(
|
return jellyfinMediaSourceIds.Map(id => new LibraryViewModel(
|
||||||
id => new LibraryViewModel("Jellyfin", 0, "Collections", 0, id, string.Empty));
|
"Jellyfin",
|
||||||
|
0,
|
||||||
|
"Collections",
|
||||||
|
0,
|
||||||
|
id,
|
||||||
|
string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<IEnumerable<LibraryViewModel>> GetPlexExternalCollections(
|
private static async Task<IEnumerable<LibraryViewModel>> GetPlexExternalCollections(
|
||||||
@@ -59,7 +64,6 @@ public class GetExternalCollectionsHandler : IRequestHandler<GetExternalCollecti
|
|||||||
.Map(pms => pms.Id)
|
.Map(pms => pms.Id)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
return plexMediaSourceIds.Map(
|
return plexMediaSourceIds.Map(id => new LibraryViewModel("Plex", 0, "Collections", 0, id, string.Empty));
|
||||||
id => new LibraryViewModel("Plex", 0, "Collections", 0, id, string.Empty));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ public class GetRecentLogEntriesHandler : IRequestHandler<GetRecentLogEntries, P
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.Filter))
|
if (!string.IsNullOrWhiteSpace(request.Filter))
|
||||||
{
|
{
|
||||||
entries = entries.Filter(
|
entries = entries.Filter(le =>
|
||||||
le => le.Level.ToString().Contains(request.Filter, StringComparison.OrdinalIgnoreCase) ||
|
le.Level.ToString().Contains(request.Filter, StringComparison.OrdinalIgnoreCase) ||
|
||||||
le.Message.Contains(request.Filter, StringComparison.OrdinalIgnoreCase));
|
le.Message.Contains(request.Filter, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,8 +171,8 @@ internal static class Mapper
|
|||||||
Option<EmbyMediaSource> maybeEmby) =>
|
Option<EmbyMediaSource> maybeEmby) =>
|
||||||
new(
|
new(
|
||||||
collection.Name,
|
collection.Name,
|
||||||
collection.MediaItems.OfType<Movie>().Map(
|
collection.MediaItems.OfType<Movie>().Map(m =>
|
||||||
m => ProjectToViewModel(m.MovieMetadata.Head(), maybeJellyfin, maybeEmby) with
|
ProjectToViewModel(m.MovieMetadata.Head(), maybeJellyfin, maybeEmby) with
|
||||||
{
|
{
|
||||||
CustomIndex = GetCustomIndex(collection, m.Id)
|
CustomIndex = GetCustomIndex(collection, m.Id)
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
@@ -183,8 +183,7 @@ internal static class Mapper
|
|||||||
.ToList(),
|
.ToList(),
|
||||||
// collection view doesn't use local paths
|
// collection view doesn't use local paths
|
||||||
collection.MediaItems.OfType<Episode>()
|
collection.MediaItems.OfType<Episode>()
|
||||||
.Map(
|
.Map(e => ProjectToViewModel(
|
||||||
e => ProjectToViewModel(
|
|
||||||
e.EpisodeMetadata.Head(),
|
e.EpisodeMetadata.Head(),
|
||||||
maybeJellyfin,
|
maybeJellyfin,
|
||||||
maybeEmby,
|
maybeEmby,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class AddArtistToCollectionHandler :
|
|||||||
IRequestHandler<AddArtistToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddArtistToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddArtistToCollectionHandler(
|
public AddArtistToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class AddEpisodeToCollectionHandler :
|
|||||||
IRequestHandler<AddEpisodeToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddEpisodeToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddEpisodeToCollectionHandler(
|
public AddEpisodeToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ namespace ErsatzTV.Application.MediaCollections;
|
|||||||
public class AddImageToCollectionHandler : IRequestHandler<AddImageToCollection, Either<BaseError, Unit>>
|
public class AddImageToCollectionHandler : IRequestHandler<AddImageToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddImageToCollectionHandler(
|
public AddImageToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ public class AddItemsToCollectionHandler :
|
|||||||
IRequestHandler<AddItemsToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddItemsToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
private readonly IMovieRepository _movieRepository;
|
private readonly IMovieRepository _movieRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
private readonly ITelevisionRepository _televisionRepository;
|
private readonly ITelevisionRepository _televisionRepository;
|
||||||
|
|
||||||
public AddItemsToCollectionHandler(
|
public AddItemsToCollectionHandler(
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class AddMovieToCollectionHandler :
|
|||||||
IRequestHandler<AddMovieToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddMovieToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddMovieToCollectionHandler(
|
public AddMovieToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class AddMusicVideoToCollectionHandler :
|
|||||||
IRequestHandler<AddMusicVideoToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddMusicVideoToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddMusicVideoToCollectionHandler(
|
public AddMusicVideoToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class AddOtherVideoToCollectionHandler :
|
|||||||
IRequestHandler<AddOtherVideoToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddOtherVideoToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddOtherVideoToCollectionHandler(
|
public AddOtherVideoToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class AddSeasonToCollectionHandler :
|
|||||||
IRequestHandler<AddSeasonToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddSeasonToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddSeasonToCollectionHandler(
|
public AddSeasonToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class AddShowToCollectionHandler :
|
|||||||
IRequestHandler<AddShowToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddShowToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddShowToCollectionHandler(
|
public AddShowToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class AddSongToCollectionHandler :
|
|||||||
IRequestHandler<AddSongToCollection, Either<BaseError, Unit>>
|
IRequestHandler<AddSongToCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public AddSongToCollectionHandler(
|
public AddSongToCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -89,11 +89,11 @@ public partial class AddTraktListHandler : TraktCommandBase, IRequestHandler<Add
|
|||||||
return maybeList.Map(_ => Unit.Default);
|
return maybeList.Map(_ => Unit.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed record Parameters(string User, string List);
|
|
||||||
|
|
||||||
[GeneratedRegex(@"https:\/\/trakt\.tv\/users\/([\w\-_]+)\/(?:lists\/)?([\w\-_]+)")]
|
[GeneratedRegex(@"https:\/\/trakt\.tv\/users\/([\w\-_]+)\/(?:lists\/)?([\w\-_]+)")]
|
||||||
private static partial Regex UriTraktListRegex();
|
private static partial Regex UriTraktListRegex();
|
||||||
|
|
||||||
[GeneratedRegex(@"([\w\-_]+)\/(?:lists\/)?([\w\-_]+)")]
|
[GeneratedRegex(@"([\w\-_]+)\/(?:lists\/)?([\w\-_]+)")]
|
||||||
private static partial Regex ShorthandTraktListRegex();
|
private static partial Regex ShorthandTraktListRegex();
|
||||||
|
|
||||||
|
private sealed record Parameters(string User, string List);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ public class CreateCollectionHandler :
|
|||||||
private static Task<Validation<BaseError, Collection>> Validate(
|
private static Task<Validation<BaseError, Collection>> Validate(
|
||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateCollection request) =>
|
CreateCollection request) =>
|
||||||
ValidateName(dbContext, request).MapT(
|
ValidateName(dbContext, request).MapT(name => new Collection
|
||||||
name => new Collection
|
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
MediaItems = new List<MediaItem>()
|
MediaItems = new List<MediaItem>()
|
||||||
|
|||||||
@@ -51,12 +51,10 @@ public class CreateMultiCollectionHandler :
|
|||||||
private static Task<Validation<BaseError, MultiCollection>> Validate(
|
private static Task<Validation<BaseError, MultiCollection>> Validate(
|
||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateMultiCollection request) =>
|
CreateMultiCollection request) =>
|
||||||
ValidateName(dbContext, request).MapT(
|
ValidateName(dbContext, request).MapT(name => new MultiCollection
|
||||||
name => new MultiCollection
|
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
MultiCollectionItems = request.Items.Bind(
|
MultiCollectionItems = request.Items.Bind(i =>
|
||||||
i =>
|
|
||||||
{
|
{
|
||||||
if (i.CollectionId.HasValue)
|
if (i.CollectionId.HasValue)
|
||||||
{
|
{
|
||||||
@@ -72,8 +70,7 @@ public class CreateMultiCollectionHandler :
|
|||||||
return Option<MultiCollectionItem>.None;
|
return Option<MultiCollectionItem>.None;
|
||||||
})
|
})
|
||||||
.ToList(),
|
.ToList(),
|
||||||
MultiCollectionSmartItems = request.Items.Bind(
|
MultiCollectionSmartItems = request.Items.Bind(i =>
|
||||||
i =>
|
|
||||||
{
|
{
|
||||||
if (i.SmartCollectionId.HasValue)
|
if (i.SmartCollectionId.HasValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ public class CreatePlaylistHandler(IDbContextFactory<TvContext> dbContextFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Validation<BaseError, Playlist>> Validate(TvContext dbContext, CreatePlaylist request) =>
|
private static async Task<Validation<BaseError, Playlist>> Validate(TvContext dbContext, CreatePlaylist request) =>
|
||||||
await ValidatePlaylistName(dbContext, request).MapT(
|
await ValidatePlaylistName(dbContext, request).MapT(name => new Playlist
|
||||||
name => new Playlist
|
|
||||||
{
|
{
|
||||||
PlaylistGroupId = request.PlaylistGroupId,
|
PlaylistGroupId = request.PlaylistGroupId,
|
||||||
Name = name
|
Name = name
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ public class CreateSmartCollectionHandler :
|
|||||||
private static Task<Validation<BaseError, SmartCollection>> Validate(
|
private static Task<Validation<BaseError, SmartCollection>> Validate(
|
||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateSmartCollection request) =>
|
CreateSmartCollection request) =>
|
||||||
ValidateName(dbContext, request).MapT(
|
ValidateName(dbContext, request).MapT(name => new SmartCollection
|
||||||
name => new SmartCollection
|
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Query = request.Query
|
Query = request.Query
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ namespace ErsatzTV.Application.MediaCollections;
|
|||||||
public class RemoveItemsFromCollectionHandler : IRequestHandler<RemoveItemsFromCollection, Either<BaseError, Unit>>
|
public class RemoveItemsFromCollectionHandler : IRequestHandler<RemoveItemsFromCollection, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
|
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||||
|
|
||||||
public RemoveItemsFromCollectionHandler(
|
public RemoveItemsFromCollectionHandler(
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ public class UpdateMultiCollectionHandler : IRequestHandler<UpdateMultiCollectio
|
|||||||
.Filter(i => i.CollectionId.HasValue)
|
.Filter(i => i.CollectionId.HasValue)
|
||||||
// ReSharper disable once PossibleInvalidOperationException
|
// ReSharper disable once PossibleInvalidOperationException
|
||||||
.Filter(i => c.MultiCollectionItems.All(i2 => i2.CollectionId != i.CollectionId.Value))
|
.Filter(i => c.MultiCollectionItems.All(i2 => i2.CollectionId != i.CollectionId.Value))
|
||||||
.Map(
|
.Map(i => new MultiCollectionItem
|
||||||
i => new MultiCollectionItem
|
|
||||||
{
|
{
|
||||||
// ReSharper disable once PossibleInvalidOperationException
|
// ReSharper disable once PossibleInvalidOperationException
|
||||||
CollectionId = i.CollectionId.Value,
|
CollectionId = i.CollectionId.Value,
|
||||||
@@ -70,8 +69,8 @@ public class UpdateMultiCollectionHandler : IRequestHandler<UpdateMultiCollectio
|
|||||||
// update existing items
|
// update existing items
|
||||||
foreach (MultiCollectionItem item in c.MultiCollectionItems)
|
foreach (MultiCollectionItem item in c.MultiCollectionItems)
|
||||||
{
|
{
|
||||||
foreach (UpdateMultiCollectionItem incoming in request.Items.Filter(
|
foreach (UpdateMultiCollectionItem incoming in
|
||||||
i => i.CollectionId == item.CollectionId))
|
request.Items.Filter(i => i.CollectionId == item.CollectionId))
|
||||||
{
|
{
|
||||||
item.ScheduleAsGroup = incoming.ScheduleAsGroup;
|
item.ScheduleAsGroup = incoming.ScheduleAsGroup;
|
||||||
item.PlaybackOrder = incoming.PlaybackOrder;
|
item.PlaybackOrder = incoming.PlaybackOrder;
|
||||||
@@ -85,8 +84,7 @@ public class UpdateMultiCollectionHandler : IRequestHandler<UpdateMultiCollectio
|
|||||||
.Filter(i => i.SmartCollectionId.HasValue)
|
.Filter(i => i.SmartCollectionId.HasValue)
|
||||||
// ReSharper disable once PossibleInvalidOperationException
|
// ReSharper disable once PossibleInvalidOperationException
|
||||||
.Filter(i => c.MultiCollectionSmartItems.All(i2 => i2.SmartCollectionId != i.SmartCollectionId.Value))
|
.Filter(i => c.MultiCollectionSmartItems.All(i2 => i2.SmartCollectionId != i.SmartCollectionId.Value))
|
||||||
.Map(
|
.Map(i => new MultiCollectionSmartItem
|
||||||
i => new MultiCollectionSmartItem
|
|
||||||
{
|
{
|
||||||
// ReSharper disable once PossibleInvalidOperationException
|
// ReSharper disable once PossibleInvalidOperationException
|
||||||
SmartCollectionId = i.SmartCollectionId.Value,
|
SmartCollectionId = i.SmartCollectionId.Value,
|
||||||
@@ -105,8 +103,8 @@ public class UpdateMultiCollectionHandler : IRequestHandler<UpdateMultiCollectio
|
|||||||
// update existing items
|
// update existing items
|
||||||
foreach (MultiCollectionSmartItem item in c.MultiCollectionSmartItems)
|
foreach (MultiCollectionSmartItem item in c.MultiCollectionSmartItems)
|
||||||
{
|
{
|
||||||
foreach (UpdateMultiCollectionItem incoming in request.Items.Filter(
|
foreach (UpdateMultiCollectionItem incoming in request.Items.Filter(i =>
|
||||||
i => i.SmartCollectionId == item.SmartCollectionId))
|
i.SmartCollectionId == item.SmartCollectionId))
|
||||||
{
|
{
|
||||||
item.ScheduleAsGroup = incoming.ScheduleAsGroup;
|
item.ScheduleAsGroup = incoming.ScheduleAsGroup;
|
||||||
item.PlaybackOrder = incoming.PlaybackOrder;
|
item.PlaybackOrder = incoming.PlaybackOrder;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Globalization;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Domain;
|
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
|
||||||
using ErsatzTV.Core.Metadata;
|
using ErsatzTV.Core.Metadata;
|
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaItems;
|
namespace ErsatzTV.Application.MediaItems;
|
||||||
|
|||||||
@@ -46,9 +46,10 @@ internal static class Mapper
|
|||||||
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
||||||
|
|
||||||
return languageCodes
|
return languageCodes
|
||||||
.Map(
|
.Map(lang => allCultures.Filter(ci => string.Equals(
|
||||||
lang => allCultures.Filter(
|
ci.ThreeLetterISOLanguageName,
|
||||||
ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase)))
|
lang,
|
||||||
|
StringComparison.OrdinalIgnoreCase)))
|
||||||
.Flatten()
|
.Flatten()
|
||||||
.Map(ci => ci.EnglishName)
|
.Map(ci => ci.EnglishName)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
|||||||
{
|
{
|
||||||
private readonly IBlockPlayoutBuilder _blockPlayoutBuilder;
|
private readonly IBlockPlayoutBuilder _blockPlayoutBuilder;
|
||||||
private readonly IBlockPlayoutFillerBuilder _blockPlayoutFillerBuilder;
|
private readonly IBlockPlayoutFillerBuilder _blockPlayoutFillerBuilder;
|
||||||
private readonly IYamlPlayoutBuilder _yamlPlayoutBuilder;
|
|
||||||
private readonly IClient _client;
|
private readonly IClient _client;
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IEntityLocker _entityLocker;
|
private readonly IEntityLocker _entityLocker;
|
||||||
@@ -27,6 +26,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
|||||||
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
|
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
|
||||||
private readonly IPlayoutBuilder _playoutBuilder;
|
private readonly IPlayoutBuilder _playoutBuilder;
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
||||||
|
private readonly IYamlPlayoutBuilder _yamlPlayoutBuilder;
|
||||||
|
|
||||||
public BuildPlayoutHandler(
|
public BuildPlayoutHandler(
|
||||||
IClient client,
|
IClient client,
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ public class CreateBlockPlayoutHandler(
|
|||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateBlockPlayout request) =>
|
CreateBlockPlayout request) =>
|
||||||
(await ValidateChannel(dbContext, request), ValidatePlayoutType(request))
|
(await ValidateChannel(dbContext, request), ValidatePlayoutType(request))
|
||||||
.Apply(
|
.Apply((channel, playoutType) => new Playout
|
||||||
(channel, playoutType) => new Playout
|
|
||||||
{
|
{
|
||||||
ChannelId = channel.Id,
|
ChannelId = channel.Id,
|
||||||
ProgramSchedulePlayoutType = playoutType,
|
ProgramSchedulePlayoutType = playoutType,
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ public class CreateExternalJsonPlayoutHandler
|
|||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateExternalJsonPlayout request) =>
|
CreateExternalJsonPlayout request) =>
|
||||||
(await ValidateChannel(dbContext, request), ValidateExternalJsonFile(request), ValidatePlayoutType(request))
|
(await ValidateChannel(dbContext, request), ValidateExternalJsonFile(request), ValidatePlayoutType(request))
|
||||||
.Apply(
|
.Apply((channel, externalJsonFile, playoutType) => new Playout
|
||||||
(channel, externalJsonFile, playoutType) => new Playout
|
|
||||||
{
|
{
|
||||||
ChannelId = channel.Id,
|
ChannelId = channel.Id,
|
||||||
ExternalJsonFile = externalJsonFile,
|
ExternalJsonFile = externalJsonFile,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit
|
|||||||
{
|
{
|
||||||
await _channel.WriteAsync(new TimeShiftOnDemandPlayout(playout.Channel.Number, DateTimeOffset.Now, false));
|
await _channel.WriteAsync(new TimeShiftOnDemandPlayout(playout.Channel.Number, DateTimeOffset.Now, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
await _channel.WriteAsync(new RefreshChannelList());
|
await _channel.WriteAsync(new RefreshChannelList());
|
||||||
return new CreatePlayoutResponse(playout.Id);
|
return new CreatePlayoutResponse(playout.Id);
|
||||||
}
|
}
|
||||||
@@ -50,8 +51,7 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit
|
|||||||
CreateFloodPlayout request) =>
|
CreateFloodPlayout request) =>
|
||||||
(await ValidateChannel(dbContext, request), await ValidateProgramSchedule(dbContext, request),
|
(await ValidateChannel(dbContext, request), await ValidateProgramSchedule(dbContext, request),
|
||||||
ValidatePlayoutType(request))
|
ValidatePlayoutType(request))
|
||||||
.Apply(
|
.Apply((channel, programSchedule, playoutType) => new Playout
|
||||||
(channel, programSchedule, playoutType) => new Playout
|
|
||||||
{
|
{
|
||||||
ChannelId = channel.Id,
|
ChannelId = channel.Id,
|
||||||
ProgramScheduleId = programSchedule.Id,
|
ProgramScheduleId = programSchedule.Id,
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ public class CreateYamlPlayoutHandler
|
|||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateYamlPlayout request) =>
|
CreateYamlPlayout request) =>
|
||||||
(await ValidateChannel(dbContext, request), ValidateYamlFile(request), ValidatePlayoutType(request))
|
(await ValidateChannel(dbContext, request), ValidateYamlFile(request), ValidatePlayoutType(request))
|
||||||
.Apply(
|
.Apply((channel, externalJsonFile, playoutType) => new Playout
|
||||||
(channel, externalJsonFile, playoutType) => new Playout
|
|
||||||
{
|
{
|
||||||
ChannelId = channel.Id,
|
ChannelId = channel.Id,
|
||||||
TemplateFile = externalJsonFile,
|
TemplateFile = externalJsonFile,
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ public class ResetAllPlayoutsHandler(
|
|||||||
case ProgramSchedulePlayoutType.Yaml:
|
case ProgramSchedulePlayoutType.Yaml:
|
||||||
if (!locker.IsPlayoutLocked(playout.Id))
|
if (!locker.IsPlayoutLocked(playout.Id))
|
||||||
{
|
{
|
||||||
await channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset), cancellationToken);
|
await channel.WriteAsync(
|
||||||
|
new BuildPlayout(playout.Id, PlayoutBuildMode.Reset),
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ProgramSchedulePlayoutType.ExternalJson:
|
case ProgramSchedulePlayoutType.ExternalJson:
|
||||||
case ProgramSchedulePlayoutType.None:
|
case ProgramSchedulePlayoutType.None:
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Globalization;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Domain;
|
|
||||||
|
|
||||||
namespace ErsatzTV.Application.Playouts;
|
namespace ErsatzTV.Application.Playouts;
|
||||||
|
|
||||||
@@ -51,16 +50,14 @@ internal static class Mapper
|
|||||||
.Map(am => $"{am.Title} - ").IfNone(string.Empty);
|
.Map(am => $"{am.Title} - ").IfNone(string.Empty);
|
||||||
return mv.MusicVideoMetadata.HeadOrNone()
|
return mv.MusicVideoMetadata.HeadOrNone()
|
||||||
.Map(mvm => $"{artistName}{mvm.Title}")
|
.Map(mvm => $"{artistName}{mvm.Title}")
|
||||||
.Map(
|
.Map(s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||||
s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
|
||||||
? s
|
? s
|
||||||
: $"{s} ({playoutItem.ChapterTitle})")
|
: $"{s} ({playoutItem.ChapterTitle})")
|
||||||
.IfNone("[unknown music video]");
|
.IfNone("[unknown music video]");
|
||||||
case OtherVideo ov:
|
case OtherVideo ov:
|
||||||
return ov.OtherVideoMetadata.HeadOrNone()
|
return ov.OtherVideoMetadata.HeadOrNone()
|
||||||
.Map(ovm => ovm.Title ?? string.Empty)
|
.Map(ovm => ovm.Title ?? string.Empty)
|
||||||
.Map(
|
.Map(s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||||
s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
|
||||||
? s
|
? s
|
||||||
: $"{s} ({playoutItem.ChapterTitle})")
|
: $"{s} ({playoutItem.ChapterTitle})")
|
||||||
.IfNone("[unknown video]");
|
.IfNone("[unknown video]");
|
||||||
@@ -70,8 +67,7 @@ internal static class Mapper
|
|||||||
.IfNone(string.Empty);
|
.IfNone(string.Empty);
|
||||||
return s.SongMetadata.HeadOrNone()
|
return s.SongMetadata.HeadOrNone()
|
||||||
.Map(sm => $"{songArtist}{sm.Title ?? string.Empty}")
|
.Map(sm => $"{songArtist}{sm.Title ?? string.Empty}")
|
||||||
.Map(
|
.Map(t => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||||
t => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
|
||||||
? t
|
? t
|
||||||
: $"{s} ({playoutItem.ChapterTitle})")
|
: $"{s} ({playoutItem.ChapterTitle})")
|
||||||
.IfNone("[unknown song]");
|
.IfNone("[unknown song]");
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public class GetAllPlayoutsHandler : IRequestHandler<GetAllPlayouts, List<Playou
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(p => p.ProgramSchedule)
|
.Include(p => p.ProgramSchedule)
|
||||||
.Filter(p => p.Channel != null)
|
.Filter(p => p.Channel != null)
|
||||||
.Map(
|
.Map(p => new PlayoutNameViewModel(
|
||||||
p => new PlayoutNameViewModel(
|
|
||||||
p.Id,
|
p.Id,
|
||||||
p.ProgramSchedulePlayoutType,
|
p.ProgramSchedulePlayoutType,
|
||||||
p.Channel.Name,
|
p.Channel.Name,
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public class GetPlayoutByIdHandler(IDbContextFactory<TvContext> dbContextFactory
|
|||||||
.Include(p => p.ProgramSchedule)
|
.Include(p => p.ProgramSchedule)
|
||||||
.Include(p => p.Channel)
|
.Include(p => p.Channel)
|
||||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId)
|
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId)
|
||||||
.MapT(
|
.MapT(p => new PlayoutNameViewModel(
|
||||||
p => new PlayoutNameViewModel(
|
|
||||||
p.Id,
|
p.Id,
|
||||||
p.ProgramSchedulePlayoutType,
|
p.ProgramSchedulePlayoutType,
|
||||||
p.Channel.Name,
|
p.Channel.Name,
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ public class StartPlexPinFlowHandler : IRequestHandler<StartPlexPinFlow, Either<
|
|||||||
public Task<Either<BaseError, string>> Handle(
|
public Task<Either<BaseError, string>> Handle(
|
||||||
StartPlexPinFlow request,
|
StartPlexPinFlow request,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
_plexTvApiClient.StartPinFlow().Bind(
|
_plexTvApiClient.StartPinFlow().Bind(result => result.Match(
|
||||||
result => result.Match(
|
|
||||||
Left: error => Task.FromResult(Left<BaseError, string>(error)),
|
Left: error => Task.FromResult(Left<BaseError, string>(error)),
|
||||||
Right: async pin =>
|
Right: async pin =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.Plex;
|
namespace ErsatzTV.Application.Plex;
|
||||||
|
|
||||||
public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler, IRequestHandler<SynchronizePlexMediaSources,
|
public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler,
|
||||||
|
IRequestHandler<SynchronizePlexMediaSources,
|
||||||
Either<BaseError, List<PlexMediaSource>>>
|
Either<BaseError, List<PlexMediaSource>>>
|
||||||
{
|
{
|
||||||
private const string LocalhostUri = "http://localhost:32400";
|
private const string LocalhostUri = "http://localhost:32400";
|
||||||
@@ -56,8 +57,8 @@ public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler, IRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delete removed servers
|
// delete removed servers
|
||||||
foreach (PlexMediaSource removed in allExisting.Filter(
|
foreach (PlexMediaSource removed in allExisting.Filter(s =>
|
||||||
s => servers.All(pms => pms.ClientIdentifier != s.ClientIdentifier)))
|
servers.All(pms => pms.ClientIdentifier != s.ClientIdentifier)))
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"Deleting removed Plex server {ServerName}!",
|
"Deleting removed Plex server {ServerName}!",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Interfaces.Plex;
|
using ErsatzTV.Core.Interfaces.Plex;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
@@ -12,7 +13,9 @@ public abstract class PlexBaseConnectionHandler(
|
|||||||
IMediaSourceRepository mediaSourceRepository,
|
IMediaSourceRepository mediaSourceRepository,
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
{
|
{
|
||||||
protected async Task<Option<PlexConnection>> FindConnectionToActivate(PlexMediaSource server, PlexServerAuthToken token)
|
protected async Task<Option<PlexConnection>> FindConnectionToActivate(
|
||||||
|
PlexMediaSource server,
|
||||||
|
PlexServerAuthToken token)
|
||||||
{
|
{
|
||||||
Option<PlexConnection> result = Option<PlexConnection>.None;
|
Option<PlexConnection> result = Option<PlexConnection>.None;
|
||||||
|
|
||||||
@@ -43,7 +46,8 @@ public abstract class PlexBaseConnectionHandler(
|
|||||||
tasks.Remove(completed);
|
tasks.Remove(completed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Option<PlexConnection> maybeBest = successfulTimes.OrderByDescending(kv => kv.Value).Select(kvp => kvp.Key).HeadOrNone();
|
Option<PlexConnection> maybeBest =
|
||||||
|
successfulTimes.OrderByDescending(kv => kv.Value).Select(kvp => kvp.Key).HeadOrNone();
|
||||||
foreach (PlexConnection connection in maybeBest)
|
foreach (PlexConnection connection in maybeBest)
|
||||||
{
|
{
|
||||||
connection.IsActive = true;
|
connection.IsActive = true;
|
||||||
@@ -60,12 +64,16 @@ public abstract class PlexBaseConnectionHandler(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PingPlexConnection(PlexConnection connection, PlexServerAuthToken token, ConcurrentDictionary<PlexConnection, TimeSpan> successfulTimes, CancellationToken cancellationToken)
|
private async Task PingPlexConnection(
|
||||||
|
PlexConnection connection,
|
||||||
|
PlexServerAuthToken token,
|
||||||
|
ConcurrentDictionary<PlexConnection, TimeSpan> successfulTimes,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.LogDebug("Attempting to locate to Plex at {Uri}", connection.Uri);
|
logger.LogDebug("Attempting to locate to Plex at {Uri}", connection.Uri);
|
||||||
var sw = new System.Diagnostics.Stopwatch();
|
var sw = new Stopwatch();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
bool pingResult = await plexServerApiClient.Ping(connection, token, cancellationToken);
|
bool pingResult = await plexServerApiClient.Ping(connection, token, cancellationToken);
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.Plex;
|
namespace ErsatzTV.Application.Plex;
|
||||||
|
|
||||||
public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler, IRequestHandler<GetPlexConnectionParameters,
|
public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler,
|
||||||
|
IRequestHandler<GetPlexConnectionParameters,
|
||||||
Either<BaseError, PlexConnectionParametersViewModel>>
|
Either<BaseError, PlexConnectionParametersViewModel>>
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<GetPlexConnectionParametersHandler> _logger;
|
||||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
private readonly IPlexServerApiClient _plexServerApiClient;
|
|
||||||
private readonly IPlexSecretStore _plexSecretStore;
|
private readonly IPlexSecretStore _plexSecretStore;
|
||||||
private readonly ILogger<GetPlexConnectionParametersHandler> _logger;
|
private readonly IPlexServerApiClient _plexServerApiClient;
|
||||||
|
|
||||||
public GetPlexConnectionParametersHandler(
|
public GetPlexConnectionParametersHandler(
|
||||||
IMemoryCache memoryCache,
|
IMemoryCache memoryCache,
|
||||||
@@ -49,7 +50,8 @@ public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler, IRe
|
|||||||
foreach (PlexServerAuthToken token in maybeToken)
|
foreach (PlexServerAuthToken token in maybeToken)
|
||||||
{
|
{
|
||||||
// try to keep the same connection
|
// try to keep the same connection
|
||||||
Option<PlexConnection> maybeActiveConnection = mediaSource.Connections.Filter(c => c.IsActive).HeadOrNone();
|
Option<PlexConnection> maybeActiveConnection =
|
||||||
|
mediaSource.Connections.Filter(c => c.IsActive).HeadOrNone();
|
||||||
foreach (PlexConnection activeConnection in maybeActiveConnection)
|
foreach (PlexConnection activeConnection in maybeActiveConnection)
|
||||||
{
|
{
|
||||||
if (await _plexServerApiClient.Ping(activeConnection, token, cancellationToken))
|
if (await _plexServerApiClient.Ping(activeConnection, token, cancellationToken))
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ public class CreateProgramScheduleHandler(IDbContextFactory<TvContext> dbContext
|
|||||||
private static Task<Validation<BaseError, ProgramSchedule>> Validate(
|
private static Task<Validation<BaseError, ProgramSchedule>> Validate(
|
||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateProgramSchedule request) =>
|
CreateProgramSchedule request) =>
|
||||||
ValidateName(dbContext, request).MapT(
|
ValidateName(dbContext, request).MapT(name =>
|
||||||
name =>
|
|
||||||
{
|
{
|
||||||
bool keepMultiPartEpisodesTogether = request.KeepMultiPartEpisodesTogether;
|
bool keepMultiPartEpisodesTogether = request.KeepMultiPartEpisodesTogether;
|
||||||
return new ProgramSchedule
|
return new ProgramSchedule
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ public class GetAllProgramSchedulesHandler(IDbContextFactory<TvContext> dbContex
|
|||||||
{
|
{
|
||||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
return await dbContext.ProgramSchedules
|
return await dbContext.ProgramSchedules
|
||||||
.Map(
|
.Map(ps => new ProgramScheduleViewModel(
|
||||||
ps => new ProgramScheduleViewModel(
|
|
||||||
ps.Id,
|
ps.Id,
|
||||||
ps.Name,
|
ps.Name,
|
||||||
ps.KeepMultiPartEpisodesTogether,
|
ps.KeepMultiPartEpisodesTogether,
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ public class GetProgramScheduleItemsHandler :
|
|||||||
.Include(i => i.FallbackFiller)
|
.Include(i => i.FallbackFiller)
|
||||||
.Include(i => i.Watermark)
|
.Include(i => i.Watermark)
|
||||||
.ToListAsync(cancellationToken)
|
.ToListAsync(cancellationToken)
|
||||||
.Map(
|
.Map(programScheduleItems => programScheduleItems.Map(ProjectToViewModel)
|
||||||
programScheduleItems => programScheduleItems.Map(ProjectToViewModel)
|
|
||||||
.Map(psi => EnforceProperties(maybeProgramSchedule, psi)).ToList());
|
.Map(psi => EnforceProperties(maybeProgramSchedule, psi)).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ public class CreateCustomResolutionHandler : IRequestHandler<CreateCustomResolut
|
|||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
CreateCustomResolution request) =>
|
CreateCustomResolution request) =>
|
||||||
ResolutionMustBeUnique(dbContext, request)
|
ResolutionMustBeUnique(dbContext, request)
|
||||||
.MapT(
|
.MapT(_ => new Resolution
|
||||||
_ => new Resolution
|
|
||||||
{
|
{
|
||||||
Name = $"{request.Width}x{request.Height}",
|
Name = $"{request.Width}x{request.Height}",
|
||||||
Width = request.Width,
|
Width = request.Width,
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ public class CreateBlockHandler(IDbContextFactory<TvContext> dbContextFactory)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Validation<BaseError, Block>> Validate(TvContext dbContext, CreateBlock request) =>
|
private static async Task<Validation<BaseError, Block>> Validate(TvContext dbContext, CreateBlock request) =>
|
||||||
await ValidateBlockName(dbContext, request).MapT(
|
await ValidateBlockName(dbContext, request).MapT(name => new Block
|
||||||
name => new Block
|
|
||||||
{
|
{
|
||||||
BlockGroupId = request.BlockGroupId,
|
BlockGroupId = request.BlockGroupId,
|
||||||
Name = name,
|
Name = name,
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ public class CreateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Validation<BaseError, Deco>> Validate(TvContext dbContext, CreateDeco request) =>
|
private static async Task<Validation<BaseError, Deco>> Validate(TvContext dbContext, CreateDeco request) =>
|
||||||
await ValidateDecoName(dbContext, request).MapT(
|
await ValidateDecoName(dbContext, request).MapT(name => new Deco
|
||||||
name => new Deco
|
|
||||||
{
|
{
|
||||||
DecoGroupId = request.DecoGroupId,
|
DecoGroupId = request.DecoGroupId,
|
||||||
Name = name
|
Name = name
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ public class CreateDecoTemplateHandler(IDbContextFactory<TvContext> dbContextFac
|
|||||||
|
|
||||||
private static Task<Validation<BaseError, DecoTemplate>> Validate(CreateDecoTemplate request) =>
|
private static Task<Validation<BaseError, DecoTemplate>> Validate(CreateDecoTemplate request) =>
|
||||||
Task.FromResult(
|
Task.FromResult(
|
||||||
ValidateName(request).Map(
|
ValidateName(request).Map(name => new DecoTemplate
|
||||||
name => new DecoTemplate
|
|
||||||
{
|
{
|
||||||
DecoTemplateGroupId = request.DecoTemplateGroupId,
|
DecoTemplateGroupId = request.DecoTemplateGroupId,
|
||||||
Name = name
|
Name = name
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ public class CreateTemplateHandler(IDbContextFactory<TvContext> dbContextFactory
|
|||||||
|
|
||||||
private static Task<Validation<BaseError, Template>> Validate(CreateTemplate request) =>
|
private static Task<Validation<BaseError, Template>> Validate(CreateTemplate request) =>
|
||||||
Task.FromResult(
|
Task.FromResult(
|
||||||
ValidateName(request).Map(
|
ValidateName(request).Map(name => new Template
|
||||||
name => new Template
|
|
||||||
{
|
{
|
||||||
TemplateGroupId = request.TemplateGroupId,
|
TemplateGroupId = request.TemplateGroupId,
|
||||||
Name = name
|
Name = name
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ public class ErasePlayoutHistoryHandler(IDbContextFactory<TvContext> dbContextFa
|
|||||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
Option<Playout> maybePlayout = await dbContext.Playouts
|
Option<Playout> maybePlayout = await dbContext.Playouts
|
||||||
.Filter(
|
.Filter(p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
|
||||||
p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
|
|
||||||
p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml)
|
p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml)
|
||||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
|
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ public class ErasePlayoutItemsHandler(IDbContextFactory<TvContext> dbContextFact
|
|||||||
Option<Playout> maybePlayout = await dbContext.Playouts
|
Option<Playout> maybePlayout = await dbContext.Playouts
|
||||||
.Include(p => p.Items)
|
.Include(p => p.Items)
|
||||||
.Include(p => p.PlayoutHistory)
|
.Include(p => p.PlayoutHistory)
|
||||||
.Filter(
|
.Filter(p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
|
||||||
p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
|
|
||||||
p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml)
|
p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml)
|
||||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
|
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
|
||||||
|
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ public class ReplaceTemplateItemsHandler(IDbContextFactory<TvContext> dbContextF
|
|||||||
.ToListAsync()
|
.ToListAsync()
|
||||||
.Map(list => list.ToDictionary(b => b.Id, b => b));
|
.Map(list => list.ToDictionary(b => b.Id, b => b));
|
||||||
|
|
||||||
var allTemplateItems = request.Items.Map(
|
var allTemplateItems = request.Items.Map(i =>
|
||||||
i =>
|
|
||||||
{
|
{
|
||||||
Block block = allBlocks[i.BlockId];
|
Block block = allBlocks[i.BlockId];
|
||||||
return new BlockTemplateItem(
|
return new BlockTemplateItem(
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Option<Deco> maybeExisting = await dbContext.Decos
|
Option<Deco> maybeExisting = await dbContext.Decos
|
||||||
.FirstOrDefaultAsync(
|
.FirstOrDefaultAsync(d =>
|
||||||
d => d.Id != request.DecoId && d.DecoGroupId == request.DecoGroupId && d.Name == request.Name)
|
d.Id != request.DecoId && d.DecoGroupId == request.DecoGroupId && d.Name == request.Name)
|
||||||
.Map(Optional);
|
.Map(Optional);
|
||||||
|
|
||||||
return maybeExisting.IsSome
|
return maybeExisting.IsSome
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ public class GetDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbContextF
|
|||||||
.Filter(i => i.DecoTemplateId == request.DecoTemplateId)
|
.Filter(i => i.DecoTemplateId == request.DecoTemplateId)
|
||||||
.Include(i => i.Deco)
|
.Include(i => i.Deco)
|
||||||
.ToListAsync(cancellationToken)
|
.ToListAsync(cancellationToken)
|
||||||
.Map(
|
.Map(items => items
|
||||||
items => items
|
|
||||||
.Map(Mapper.ProjectToViewModel)
|
.Map(Mapper.ProjectToViewModel)
|
||||||
.Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero)
|
.Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero)
|
||||||
.ToList());
|
.ToList());
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ public class GetTemplateItemsHandler(IDbContextFactory<TvContext> dbContextFacto
|
|||||||
.Filter(i => i.TemplateId == request.TemplateId)
|
.Filter(i => i.TemplateId == request.TemplateId)
|
||||||
.Include(i => i.Block)
|
.Include(i => i.Block)
|
||||||
.ToListAsync(cancellationToken)
|
.ToListAsync(cancellationToken)
|
||||||
.Map(
|
.Map(items => items
|
||||||
items => items
|
|
||||||
.Map(Mapper.ProjectToViewModel)
|
.Map(Mapper.ProjectToViewModel)
|
||||||
.Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero)
|
.Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero)
|
||||||
.ToList());
|
.ToList());
|
||||||
|
|||||||
@@ -30,5 +30,6 @@ public class QuerySearchIndexAllItemsHandler : IRequestHandler<QuerySearchIndexA
|
|||||||
await GetIds(LuceneSearchIndex.ImageType, request.Query));
|
await GetIds(LuceneSearchIndex.ImageType, request.Query));
|
||||||
|
|
||||||
private async Task<List<int>> GetIds(string type, string query) =>
|
private async Task<List<int>> GetIds(string type, string query) =>
|
||||||
(await _searchIndex.Search(_client, $"type:{type} AND ({query})", string.Empty, 0, 0)).Items.Map(i => i.Id).ToList();
|
(await _searchIndex.Search(_client, $"type:{type} AND ({query})", string.Empty, 0, 0)).Items.Map(i => i.Id)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ public class QuerySearchTargetsHandler : IRequestHandler<QuerySearchTargets, Lis
|
|||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
result.AddRange(
|
result.AddRange(
|
||||||
schedules.SelectMany(
|
schedules.SelectMany(s => new[]
|
||||||
s => new[]
|
|
||||||
{
|
{
|
||||||
new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.Schedule),
|
new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.Schedule),
|
||||||
new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.ScheduleItems)
|
new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.ScheduleItems)
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public class SearchArtistsHandler : IRequestHandler<SearchArtists, List<NamedMed
|
|||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
return await dbContext.ArtistMetadata
|
return await dbContext.ArtistMetadata
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(
|
.Where(a => EF.Functions.Like(
|
||||||
a => EF.Functions.Like(
|
|
||||||
EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation),
|
EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation),
|
||||||
$"%{request.Query}%"))
|
$"%{request.Query}%"))
|
||||||
.OrderBy(a => EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation))
|
.OrderBy(a => EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation))
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public class SearchCollectionsHandler : IRequestHandler<SearchCollections, List<
|
|||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
return await dbContext.Collections
|
return await dbContext.Collections
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(
|
.Where(c => EF.Functions.Like(
|
||||||
c => EF.Functions.Like(
|
|
||||||
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
||||||
$"%{request.Query}%"))
|
$"%{request.Query}%"))
|
||||||
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
|
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ public class SearchMoviesHandler(IDbContextFactory<TvContext> dbContextFactory)
|
|||||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
return await dbContext.MovieMetadata
|
return await dbContext.MovieMetadata
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(
|
.Where(s => EF.Functions.Like(
|
||||||
s => EF.Functions.Like(
|
|
||||||
EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation),
|
EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation),
|
||||||
$"%{request.Query}%"))
|
$"%{request.Query}%"))
|
||||||
.OrderBy(a => EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation))
|
.OrderBy(a => EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation))
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public class SearchMultiCollectionsHandler : IRequestHandler<SearchMultiCollecti
|
|||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
return await dbContext.MultiCollections
|
return await dbContext.MultiCollections
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(
|
.Where(c => EF.Functions.Like(
|
||||||
c => EF.Functions.Like(
|
|
||||||
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
||||||
$"%{request.Query}%"))
|
$"%{request.Query}%"))
|
||||||
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
|
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public class SearchSmartCollectionsHandler : IRequestHandler<SearchSmartCollecti
|
|||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
return await dbContext.SmartCollections
|
return await dbContext.SmartCollections
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(
|
.Where(c => EF.Functions.Like(
|
||||||
c => EF.Functions.Like(
|
|
||||||
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
||||||
$"%{request.Query}%"))
|
$"%{request.Query}%"))
|
||||||
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
|
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ public class SearchTelevisionShowsHandler : IRequestHandler<SearchTelevisionShow
|
|||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
return await dbContext.ShowMetadata
|
return await dbContext.ShowMetadata
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(
|
.Where(s => EF.Functions.Like(
|
||||||
s => EF.Functions.Like(
|
|
||||||
EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation),
|
EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation),
|
||||||
$"%{request.Query}%"))
|
$"%{request.Query}%"))
|
||||||
.OrderBy(s => EF.Functions.Collate(s.Title, TvContext.CaseInsensitiveCollation))
|
.OrderBy(s => EF.Functions.Collate(s.Title, TvContext.CaseInsensitiveCollation))
|
||||||
|
|||||||
@@ -174,7 +174,9 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
PlaylistStart = _transcodedUntil;
|
PlaylistStart = _transcodedUntil;
|
||||||
|
|
||||||
// time shift on-demand playout if needed
|
// time shift on-demand playout if needed
|
||||||
await _mediator.Send(new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true), cancellationToken);
|
await _mediator.Send(
|
||||||
|
new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
bool initialWorkAhead = Volatile.Read(ref _workAheadCount) < await GetWorkAheadLimit();
|
bool initialWorkAhead = Volatile.Read(ref _workAheadCount) < await GetWorkAheadLimit();
|
||||||
_state = initialWorkAhead ? HlsSessionState.SeekAndWorkAhead : HlsSessionState.SeekAndRealtime;
|
_state = initialWorkAhead ? HlsSessionState.SeekAndWorkAhead : HlsSessionState.SeekAndRealtime;
|
||||||
@@ -583,8 +585,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
Directory.GetFiles(
|
Directory.GetFiles(
|
||||||
Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber),
|
Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber),
|
||||||
"live*.mp4"))
|
"live*.mp4"))
|
||||||
.Map(
|
.Map(file =>
|
||||||
file =>
|
|
||||||
{
|
{
|
||||||
string fileName = Path.GetFileName(file);
|
string fileName = Path.GetFileName(file);
|
||||||
var sequenceNumber = int.Parse(
|
var sequenceNumber = int.Parse(
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
|
|||||||
PlaylistStart = _transcodedUntil;
|
PlaylistStart = _transcodedUntil;
|
||||||
|
|
||||||
// time shift on-demand playout if needed
|
// time shift on-demand playout if needed
|
||||||
await _mediator.Send(new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true), cancellationToken);
|
await _mediator.Send(
|
||||||
|
new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
// start concat/segmenter process
|
// start concat/segmenter process
|
||||||
// other transcode processes will be started by incoming requests from concat/segmenter process
|
// other transcode processes will be started by incoming requests from concat/segmenter process
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ public abstract class FFmpegProcessHandler<T> : IRequestHandler<T, Either<BaseEr
|
|||||||
.Include(c => c.Artwork)
|
.Include(c => c.Artwork)
|
||||||
.Include(c => c.Watermark)
|
.Include(c => c.Watermark)
|
||||||
.SelectOneAsync(c => c.Number, c => c.Number == request.ChannelNumber)
|
.SelectOneAsync(c => c.Number, c => c.Number == request.ChannelNumber)
|
||||||
.MapT(
|
.MapT(channel =>
|
||||||
channel =>
|
|
||||||
{
|
{
|
||||||
channel.StreamingMode = request.Mode.ToLowerInvariant() switch
|
channel.StreamingMode = request.Mode.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-3
@@ -227,8 +227,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
|||||||
|
|
||||||
Option<ChannelWatermark> maybeGlobalWatermark = await dbContext.ConfigElements
|
Option<ChannelWatermark> maybeGlobalWatermark = await dbContext.ConfigElements
|
||||||
.GetValue<int>(ConfigElementKey.FFmpegGlobalWatermarkId)
|
.GetValue<int>(ConfigElementKey.FFmpegGlobalWatermarkId)
|
||||||
.BindT(
|
.BindT(watermarkId => dbContext.ChannelWatermarks
|
||||||
watermarkId => dbContext.ChannelWatermarks
|
|
||||||
.SelectOneAsync(w => w.Id, w => w.Id == watermarkId));
|
.SelectOneAsync(w => w.Id, w => w.Id == watermarkId));
|
||||||
|
|
||||||
Option<ChannelWatermark> playoutItemWatermark = Optional(playoutItemWithPath.PlayoutItem.Watermark);
|
Option<ChannelWatermark> playoutItemWatermark = Optional(playoutItemWithPath.PlayoutItem.Watermark);
|
||||||
@@ -261,7 +260,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
|||||||
// override watermark as song_progress_overlay.png
|
// override watermark as song_progress_overlay.png
|
||||||
if (videoVersion is BackgroundImageMediaVersion { IsSongWithProgress: true })
|
if (videoVersion is BackgroundImageMediaVersion { IsSongWithProgress: true })
|
||||||
{
|
{
|
||||||
double ratio = channel.FFmpegProfile.Resolution.Width / (double)channel.FFmpegProfile.Resolution.Height;
|
double ratio = channel.FFmpegProfile.Resolution.Width /
|
||||||
|
(double)channel.FFmpegProfile.Resolution.Height;
|
||||||
bool is43 = Math.Abs(ratio - 4.0 / 3.0) < 0.01;
|
bool is43 = Math.Abs(ratio - 4.0 / 3.0) < 0.01;
|
||||||
string image = is43 ? "song_progress_overlay_43.png" : "song_progress_overlay.png";
|
string image = is43 ? "song_progress_overlay_43.png" : "song_progress_overlay.png";
|
||||||
|
|
||||||
|
|||||||
@@ -98,10 +98,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
|||||||
// only check the requested playout if subtitles are enabled
|
// only check the requested playout if subtitles are enabled
|
||||||
Option<Playout> requestedPlayout = await dbContext.Playouts
|
Option<Playout> requestedPlayout = await dbContext.Playouts
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Filter(
|
.Filter(p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
|
||||||
p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
|
p.ProgramSchedule.Items.Any(psi =>
|
||||||
p.ProgramSchedule.Items.Any(
|
psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
|
||||||
psi => psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
|
|
||||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId.IfNone(-1));
|
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId.IfNone(-1));
|
||||||
|
|
||||||
playoutIdsToCheck.AddRange(requestedPlayout.Map(p => p.Id));
|
playoutIdsToCheck.AddRange(requestedPlayout.Map(p => p.Id));
|
||||||
@@ -111,10 +110,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
|||||||
{
|
{
|
||||||
playoutIdsToCheck = dbContext.Playouts
|
playoutIdsToCheck = dbContext.Playouts
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Filter(
|
.Filter(p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
|
||||||
p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
|
p.ProgramSchedule.Items.Any(psi =>
|
||||||
p.ProgramSchedule.Items.Any(
|
psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
|
||||||
psi => psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
|
|
||||||
.Map(p => p.Id)
|
.Map(p => p.Id)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@@ -216,10 +214,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
|||||||
List<int> episodeIds = await dbContext.EpisodeMetadata
|
List<int> episodeIds = await dbContext.EpisodeMetadata
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Filter(em => mediaItemIds.Contains(em.EpisodeId))
|
.Filter(em => mediaItemIds.Contains(em.EpisodeId))
|
||||||
.Filter(
|
.Filter(em => em.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||||
em => em.Subtitles.Any(
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" &&
|
||||||
s => s.SubtitleKind == SubtitleKind.Embedded &&
|
s.Codec != "dvdsub" &&
|
||||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
|
||||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
||||||
.Map(em => em.EpisodeId)
|
.Map(em => em.EpisodeId)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
@@ -228,10 +225,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
|||||||
List<int> movieIds = await dbContext.MovieMetadata
|
List<int> movieIds = await dbContext.MovieMetadata
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Filter(mm => mediaItemIds.Contains(mm.MovieId))
|
.Filter(mm => mediaItemIds.Contains(mm.MovieId))
|
||||||
.Filter(
|
.Filter(mm => mm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||||
mm => mm.Subtitles.Any(
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" &&
|
||||||
s => s.SubtitleKind == SubtitleKind.Embedded &&
|
s.Codec != "dvdsub" &&
|
||||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
|
||||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
||||||
.Map(mm => mm.MovieId)
|
.Map(mm => mm.MovieId)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
@@ -240,10 +236,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
|||||||
List<int> musicVideoIds = await dbContext.MusicVideoMetadata
|
List<int> musicVideoIds = await dbContext.MusicVideoMetadata
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Filter(mm => mediaItemIds.Contains(mm.MusicVideoId))
|
.Filter(mm => mediaItemIds.Contains(mm.MusicVideoId))
|
||||||
.Filter(
|
.Filter(mm => mm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||||
mm => mm.Subtitles.Any(
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" &&
|
||||||
s => s.SubtitleKind == SubtitleKind.Embedded &&
|
s.Codec != "dvdsub" &&
|
||||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
|
||||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
||||||
.Map(mm => mm.MusicVideoId)
|
.Map(mm => mm.MusicVideoId)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
@@ -252,10 +247,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
|||||||
List<int> otherVideoIds = await dbContext.OtherVideoMetadata
|
List<int> otherVideoIds = await dbContext.OtherVideoMetadata
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Filter(ovm => mediaItemIds.Contains(ovm.OtherVideoId))
|
.Filter(ovm => mediaItemIds.Contains(ovm.OtherVideoId))
|
||||||
.Filter(
|
.Filter(ovm => ovm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||||
ovm => ovm.Subtitles.Any(
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" &&
|
||||||
s => s.SubtitleKind == SubtitleKind.Embedded &&
|
s.Codec != "dvdsub" &&
|
||||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
|
||||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
||||||
.Map(ovm => ovm.OtherVideoId)
|
.Map(ovm => ovm.OtherVideoId)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
@@ -284,11 +278,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
|||||||
// find each subtitle that needs extraction
|
// find each subtitle that needs extraction
|
||||||
IEnumerable<Subtitle> subtitles = allSubtitles
|
IEnumerable<Subtitle> subtitles = allSubtitles
|
||||||
.Filter(s => s.SubtitleKind == SubtitleKind.Embedded)
|
.Filter(s => s.SubtitleKind == SubtitleKind.Embedded)
|
||||||
.Filter(
|
.Filter(s => s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
||||||
s => s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
|
||||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")
|
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")
|
||||||
.Filter(
|
.Filter(s => s.IsExtracted == false || string.IsNullOrWhiteSpace(s.Path) ||
|
||||||
s => s.IsExtracted == false || string.IsNullOrWhiteSpace(s.Path) ||
|
|
||||||
FileDoesntExist(mediaItem.Id, s));
|
FileDoesntExist(mediaItem.Id, s));
|
||||||
|
|
||||||
// find cache paths for each subtitle
|
// find cache paths for each subtitle
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using ErsatzTV.Application.MediaCards;
|
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Emby;
|
using ErsatzTV.Core.Emby;
|
||||||
using ErsatzTV.Core.Jellyfin;
|
using ErsatzTV.Core.Jellyfin;
|
||||||
@@ -23,17 +22,17 @@ internal static class Mapper
|
|||||||
show.ShowMetadata.HeadOrNone().Map(m => GetPoster(m, maybeJellyfin, maybeEmby)).IfNone(string.Empty),
|
show.ShowMetadata.HeadOrNone().Map(m => GetPoster(m, maybeJellyfin, maybeEmby)).IfNone(string.Empty),
|
||||||
show.ShowMetadata.HeadOrNone().Map(m => GetFanArt(m, maybeJellyfin, maybeEmby)).IfNone(string.Empty),
|
show.ShowMetadata.HeadOrNone().Map(m => GetFanArt(m, maybeJellyfin, maybeEmby)).IfNone(string.Empty),
|
||||||
show.ShowMetadata.HeadOrNone().Map(m => m.Genres.Map(g => g.Name).ToList()).IfNone([]),
|
show.ShowMetadata.HeadOrNone().Map(m => m.Genres.Map(g => g.Name).ToList()).IfNone([]),
|
||||||
show.ShowMetadata.HeadOrNone().Map(m => m.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(g => g.Name).ToList()).IfNone([]),
|
show.ShowMetadata.HeadOrNone().Map(m =>
|
||||||
|
m.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(g => g.Name).ToList()).IfNone([]),
|
||||||
show.ShowMetadata.HeadOrNone().Map(m => m.Studios.Map(s => s.Name).ToList()).IfNone([]),
|
show.ShowMetadata.HeadOrNone().Map(m => m.Studios.Map(s => s.Name).ToList()).IfNone([]),
|
||||||
show.ShowMetadata.HeadOrNone().Map(m => m.Tags.Where(t => t.ExternalTypeId == Tag.PlexNetworkTypeId).Map(g => g.Name).ToList()).IfNone([]),
|
show.ShowMetadata.HeadOrNone().Map(m =>
|
||||||
|
m.Tags.Where(t => t.ExternalTypeId == Tag.PlexNetworkTypeId).Map(g => g.Name).ToList()).IfNone([]),
|
||||||
show.ShowMetadata.HeadOrNone()
|
show.ShowMetadata.HeadOrNone()
|
||||||
.Map(
|
.Map(m => (m.ContentRating ?? string.Empty).Split("/").Map(s => s.Trim())
|
||||||
m => (m.ContentRating ?? string.Empty).Split("/").Map(s => s.Trim())
|
|
||||||
.Where(x => !string.IsNullOrWhiteSpace(x)).ToList()).IfNone([]),
|
.Where(x => !string.IsNullOrWhiteSpace(x)).ToList()).IfNone([]),
|
||||||
LanguagesForShow(languages),
|
LanguagesForShow(languages),
|
||||||
show.ShowMetadata.HeadOrNone()
|
show.ShowMetadata.HeadOrNone()
|
||||||
.Map(
|
.Map(m => m.Actors.OrderBy(a => a.Order).ThenBy(a => a.Id)
|
||||||
m => m.Actors.OrderBy(a => a.Order).ThenBy(a => a.Id)
|
|
||||||
.Map(a => MediaCards.Mapper.ProjectToViewModel(a, maybeJellyfin, maybeEmby))
|
.Map(a => MediaCards.Mapper.ProjectToViewModel(a, maybeJellyfin, maybeEmby))
|
||||||
.ToList())
|
.ToList())
|
||||||
.IfNone([]));
|
.IfNone([]));
|
||||||
@@ -104,9 +103,10 @@ internal static class Mapper
|
|||||||
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
||||||
|
|
||||||
return languages
|
return languages
|
||||||
.Map(
|
.Map(lang => allCultures.Filter(ci => string.Equals(
|
||||||
lang => allCultures.Filter(
|
ci.ThreeLetterISOLanguageName,
|
||||||
ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase)))
|
lang,
|
||||||
|
StringComparison.OrdinalIgnoreCase)))
|
||||||
.Flatten()
|
.Flatten()
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ public class CreateWatermarkHandler : IRequestHandler<CreateWatermark, Either<Ba
|
|||||||
|
|
||||||
private static Validation<BaseError, ChannelWatermark> Validate(CreateWatermark request) =>
|
private static Validation<BaseError, ChannelWatermark> Validate(CreateWatermark request) =>
|
||||||
ValidateName(request)
|
ValidateName(request)
|
||||||
.Map(
|
.Map(_ =>
|
||||||
_ =>
|
|
||||||
{
|
{
|
||||||
var watermark = new ChannelWatermark
|
var watermark = new ChannelWatermark
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using Shouldly;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.Domain;
|
namespace ErsatzTV.Core.Tests.Domain;
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ using ErsatzTV.Core.Domain;
|
|||||||
using ErsatzTV.Core.Emby;
|
using ErsatzTV.Core.Emby;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.FFmpeg.Runtime;
|
using ErsatzTV.FFmpeg.Runtime;
|
||||||
using Shouldly;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.Emby;
|
namespace ErsatzTV.Core.Tests.Emby;
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,6 @@ public class CustomStreamSelectorTests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class SelectStreams
|
public class SelectStreams
|
||||||
{
|
{
|
||||||
private static readonly string TestFileName = Path.Combine(FileSystemLayout.ChannelStreamSelectorsFolder, "test.yml");
|
|
||||||
|
|
||||||
private Channel _channel;
|
|
||||||
private MediaItemAudioVersion _audioVersion;
|
|
||||||
private List<Subtitle> _subtitles;
|
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
@@ -41,16 +35,24 @@ public class CustomStreamSelectorTests
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly string TestFileName = Path.Combine(
|
||||||
|
FileSystemLayout.ChannelStreamSelectorsFolder,
|
||||||
|
"test.yml");
|
||||||
|
|
||||||
|
private Channel _channel;
|
||||||
|
private MediaItemAudioVersion _audioVersion;
|
||||||
|
private List<Subtitle> _subtitles;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Should_Select_eng_Audio_Exact_Match()
|
public async Task Should_Select_eng_Audio_Exact_Match()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "eng"
|
- "eng"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -71,11 +73,11 @@ items:
|
|||||||
public async Task Should_Select_und_Audio_Missing_Language()
|
public async Task Should_Select_und_Audio_Missing_Language()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language: ["und"]
|
- audio_language: ["und"]
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -96,11 +98,11 @@ items:
|
|||||||
public async Task Should_Select_eng_Audio_Exact_Match_Multiple_Audio_Languages()
|
public async Task Should_Select_eng_Audio_Exact_Match_Multiple_Audio_Languages()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language: ["en", "eng"]
|
- audio_language: ["en", "eng"]
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -121,9 +123,9 @@ items:
|
|||||||
public async Task Should_Select_eng_Audio_Exact_Match_Multiple_Items()
|
public async Task Should_Select_eng_Audio_Exact_Match_Multiple_Items()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "de"
|
- "de"
|
||||||
subtitle_language:
|
subtitle_language:
|
||||||
@@ -131,7 +133,7 @@ items:
|
|||||||
- audio_language:
|
- audio_language:
|
||||||
- "eng"
|
- "eng"
|
||||||
disable_subtitles: true
|
disable_subtitles: true
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -152,12 +154,12 @@ items:
|
|||||||
public async Task Should_Select_eng_Audio_Pattern_Match()
|
public async Task Should_Select_eng_Audio_Pattern_Match()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "en*"
|
- "en*"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -178,12 +180,12 @@ items:
|
|||||||
public async Task Should_Select_en_Audio_Pattern_Match()
|
public async Task Should_Select_en_Audio_Pattern_Match()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "en*"
|
- "en*"
|
||||||
""";
|
""";
|
||||||
_audioVersion = GetTestAudioVersion("en");
|
_audioVersion = GetTestAudioVersion("en");
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
@@ -205,13 +207,13 @@ items:
|
|||||||
public async Task disable_subtitles_Should_Select_No_Subtitles()
|
public async Task disable_subtitles_Should_Select_No_Subtitles()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "eng"
|
- "eng"
|
||||||
disable_subtitles: true
|
disable_subtitles: true
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -226,14 +228,14 @@ items:
|
|||||||
public async Task Should_Select_eng_Subtitle_Exact_Match()
|
public async Task Should_Select_eng_Subtitle_Exact_Match()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "ja"
|
- "ja"
|
||||||
subtitle_language:
|
subtitle_language:
|
||||||
- "eng"
|
- "eng"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -254,14 +256,14 @@ items:
|
|||||||
public async Task Should_Select_eng_Subtitle_Pattern_Match()
|
public async Task Should_Select_eng_Subtitle_Pattern_Match()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "ja"
|
- "ja"
|
||||||
subtitle_language:
|
subtitle_language:
|
||||||
- "en*"
|
- "en*"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -282,14 +284,14 @@ items:
|
|||||||
public async Task Should_Select_en_Subtitle_Pattern_Match()
|
public async Task Should_Select_en_Subtitle_Pattern_Match()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "ja"
|
- "ja"
|
||||||
subtitle_language:
|
subtitle_language:
|
||||||
- "en*"
|
- "en*"
|
||||||
""";
|
""";
|
||||||
_audioVersion = GetTestAudioVersion("en");
|
_audioVersion = GetTestAudioVersion("en");
|
||||||
|
|
||||||
_subtitles =
|
_subtitles =
|
||||||
@@ -316,9 +318,9 @@ items:
|
|||||||
public async Task Should_Select_No_Subtitle_Exact_Match_Multiple_Items()
|
public async Task Should_Select_No_Subtitle_Exact_Match_Multiple_Items()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "de"
|
- "de"
|
||||||
subtitle_language:
|
subtitle_language:
|
||||||
@@ -326,7 +328,7 @@ items:
|
|||||||
- audio_language:
|
- audio_language:
|
||||||
- "eng"
|
- "eng"
|
||||||
disable_subtitles: true
|
disable_subtitles: true
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -349,9 +351,9 @@ items:
|
|||||||
public async Task Should_Select_Foreign_Audio_And_English_Subtitle_Multiple_Items()
|
public async Task Should_Select_Foreign_Audio_And_English_Subtitle_Multiple_Items()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "ja"
|
- "ja"
|
||||||
subtitle_language:
|
subtitle_language:
|
||||||
@@ -359,7 +361,7 @@ items:
|
|||||||
- audio_language:
|
- audio_language:
|
||||||
- "eng"
|
- "eng"
|
||||||
disable_subtitles: true
|
disable_subtitles: true
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -388,14 +390,14 @@ items:
|
|||||||
public async Task Should_Ignore_Blocked_Audio_Title()
|
public async Task Should_Ignore_Blocked_Audio_Title()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "en*"
|
- "en*"
|
||||||
audio_title_blocklist:
|
audio_title_blocklist:
|
||||||
- "riff"
|
- "riff"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -416,14 +418,14 @@ items:
|
|||||||
public async Task Should_Select_Allowed_Audio_Title()
|
public async Task Should_Select_Allowed_Audio_Title()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "en*"
|
- "en*"
|
||||||
audio_title_allowlist:
|
audio_title_allowlist:
|
||||||
- "movie"
|
- "movie"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -444,16 +446,16 @@ items:
|
|||||||
public async Task Should_Ignore_Blocked_Subtitle_Title()
|
public async Task Should_Ignore_Blocked_Subtitle_Title()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "*"
|
- "*"
|
||||||
subtitle_language:
|
subtitle_language:
|
||||||
- "en"
|
- "en"
|
||||||
subtitle_title_blocklist:
|
subtitle_title_blocklist:
|
||||||
- "signs"
|
- "signs"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -474,16 +476,16 @@ items:
|
|||||||
public async Task Should_Select_Allowed_Subtitle_Title()
|
public async Task Should_Select_Allowed_Subtitle_Title()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "*"
|
- "*"
|
||||||
subtitle_language:
|
subtitle_language:
|
||||||
- "en"
|
- "en"
|
||||||
subtitle_title_allowlist:
|
subtitle_title_allowlist:
|
||||||
- "songs"
|
- "songs"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -504,13 +506,13 @@ items:
|
|||||||
public async Task Should_Select_Condition_Forced_Subtitle()
|
public async Task Should_Select_Condition_Forced_Subtitle()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "*"
|
- "*"
|
||||||
subtitle_condition: "forced"
|
subtitle_condition: "forced"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -531,13 +533,13 @@ items:
|
|||||||
public async Task Should_Select_Condition_External_Subtitle()
|
public async Task Should_Select_Condition_External_Subtitle()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "*"
|
- "*"
|
||||||
subtitle_condition: "lang like 'en%' and external"
|
subtitle_condition: "lang like 'en%' and external"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -558,13 +560,13 @@ items:
|
|||||||
public async Task Should_Select_Condition_Audio_Title()
|
public async Task Should_Select_Condition_Audio_Title()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "en*"
|
- "en*"
|
||||||
audio_condition: "title like '%movie%'"
|
audio_condition: "title like '%movie%'"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -585,13 +587,13 @@ items:
|
|||||||
public async Task Should_Select_Condition_Audio_Channels()
|
public async Task Should_Select_Condition_Audio_Channels()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "en*"
|
- "en*"
|
||||||
audio_condition: "channels > 2"
|
audio_condition: "channels > 2"
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -612,12 +614,12 @@ items:
|
|||||||
public async Task Should_Select_Prioritized_Audio_Language()
|
public async Task Should_Select_Prioritized_Audio_Language()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language: ["en*","ja"]
|
- audio_language: ["en*","ja"]
|
||||||
audio_title_blocklist: ["riff"]
|
audio_title_blocklist: ["riff"]
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -638,13 +640,13 @@ items:
|
|||||||
public async Task Should_Select_Prioritized_Subtitle_Language()
|
public async Task Should_Select_Prioritized_Subtitle_Language()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language:
|
- audio_language:
|
||||||
- "*"
|
- "*"
|
||||||
subtitle_language: ["jp","en*"]
|
subtitle_language: ["jp","en*"]
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -665,14 +667,14 @@ items:
|
|||||||
public async Task Should_Select_No_Streams_When_Languages_Do_Not_Match()
|
public async Task Should_Select_No_Streams_When_Languages_Do_Not_Match()
|
||||||
{
|
{
|
||||||
const string YAML =
|
const string YAML =
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
items:
|
items:
|
||||||
- audio_language: ["en"]
|
- audio_language: ["en"]
|
||||||
subtitle_language: ["es*","de*"]
|
subtitle_language: ["es*","de*"]
|
||||||
- audio_language: ["ja"]
|
- audio_language: ["ja"]
|
||||||
subtitle_language: ["es*","de*"]
|
subtitle_language: ["es*","de*"]
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var streamSelector = new CustomStreamSelector(
|
var streamSelector = new CustomStreamSelector(
|
||||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||||
@@ -697,7 +699,7 @@ items:
|
|||||||
MediaStreamKind = MediaStreamKind.Audio,
|
MediaStreamKind = MediaStreamKind.Audio,
|
||||||
Channels = 2,
|
Channels = 2,
|
||||||
Language = "ja",
|
Language = "ja",
|
||||||
Title = "Some Title",
|
Title = "Some Title"
|
||||||
},
|
},
|
||||||
new MediaStream
|
new MediaStream
|
||||||
{
|
{
|
||||||
@@ -714,14 +716,14 @@ items:
|
|||||||
MediaStreamKind = MediaStreamKind.Audio,
|
MediaStreamKind = MediaStreamKind.Audio,
|
||||||
Channels = 6,
|
Channels = 6,
|
||||||
Language = englishLanguage,
|
Language = englishLanguage,
|
||||||
Title = "Movie Title",
|
Title = "Movie Title"
|
||||||
},
|
},
|
||||||
new MediaStream
|
new MediaStream
|
||||||
{
|
{
|
||||||
Index = 3,
|
Index = 3,
|
||||||
MediaStreamKind = MediaStreamKind.Audio,
|
MediaStreamKind = MediaStreamKind.Audio,
|
||||||
Channels = 2,
|
Channels = 2,
|
||||||
Title = "Who Knows",
|
Title = "Who Knows"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.FFmpeg;
|
using ErsatzTV.Core.FFmpeg;
|
||||||
using ErsatzTV.Core.Interfaces.FFmpeg;
|
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||||
using Shouldly;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ using ErsatzTV.Core.FFmpeg;
|
|||||||
using ErsatzTV.Core.Interfaces.Metadata;
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Infrastructure.Scripting;
|
using ErsatzTV.Infrastructure.Scripting;
|
||||||
using Shouldly;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ public class FFmpegStreamSelectorTests
|
|||||||
MediaStreamKind = MediaStreamKind.Audio,
|
MediaStreamKind = MediaStreamKind.Audio,
|
||||||
Channels = 2,
|
Channels = 2,
|
||||||
Language = "ja",
|
Language = "ja",
|
||||||
Title = "Some Title",
|
Title = "Some Title"
|
||||||
},
|
},
|
||||||
new MediaStream
|
new MediaStream
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,12 @@ public class FFmpegStreamSelectorTests
|
|||||||
Substitute.For<ILocalFileSystem>(),
|
Substitute.For<ILocalFileSystem>(),
|
||||||
Substitute.For<ILogger<FFmpegStreamSelector>>());
|
Substitute.For<ILogger<FFmpegStreamSelector>>());
|
||||||
|
|
||||||
Option<MediaStream> selectedStream = await selector.SelectAudioStream(audioVersion, StreamingMode.TransportStream, channel, "jpn", "Whatever");
|
Option<MediaStream> selectedStream = await selector.SelectAudioStream(
|
||||||
|
audioVersion,
|
||||||
|
StreamingMode.TransportStream,
|
||||||
|
channel,
|
||||||
|
"jpn",
|
||||||
|
"Whatever");
|
||||||
selectedStream.IsSome.ShouldBeTrue();
|
selectedStream.IsSome.ShouldBeTrue();
|
||||||
foreach (MediaStream stream in selectedStream)
|
foreach (MediaStream stream in selectedStream)
|
||||||
{
|
{
|
||||||
@@ -86,7 +91,7 @@ public class FFmpegStreamSelectorTests
|
|||||||
MediaStreamKind = MediaStreamKind.Audio,
|
MediaStreamKind = MediaStreamKind.Audio,
|
||||||
Channels = 2,
|
Channels = 2,
|
||||||
Language = "ja",
|
Language = "ja",
|
||||||
Title = "Some Title",
|
Title = "Some Title"
|
||||||
},
|
},
|
||||||
new MediaStream
|
new MediaStream
|
||||||
{
|
{
|
||||||
@@ -118,7 +123,12 @@ public class FFmpegStreamSelectorTests
|
|||||||
Substitute.For<ILocalFileSystem>(),
|
Substitute.For<ILocalFileSystem>(),
|
||||||
Substitute.For<ILogger<FFmpegStreamSelector>>());
|
Substitute.For<ILogger<FFmpegStreamSelector>>());
|
||||||
|
|
||||||
Option<MediaStream> selectedStream = await selector.SelectAudioStream(audioVersion, StreamingMode.TransportStream, channel, null, channel.PreferredAudioTitle);
|
Option<MediaStream> selectedStream = await selector.SelectAudioStream(
|
||||||
|
audioVersion,
|
||||||
|
StreamingMode.TransportStream,
|
||||||
|
channel,
|
||||||
|
null,
|
||||||
|
channel.PreferredAudioTitle);
|
||||||
selectedStream.IsSome.ShouldBeTrue();
|
selectedStream.IsSome.ShouldBeTrue();
|
||||||
foreach (MediaStream stream in selectedStream)
|
foreach (MediaStream stream in selectedStream)
|
||||||
{
|
{
|
||||||
@@ -143,8 +153,8 @@ public class FFmpegStreamSelectorTests
|
|||||||
{
|
{
|
||||||
StreamIndex = 1,
|
StreamIndex = 1,
|
||||||
SubtitleKind = SubtitleKind.Sidecar,
|
SubtitleKind = SubtitleKind.Sidecar,
|
||||||
Language = "he",
|
Language = "he"
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var channel = new Channel(Guid.NewGuid());
|
var channel = new Channel(Guid.NewGuid());
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using ErsatzTV.Core.FFmpeg;
|
using ErsatzTV.Core.FFmpeg;
|
||||||
using ErsatzTV.Core.Interfaces.FFmpeg;
|
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||||
using Shouldly;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using ErsatzTV.Core.FFmpeg;
|
using ErsatzTV.Core.FFmpeg;
|
||||||
using Shouldly;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository
|
|||||||
public Task<List<MediaItem>> GetMultiCollectionItemsByName(string name) => throw new NotSupportedException();
|
public Task<List<MediaItem>> GetMultiCollectionItemsByName(string name) => throw new NotSupportedException();
|
||||||
public Task<List<MediaItem>> GetSmartCollectionItems(int id) => _data[id].ToList().AsTask();
|
public Task<List<MediaItem>> GetSmartCollectionItems(int id) => _data[id].ToList().AsTask();
|
||||||
public Task<List<MediaItem>> GetSmartCollectionItemsByName(string name) => throw new NotSupportedException();
|
public Task<List<MediaItem>> GetSmartCollectionItemsByName(string name) => throw new NotSupportedException();
|
||||||
public Task<List<MediaItem>> GetSmartCollectionItems(string query, string smartCollectionName) => throw new NotSupportedException();
|
|
||||||
|
public Task<List<MediaItem>> GetSmartCollectionItems(string query, string smartCollectionName) =>
|
||||||
|
throw new NotSupportedException();
|
||||||
|
|
||||||
public Task<List<MediaItem>> GetShowItemsByShowGuids(List<string> guids) => throw new NotSupportedException();
|
public Task<List<MediaItem>> GetShowItemsByShowGuids(List<string> guids) => throw new NotSupportedException();
|
||||||
public Task<List<MediaItem>> GetPlaylistItems(int id) => throw new NotSupportedException();
|
public Task<List<MediaItem>> GetPlaylistItems(int id) => throw new NotSupportedException();
|
||||||
public Task<List<Movie>> GetMovie(int id) => throw new NotSupportedException();
|
public Task<List<Movie>> GetMovie(int id) => throw new NotSupportedException();
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class FakeTelevisionRepository : ITelevisionRepository
|
|||||||
public Task<bool> AddGenre(ShowMetadata metadata, Genre genre) => throw new NotSupportedException();
|
public Task<bool> AddGenre(ShowMetadata metadata, Genre genre) => throw new NotSupportedException();
|
||||||
public Task<bool> AddGenre(EpisodeMetadata metadata, Genre genre) => throw new NotSupportedException();
|
public Task<bool> AddGenre(EpisodeMetadata metadata, Genre genre) => throw new NotSupportedException();
|
||||||
|
|
||||||
public Task<bool> AddTag(ErsatzTV.Core.Domain.Metadata metadata, Tag tag) => throw new NotSupportedException();
|
public Task<bool> AddTag(Core.Domain.Metadata metadata, Tag tag) => throw new NotSupportedException();
|
||||||
|
|
||||||
public Task<bool> AddStudio(ShowMetadata metadata, Studio studio) => throw new NotSupportedException();
|
public Task<bool> AddStudio(ShowMetadata metadata, Studio studio) => throw new NotSupportedException();
|
||||||
public Task<bool> AddActor(ShowMetadata metadata, Actor actor) => throw new NotSupportedException();
|
public Task<bool> AddActor(ShowMetadata metadata, Actor actor) => throw new NotSupportedException();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using ErsatzTV.Core.Iptv;
|
using ErsatzTV.Core.Iptv;
|
||||||
using Shouldly;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.Iptv;
|
namespace ErsatzTV.Core.Tests.Iptv;
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user