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,
|
||||
"tools": {
|
||||
"jetbrains.resharper.globaltools": {
|
||||
"version": "2024.1.1",
|
||||
"version": "2025.1.4",
|
||||
"commands": [
|
||||
"jb"
|
||||
]
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,10 @@ internal static class Mapper
|
||||
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
||||
|
||||
return languages
|
||||
.Map(
|
||||
lang => allCultures.Filter(
|
||||
ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase)))
|
||||
.Map(lang => allCultures.Filter(ci => string.Equals(
|
||||
ci.ThreeLetterISOLanguageName,
|
||||
lang,
|
||||
StringComparison.OrdinalIgnoreCase)))
|
||||
.Flatten()
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
@@ -6,7 +6,8 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
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;
|
||||
|
||||
@@ -14,7 +15,8 @@ public class GetArtworkHandler(IDbContextFactory<TvContext> dbContextFactory) :
|
||||
GetArtwork request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
Option<Artwork> artwork = await dbContext.Artwork
|
||||
@@ -23,7 +25,6 @@ public class GetArtworkHandler(IDbContextFactory<TvContext> dbContextFactory) :
|
||||
.MapT(Project);
|
||||
|
||||
return artwork.ToEither(BaseError.New("Artwork not found"));
|
||||
|
||||
}
|
||||
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,
|
||||
Path = artwork.Path,
|
||||
ArtworkKind = artwork.ArtworkKind
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using System.Net;
|
||||
using ErsatzTV.Application.Artworks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.Channels;
|
||||
|
||||
|
||||
@@ -40,8 +40,7 @@ public class CreateChannelHandler(
|
||||
await FFmpegProfileMustExist(dbContext, request),
|
||||
await WatermarkMustExist(dbContext, request),
|
||||
await FillerPresetMustExist(dbContext, request))
|
||||
.Apply(
|
||||
(
|
||||
.Apply((
|
||||
name,
|
||||
number,
|
||||
ffmpegProfileId,
|
||||
@@ -62,7 +61,9 @@ public class CreateChannelHandler(
|
||||
{
|
||||
Path = 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,
|
||||
DateUpdated = DateTime.UtcNow
|
||||
});
|
||||
@@ -168,8 +169,7 @@ public class CreateChannelHandler(
|
||||
.Map(Optional)
|
||||
.Filter(c => c > 0)
|
||||
.MapT(_ => Optional(createChannel.FallbackFillerId))
|
||||
.Map(
|
||||
o => o.ToValidation<BaseError>(
|
||||
.Map(o => o.ToValidation<BaseError>(
|
||||
$"Fallback filler {createChannel.FallbackFillerId} does not exist."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
int finishIndex = j;
|
||||
while (finishIndex + 1 < sorted.Count && (sorted[finishIndex + 1].GuideGroup == startItem.GuideGroup
|
||||
|| 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++;
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ public class GetChannelLineupHandler : IRequestHandler<GetChannelLineup, List<Li
|
||||
|
||||
public Task<List<LineupItem>> Handle(GetChannelLineup request, CancellationToken cancellationToken) =>
|
||||
_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) =>
|
||||
_channelRepository.GetAll()
|
||||
.Map(channels => EnsureMode(channels, request.Mode))
|
||||
.Map(
|
||||
channels => new ChannelPlaylist(
|
||||
.Map(channels => new ChannelPlaylist(
|
||||
request.Scheme,
|
||||
request.Host,
|
||||
request.BaseUrl,
|
||||
|
||||
@@ -16,8 +16,7 @@ public class UpdateLibraryRefreshIntervalHandler :
|
||||
UpdateLibraryRefreshInterval request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(
|
||||
_ => _configElementRepository.Upsert(
|
||||
.MapT(_ => _configElementRepository.Upsert(
|
||||
ConfigElementKey.LibraryRefreshInterval,
|
||||
request.LibraryRefreshInterval))
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
@@ -43,7 +43,6 @@ public class UpdateEmbyPathReplacementsHandler : IRequestHandler<UpdateEmbyPathR
|
||||
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist(
|
||||
UpdateEmbyPathReplacements request) =>
|
||||
_mediaSourceRepository.GetEmby(request.EmbyMediaSourceId)
|
||||
.Map(
|
||||
v => v.ToValidation<BaseError>(
|
||||
.Map(v => v.ToValidation<BaseError>(
|
||||
$"Emby media source {request.EmbyMediaSourceId} does not exist."));
|
||||
}
|
||||
|
||||
@@ -54,8 +54,7 @@ public class GetEmbyConnectionParametersHandler : IRequestHandler<GetEmbyConnect
|
||||
|
||||
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist() =>
|
||||
_mediaSourceRepository.GetAllEmby().Map(list => list.HeadOrNone())
|
||||
.Map(
|
||||
v => v.ToValidation<BaseError>(
|
||||
.Map(v => v.ToValidation<BaseError>(
|
||||
"Emby media source does not exist."));
|
||||
|
||||
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<PackageReference Include="Bugsnag" Version="4.0.0" />
|
||||
<PackageReference Include="CliWrap" Version="3.9.0" />
|
||||
<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.Hosting.Abstractions" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.15">
|
||||
|
||||
@@ -42,8 +42,7 @@ public class CreateFFmpegProfileHandler :
|
||||
TvContext dbContext,
|
||||
CreateFFmpegProfile request) =>
|
||||
(ValidateName(request), ValidateThreadCount(request), await ResolutionMustExist(dbContext, request))
|
||||
.Apply(
|
||||
(name, threadCount, resolutionId) => new FFmpegProfile
|
||||
.Apply((name, threadCount, resolutionId) => new FFmpegProfile
|
||||
{
|
||||
Name = name,
|
||||
ThreadCount = threadCount,
|
||||
|
||||
@@ -16,8 +16,7 @@ public class UpdateHDHRTunerCountHandler : IRequestHandler<UpdateHDHRTunerCount,
|
||||
UpdateHDHRTunerCount request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(
|
||||
_ => _configElementRepository.Upsert(
|
||||
.MapT(_ => _configElementRepository.Upsert(
|
||||
ConfigElementKey.HDHRTunerCount,
|
||||
request.TunerCount.ToString(CultureInfo.InvariantCulture)))
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
@@ -13,10 +13,9 @@ public class GetHDHRUUIDHandler : IRequestHandler<GetHDHRUUID, Guid>
|
||||
public async Task<Guid> Handle(GetHDHRUUID request, CancellationToken cancellationToken)
|
||||
{
|
||||
Option<Guid> maybeGuid = await _configElementRepository.GetValue<Guid>(ConfigElementKey.HDHRUUID);
|
||||
return await maybeGuid.IfNoneAsync(
|
||||
async () =>
|
||||
return await maybeGuid.IfNoneAsync(async () =>
|
||||
{
|
||||
Guid guid = Guid.NewGuid();
|
||||
var guid = Guid.NewGuid();
|
||||
await _configElementRepository.Upsert(ConfigElementKey.HDHRUUID, guid);
|
||||
return guid;
|
||||
});
|
||||
|
||||
@@ -4,4 +4,5 @@ using ErsatzTV.Core.Domain;
|
||||
namespace ErsatzTV.Application.Images;
|
||||
|
||||
// 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
|
||||
await dbContext.ImageMetadata
|
||||
.Filter(
|
||||
im => im.Image.MediaVersions.Any(
|
||||
mv => mv.MediaFiles.Any(mf => mf.LibraryFolderId == currentFolder.Id)))
|
||||
.Filter(im =>
|
||||
im.Image.MediaVersions.Any(mv => mv.MediaFiles.Any(mf => mf.LibraryFolderId == currentFolder.Id)))
|
||||
.ExecuteUpdateAsync(
|
||||
setters => setters.SetProperty(im => im.DurationSeconds, effectiveDuration),
|
||||
cancellationToken);
|
||||
|
||||
@@ -3,5 +3,6 @@ using ErsatzTV.Core.Domain;
|
||||
|
||||
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>>;
|
||||
|
||||
@@ -43,7 +43,6 @@ public class UpdateJellyfinPathReplacementsHandler : IRequestHandler<UpdateJelly
|
||||
private Task<Validation<BaseError, JellyfinMediaSource>> JellyfinMediaSourceMustExist(
|
||||
UpdateJellyfinPathReplacements request) =>
|
||||
_mediaSourceRepository.GetJellyfin(request.JellyfinMediaSourceId)
|
||||
.Map(
|
||||
v => v.ToValidation<BaseError>(
|
||||
.Map(v => v.ToValidation<BaseError>(
|
||||
$"Jellyfin media source {request.JellyfinMediaSourceId} does not exist."));
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ public class GetJellyfinConnectionParametersHandler : IRequestHandler<GetJellyfi
|
||||
|
||||
private Task<Validation<BaseError, JellyfinMediaSource>> JellyfinMediaSourceMustExist() =>
|
||||
_mediaSourceRepository.GetAllJellyfin().Map(list => list.HeadOrNone())
|
||||
.Map(
|
||||
v => v.ToValidation<BaseError>(
|
||||
.Map(v => v.ToValidation<BaseError>(
|
||||
"Jellyfin media source does not exist."));
|
||||
|
||||
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
|
||||
|
||||
@@ -54,8 +54,8 @@ public abstract class CallLibraryScannerHandler<TRequest>
|
||||
{
|
||||
using var forcefulCts = new CancellationTokenSource();
|
||||
|
||||
await using CancellationTokenRegistration link = cancellationToken.Register(
|
||||
() => forcefulCts.CancelAfter(TimeSpan.FromSeconds(10))
|
||||
await using CancellationTokenRegistration link =
|
||||
cancellationToken.Register(() => forcefulCts.CancelAfter(TimeSpan.FromSeconds(10))
|
||||
);
|
||||
|
||||
CommandResult process = await Cli.Wrap(scanner)
|
||||
|
||||
@@ -64,8 +64,7 @@ public class CreateLocalLibraryHandler : LocalLibraryHandlerBase,
|
||||
.OrderBy(lms => lms.Id)
|
||||
.FirstOrDefaultAsync()
|
||||
.Map(Optional)
|
||||
.MapT(
|
||||
lms => new LocalLibrary
|
||||
.MapT(lms => new LocalLibrary
|
||||
{
|
||||
Name = request.Name,
|
||||
Paths = request.Paths.Map(p => new LibraryPath { Path = p }).ToList(),
|
||||
|
||||
@@ -44,10 +44,10 @@ public abstract class LocalLibraryHandlerBase
|
||||
// Images and OtherVideos do not conflict
|
||||
if (isConflict)
|
||||
{
|
||||
bool imagesAndOtherVideos = (path1.MediaKind is LibraryMediaKind.Images &&
|
||||
path2.MediaKind is LibraryMediaKind.OtherVideos)
|
||||
|| (path2.MediaKind is LibraryMediaKind.Images &&
|
||||
path1.MediaKind is LibraryMediaKind.OtherVideos);
|
||||
bool imagesAndOtherVideos = path1.MediaKind is LibraryMediaKind.Images &&
|
||||
path2.MediaKind is LibraryMediaKind.OtherVideos
|
||||
|| path2.MediaKind is LibraryMediaKind.Images &&
|
||||
path1.MediaKind is LibraryMediaKind.OtherVideos;
|
||||
|
||||
if (imagesAndOtherVideos)
|
||||
{
|
||||
|
||||
@@ -102,8 +102,7 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase,
|
||||
UpdateLocalLibrary request) =>
|
||||
LocalLibraryMustExist(dbContext, request)
|
||||
.BindT(parameters => NameMustBeValid(request, parameters.Incoming).MapT(_ => parameters))
|
||||
.BindT(
|
||||
parameters => PathsMustBeValid(dbContext, parameters.Incoming, parameters.Existing.Id)
|
||||
.BindT(parameters => PathsMustBeValid(dbContext, parameters.Incoming, parameters.Existing.Id)
|
||||
.MapT(_ => parameters));
|
||||
|
||||
private static Task<Validation<BaseError, Parameters>> LocalLibraryMustExist(
|
||||
@@ -112,8 +111,7 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase,
|
||||
dbContext.LocalLibraries
|
||||
.Include(ll => ll.Paths)
|
||||
.SelectOneAsync(ll => ll.Id, ll => ll.Id == request.Id)
|
||||
.MapT(
|
||||
existing =>
|
||||
.MapT(existing =>
|
||||
{
|
||||
var incoming = new LocalLibrary
|
||||
{
|
||||
|
||||
@@ -14,8 +14,7 @@ public class GetAllLocalLibrariesHandler : IRequestHandler<GetAllLocalLibraries,
|
||||
GetAllLocalLibraries request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_libraryRepository.GetAll()
|
||||
.Map(
|
||||
list => list
|
||||
.Map(list => list
|
||||
.OfType<LocalLibrary>()
|
||||
.OrderBy(l => l.MediaKind)
|
||||
.Map(ProjectToViewModel)
|
||||
|
||||
@@ -15,8 +15,7 @@ public class GetConfiguredLibrariesHandler : IRequestHandler<GetConfiguredLibrar
|
||||
GetConfiguredLibraries request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_libraryRepository.GetAll()
|
||||
.Map(
|
||||
list => list.Filter(ShouldIncludeLibrary)
|
||||
.Map(list => list.Filter(ShouldIncludeLibrary)
|
||||
.OrderBy(l => l.MediaSource is LocalMediaSource ? 0 : 1)
|
||||
.ThenBy(l => l.GetType().Name)
|
||||
.ThenBy(l => l.MediaKind)
|
||||
|
||||
@@ -46,8 +46,13 @@ public class GetExternalCollectionsHandler : IRequestHandler<GetExternalCollecti
|
||||
.Map(jms => jms.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return jellyfinMediaSourceIds.Map(
|
||||
id => new LibraryViewModel("Jellyfin", 0, "Collections", 0, id, string.Empty));
|
||||
return jellyfinMediaSourceIds.Map(id => new LibraryViewModel(
|
||||
"Jellyfin",
|
||||
0,
|
||||
"Collections",
|
||||
0,
|
||||
id,
|
||||
string.Empty));
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<LibraryViewModel>> GetPlexExternalCollections(
|
||||
@@ -59,7 +64,6 @@ public class GetExternalCollectionsHandler : IRequestHandler<GetExternalCollecti
|
||||
.Map(pms => pms.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return plexMediaSourceIds.Map(
|
||||
id => new LibraryViewModel("Plex", 0, "Collections", 0, id, string.Empty));
|
||||
return plexMediaSourceIds.Map(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))
|
||||
{
|
||||
entries = entries.Filter(
|
||||
le => le.Level.ToString().Contains(request.Filter, StringComparison.OrdinalIgnoreCase) ||
|
||||
entries = entries.Filter(le =>
|
||||
le.Level.ToString().Contains(request.Filter, StringComparison.OrdinalIgnoreCase) ||
|
||||
le.Message.Contains(request.Filter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
|
||||
@@ -171,8 +171,8 @@ internal static class Mapper
|
||||
Option<EmbyMediaSource> maybeEmby) =>
|
||||
new(
|
||||
collection.Name,
|
||||
collection.MediaItems.OfType<Movie>().Map(
|
||||
m => ProjectToViewModel(m.MovieMetadata.Head(), maybeJellyfin, maybeEmby) with
|
||||
collection.MediaItems.OfType<Movie>().Map(m =>
|
||||
ProjectToViewModel(m.MovieMetadata.Head(), maybeJellyfin, maybeEmby) with
|
||||
{
|
||||
CustomIndex = GetCustomIndex(collection, m.Id)
|
||||
}).ToList(),
|
||||
@@ -183,8 +183,7 @@ internal static class Mapper
|
||||
.ToList(),
|
||||
// collection view doesn't use local paths
|
||||
collection.MediaItems.OfType<Episode>()
|
||||
.Map(
|
||||
e => ProjectToViewModel(
|
||||
.Map(e => ProjectToViewModel(
|
||||
e.EpisodeMetadata.Head(),
|
||||
maybeJellyfin,
|
||||
maybeEmby,
|
||||
|
||||
@@ -15,9 +15,9 @@ public class AddArtistToCollectionHandler :
|
||||
IRequestHandler<AddArtistToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddArtistToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -15,9 +15,9 @@ public class AddEpisodeToCollectionHandler :
|
||||
IRequestHandler<AddEpisodeToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddEpisodeToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -14,9 +14,9 @@ namespace ErsatzTV.Application.MediaCollections;
|
||||
public class AddImageToCollectionHandler : IRequestHandler<AddImageToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddImageToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -15,10 +15,10 @@ public class AddItemsToCollectionHandler :
|
||||
IRequestHandler<AddItemsToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public AddItemsToCollectionHandler(
|
||||
|
||||
@@ -15,9 +15,9 @@ public class AddMovieToCollectionHandler :
|
||||
IRequestHandler<AddMovieToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddMovieToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -15,9 +15,9 @@ public class AddMusicVideoToCollectionHandler :
|
||||
IRequestHandler<AddMusicVideoToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddMusicVideoToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -15,9 +15,9 @@ public class AddOtherVideoToCollectionHandler :
|
||||
IRequestHandler<AddOtherVideoToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddOtherVideoToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -15,9 +15,9 @@ public class AddSeasonToCollectionHandler :
|
||||
IRequestHandler<AddSeasonToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddSeasonToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -15,9 +15,9 @@ public class AddShowToCollectionHandler :
|
||||
IRequestHandler<AddShowToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddShowToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -15,9 +15,9 @@ public class AddSongToCollectionHandler :
|
||||
IRequestHandler<AddSongToCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public AddSongToCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -89,11 +89,11 @@ public partial class AddTraktListHandler : TraktCommandBase, IRequestHandler<Add
|
||||
return maybeList.Map(_ => Unit.Default);
|
||||
}
|
||||
|
||||
private sealed record Parameters(string User, string List);
|
||||
|
||||
[GeneratedRegex(@"https:\/\/trakt\.tv\/users\/([\w\-_]+)\/(?:lists\/)?([\w\-_]+)")]
|
||||
private static partial Regex UriTraktListRegex();
|
||||
|
||||
[GeneratedRegex(@"([\w\-_]+)\/(?:lists\/)?([\w\-_]+)")]
|
||||
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(
|
||||
TvContext dbContext,
|
||||
CreateCollection request) =>
|
||||
ValidateName(dbContext, request).MapT(
|
||||
name => new Collection
|
||||
ValidateName(dbContext, request).MapT(name => new Collection
|
||||
{
|
||||
Name = name,
|
||||
MediaItems = new List<MediaItem>()
|
||||
|
||||
@@ -51,12 +51,10 @@ public class CreateMultiCollectionHandler :
|
||||
private static Task<Validation<BaseError, MultiCollection>> Validate(
|
||||
TvContext dbContext,
|
||||
CreateMultiCollection request) =>
|
||||
ValidateName(dbContext, request).MapT(
|
||||
name => new MultiCollection
|
||||
ValidateName(dbContext, request).MapT(name => new MultiCollection
|
||||
{
|
||||
Name = name,
|
||||
MultiCollectionItems = request.Items.Bind(
|
||||
i =>
|
||||
MultiCollectionItems = request.Items.Bind(i =>
|
||||
{
|
||||
if (i.CollectionId.HasValue)
|
||||
{
|
||||
@@ -72,8 +70,7 @@ public class CreateMultiCollectionHandler :
|
||||
return Option<MultiCollectionItem>.None;
|
||||
})
|
||||
.ToList(),
|
||||
MultiCollectionSmartItems = request.Items.Bind(
|
||||
i =>
|
||||
MultiCollectionSmartItems = request.Items.Bind(i =>
|
||||
{
|
||||
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) =>
|
||||
await ValidatePlaylistName(dbContext, request).MapT(
|
||||
name => new Playlist
|
||||
await ValidatePlaylistName(dbContext, request).MapT(name => new Playlist
|
||||
{
|
||||
PlaylistGroupId = request.PlaylistGroupId,
|
||||
Name = name
|
||||
|
||||
@@ -48,8 +48,7 @@ public class CreateSmartCollectionHandler :
|
||||
private static Task<Validation<BaseError, SmartCollection>> Validate(
|
||||
TvContext dbContext,
|
||||
CreateSmartCollection request) =>
|
||||
ValidateName(dbContext, request).MapT(
|
||||
name => new SmartCollection
|
||||
ValidateName(dbContext, request).MapT(name => new SmartCollection
|
||||
{
|
||||
Name = name,
|
||||
Query = request.Query
|
||||
|
||||
@@ -14,9 +14,9 @@ namespace ErsatzTV.Application.MediaCollections;
|
||||
public class RemoveItemsFromCollectionHandler : IRequestHandler<RemoveItemsFromCollection, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _searchChannel;
|
||||
|
||||
public RemoveItemsFromCollectionHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
|
||||
@@ -50,8 +50,7 @@ public class UpdateMultiCollectionHandler : IRequestHandler<UpdateMultiCollectio
|
||||
.Filter(i => i.CollectionId.HasValue)
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
.Filter(i => c.MultiCollectionItems.All(i2 => i2.CollectionId != i.CollectionId.Value))
|
||||
.Map(
|
||||
i => new MultiCollectionItem
|
||||
.Map(i => new MultiCollectionItem
|
||||
{
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
CollectionId = i.CollectionId.Value,
|
||||
@@ -70,8 +69,8 @@ public class UpdateMultiCollectionHandler : IRequestHandler<UpdateMultiCollectio
|
||||
// update existing items
|
||||
foreach (MultiCollectionItem item in c.MultiCollectionItems)
|
||||
{
|
||||
foreach (UpdateMultiCollectionItem incoming in request.Items.Filter(
|
||||
i => i.CollectionId == item.CollectionId))
|
||||
foreach (UpdateMultiCollectionItem incoming in
|
||||
request.Items.Filter(i => i.CollectionId == item.CollectionId))
|
||||
{
|
||||
item.ScheduleAsGroup = incoming.ScheduleAsGroup;
|
||||
item.PlaybackOrder = incoming.PlaybackOrder;
|
||||
@@ -85,8 +84,7 @@ public class UpdateMultiCollectionHandler : IRequestHandler<UpdateMultiCollectio
|
||||
.Filter(i => i.SmartCollectionId.HasValue)
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
.Filter(i => c.MultiCollectionSmartItems.All(i2 => i2.SmartCollectionId != i.SmartCollectionId.Value))
|
||||
.Map(
|
||||
i => new MultiCollectionSmartItem
|
||||
.Map(i => new MultiCollectionSmartItem
|
||||
{
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
SmartCollectionId = i.SmartCollectionId.Value,
|
||||
@@ -105,8 +103,8 @@ public class UpdateMultiCollectionHandler : IRequestHandler<UpdateMultiCollectio
|
||||
// update existing items
|
||||
foreach (MultiCollectionSmartItem item in c.MultiCollectionSmartItems)
|
||||
{
|
||||
foreach (UpdateMultiCollectionItem incoming in request.Items.Filter(
|
||||
i => i.SmartCollectionId == item.SmartCollectionId))
|
||||
foreach (UpdateMultiCollectionItem incoming in request.Items.Filter(i =>
|
||||
i.SmartCollectionId == item.SmartCollectionId))
|
||||
{
|
||||
item.ScheduleAsGroup = incoming.ScheduleAsGroup;
|
||||
item.PlaybackOrder = incoming.PlaybackOrder;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Globalization;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems;
|
||||
|
||||
@@ -46,9 +46,10 @@ internal static class Mapper
|
||||
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
||||
|
||||
return languageCodes
|
||||
.Map(
|
||||
lang => allCultures.Filter(
|
||||
ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase)))
|
||||
.Map(lang => allCultures.Filter(ci => string.Equals(
|
||||
ci.ThreeLetterISOLanguageName,
|
||||
lang,
|
||||
StringComparison.OrdinalIgnoreCase)))
|
||||
.Flatten()
|
||||
.Map(ci => ci.EnglishName)
|
||||
.Distinct()
|
||||
|
||||
@@ -19,7 +19,6 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
||||
{
|
||||
private readonly IBlockPlayoutBuilder _blockPlayoutBuilder;
|
||||
private readonly IBlockPlayoutFillerBuilder _blockPlayoutFillerBuilder;
|
||||
private readonly IYamlPlayoutBuilder _yamlPlayoutBuilder;
|
||||
private readonly IClient _client;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IEntityLocker _entityLocker;
|
||||
@@ -27,6 +26,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
||||
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
|
||||
private readonly IPlayoutBuilder _playoutBuilder;
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
||||
private readonly IYamlPlayoutBuilder _yamlPlayoutBuilder;
|
||||
|
||||
public BuildPlayoutHandler(
|
||||
IClient client,
|
||||
|
||||
@@ -37,8 +37,7 @@ public class CreateBlockPlayoutHandler(
|
||||
TvContext dbContext,
|
||||
CreateBlockPlayout request) =>
|
||||
(await ValidateChannel(dbContext, request), ValidatePlayoutType(request))
|
||||
.Apply(
|
||||
(channel, playoutType) => new Playout
|
||||
.Apply((channel, playoutType) => new Playout
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
ProgramSchedulePlayoutType = playoutType,
|
||||
|
||||
@@ -50,8 +50,7 @@ public class CreateExternalJsonPlayoutHandler
|
||||
TvContext dbContext,
|
||||
CreateExternalJsonPlayout request) =>
|
||||
(await ValidateChannel(dbContext, request), ValidateExternalJsonFile(request), ValidatePlayoutType(request))
|
||||
.Apply(
|
||||
(channel, externalJsonFile, playoutType) => new Playout
|
||||
.Apply((channel, externalJsonFile, playoutType) => new Playout
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
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 RefreshChannelList());
|
||||
return new CreatePlayoutResponse(playout.Id);
|
||||
}
|
||||
@@ -50,8 +51,7 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit
|
||||
CreateFloodPlayout request) =>
|
||||
(await ValidateChannel(dbContext, request), await ValidateProgramSchedule(dbContext, request),
|
||||
ValidatePlayoutType(request))
|
||||
.Apply(
|
||||
(channel, programSchedule, playoutType) => new Playout
|
||||
.Apply((channel, programSchedule, playoutType) => new Playout
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
ProgramScheduleId = programSchedule.Id,
|
||||
|
||||
@@ -50,8 +50,7 @@ public class CreateYamlPlayoutHandler
|
||||
TvContext dbContext,
|
||||
CreateYamlPlayout request) =>
|
||||
(await ValidateChannel(dbContext, request), ValidateYamlFile(request), ValidatePlayoutType(request))
|
||||
.Apply(
|
||||
(channel, externalJsonFile, playoutType) => new Playout
|
||||
.Apply((channel, externalJsonFile, playoutType) => new Playout
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
TemplateFile = externalJsonFile,
|
||||
|
||||
@@ -26,8 +26,11 @@ public class ResetAllPlayoutsHandler(
|
||||
case ProgramSchedulePlayoutType.Yaml:
|
||||
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;
|
||||
case ProgramSchedulePlayoutType.ExternalJson:
|
||||
case ProgramSchedulePlayoutType.None:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Globalization;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.Playouts;
|
||||
|
||||
@@ -51,16 +50,14 @@ internal static class Mapper
|
||||
.Map(am => $"{am.Title} - ").IfNone(string.Empty);
|
||||
return mv.MusicVideoMetadata.HeadOrNone()
|
||||
.Map(mvm => $"{artistName}{mvm.Title}")
|
||||
.Map(
|
||||
s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||
.Map(s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||
? s
|
||||
: $"{s} ({playoutItem.ChapterTitle})")
|
||||
.IfNone("[unknown music video]");
|
||||
case OtherVideo ov:
|
||||
return ov.OtherVideoMetadata.HeadOrNone()
|
||||
.Map(ovm => ovm.Title ?? string.Empty)
|
||||
.Map(
|
||||
s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||
.Map(s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||
? s
|
||||
: $"{s} ({playoutItem.ChapterTitle})")
|
||||
.IfNone("[unknown video]");
|
||||
@@ -70,8 +67,7 @@ internal static class Mapper
|
||||
.IfNone(string.Empty);
|
||||
return s.SongMetadata.HeadOrNone()
|
||||
.Map(sm => $"{songArtist}{sm.Title ?? string.Empty}")
|
||||
.Map(
|
||||
t => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||
.Map(t => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle)
|
||||
? t
|
||||
: $"{s} ({playoutItem.ChapterTitle})")
|
||||
.IfNone("[unknown song]");
|
||||
|
||||
@@ -19,8 +19,7 @@ public class GetAllPlayoutsHandler : IRequestHandler<GetAllPlayouts, List<Playou
|
||||
.AsNoTracking()
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.Filter(p => p.Channel != null)
|
||||
.Map(
|
||||
p => new PlayoutNameViewModel(
|
||||
.Map(p => new PlayoutNameViewModel(
|
||||
p.Id,
|
||||
p.ProgramSchedulePlayoutType,
|
||||
p.Channel.Name,
|
||||
|
||||
@@ -17,8 +17,7 @@ public class GetPlayoutByIdHandler(IDbContextFactory<TvContext> dbContextFactory
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.Include(p => p.Channel)
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId)
|
||||
.MapT(
|
||||
p => new PlayoutNameViewModel(
|
||||
.MapT(p => new PlayoutNameViewModel(
|
||||
p.Id,
|
||||
p.ProgramSchedulePlayoutType,
|
||||
p.Channel.Name,
|
||||
|
||||
@@ -20,8 +20,7 @@ public class StartPlexPinFlowHandler : IRequestHandler<StartPlexPinFlow, Either<
|
||||
public Task<Either<BaseError, string>> Handle(
|
||||
StartPlexPinFlow request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_plexTvApiClient.StartPinFlow().Bind(
|
||||
result => result.Match(
|
||||
_plexTvApiClient.StartPinFlow().Bind(result => result.Match(
|
||||
Left: error => Task.FromResult(Left<BaseError, string>(error)),
|
||||
Right: async pin =>
|
||||
{
|
||||
|
||||
@@ -10,7 +10,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Application.Plex;
|
||||
|
||||
public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler, IRequestHandler<SynchronizePlexMediaSources,
|
||||
public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler,
|
||||
IRequestHandler<SynchronizePlexMediaSources,
|
||||
Either<BaseError, List<PlexMediaSource>>>
|
||||
{
|
||||
private const string LocalhostUri = "http://localhost:32400";
|
||||
@@ -56,8 +57,8 @@ public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler, IRe
|
||||
}
|
||||
|
||||
// delete removed servers
|
||||
foreach (PlexMediaSource removed in allExisting.Filter(
|
||||
s => servers.All(pms => pms.ClientIdentifier != s.ClientIdentifier)))
|
||||
foreach (PlexMediaSource removed in allExisting.Filter(s =>
|
||||
servers.All(pms => pms.ClientIdentifier != s.ClientIdentifier)))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Deleting removed Plex server {ServerName}!",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
@@ -12,7 +13,9 @@ public abstract class PlexBaseConnectionHandler(
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
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;
|
||||
|
||||
@@ -43,7 +46,8 @@ public abstract class PlexBaseConnectionHandler(
|
||||
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)
|
||||
{
|
||||
connection.IsActive = true;
|
||||
@@ -60,12 +64,16 @@ public abstract class PlexBaseConnectionHandler(
|
||||
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
|
||||
{
|
||||
logger.LogDebug("Attempting to locate to Plex at {Uri}", connection.Uri);
|
||||
var sw = new System.Diagnostics.Stopwatch();
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
bool pingResult = await plexServerApiClient.Ping(connection, token, cancellationToken);
|
||||
sw.Stop();
|
||||
|
||||
@@ -8,14 +8,15 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Application.Plex;
|
||||
|
||||
public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler, IRequestHandler<GetPlexConnectionParameters,
|
||||
public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler,
|
||||
IRequestHandler<GetPlexConnectionParameters,
|
||||
Either<BaseError, PlexConnectionParametersViewModel>>
|
||||
{
|
||||
private readonly ILogger<GetPlexConnectionParametersHandler> _logger;
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly IPlexServerApiClient _plexServerApiClient;
|
||||
private readonly IPlexSecretStore _plexSecretStore;
|
||||
private readonly ILogger<GetPlexConnectionParametersHandler> _logger;
|
||||
private readonly IPlexServerApiClient _plexServerApiClient;
|
||||
|
||||
public GetPlexConnectionParametersHandler(
|
||||
IMemoryCache memoryCache,
|
||||
@@ -49,7 +50,8 @@ public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler, IRe
|
||||
foreach (PlexServerAuthToken token in maybeToken)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
if (await _plexServerApiClient.Ping(activeConnection, token, cancellationToken))
|
||||
|
||||
@@ -30,8 +30,7 @@ public class CreateProgramScheduleHandler(IDbContextFactory<TvContext> dbContext
|
||||
private static Task<Validation<BaseError, ProgramSchedule>> Validate(
|
||||
TvContext dbContext,
|
||||
CreateProgramSchedule request) =>
|
||||
ValidateName(dbContext, request).MapT(
|
||||
name =>
|
||||
ValidateName(dbContext, request).MapT(name =>
|
||||
{
|
||||
bool keepMultiPartEpisodesTogether = request.KeepMultiPartEpisodesTogether;
|
||||
return new ProgramSchedule
|
||||
|
||||
@@ -12,8 +12,7 @@ public class GetAllProgramSchedulesHandler(IDbContextFactory<TvContext> dbContex
|
||||
{
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
return await dbContext.ProgramSchedules
|
||||
.Map(
|
||||
ps => new ProgramScheduleViewModel(
|
||||
.Map(ps => new ProgramScheduleViewModel(
|
||||
ps.Id,
|
||||
ps.Name,
|
||||
ps.KeepMultiPartEpisodesTogether,
|
||||
|
||||
@@ -52,8 +52,7 @@ public class GetProgramScheduleItemsHandler :
|
||||
.Include(i => i.FallbackFiller)
|
||||
.Include(i => i.Watermark)
|
||||
.ToListAsync(cancellationToken)
|
||||
.Map(
|
||||
programScheduleItems => programScheduleItems.Map(ProjectToViewModel)
|
||||
.Map(programScheduleItems => programScheduleItems.Map(ProjectToViewModel)
|
||||
.Map(psi => EnforceProperties(maybeProgramSchedule, psi)).ToList());
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,7 @@ public class CreateCustomResolutionHandler : IRequestHandler<CreateCustomResolut
|
||||
TvContext dbContext,
|
||||
CreateCustomResolution request) =>
|
||||
ResolutionMustBeUnique(dbContext, request)
|
||||
.MapT(
|
||||
_ => new Resolution
|
||||
.MapT(_ => new Resolution
|
||||
{
|
||||
Name = $"{request.Width}x{request.Height}",
|
||||
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) =>
|
||||
await ValidateBlockName(dbContext, request).MapT(
|
||||
name => new Block
|
||||
await ValidateBlockName(dbContext, request).MapT(name => new Block
|
||||
{
|
||||
BlockGroupId = request.BlockGroupId,
|
||||
Name = name,
|
||||
|
||||
@@ -25,8 +25,7 @@ public class CreateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
}
|
||||
|
||||
private static async Task<Validation<BaseError, Deco>> Validate(TvContext dbContext, CreateDeco request) =>
|
||||
await ValidateDecoName(dbContext, request).MapT(
|
||||
name => new Deco
|
||||
await ValidateDecoName(dbContext, request).MapT(name => new Deco
|
||||
{
|
||||
DecoGroupId = request.DecoGroupId,
|
||||
Name = name
|
||||
|
||||
@@ -26,8 +26,7 @@ public class CreateDecoTemplateHandler(IDbContextFactory<TvContext> dbContextFac
|
||||
|
||||
private static Task<Validation<BaseError, DecoTemplate>> Validate(CreateDecoTemplate request) =>
|
||||
Task.FromResult(
|
||||
ValidateName(request).Map(
|
||||
name => new DecoTemplate
|
||||
ValidateName(request).Map(name => new DecoTemplate
|
||||
{
|
||||
DecoTemplateGroupId = request.DecoTemplateGroupId,
|
||||
Name = name
|
||||
|
||||
@@ -26,8 +26,7 @@ public class CreateTemplateHandler(IDbContextFactory<TvContext> dbContextFactory
|
||||
|
||||
private static Task<Validation<BaseError, Template>> Validate(CreateTemplate request) =>
|
||||
Task.FromResult(
|
||||
ValidateName(request).Map(
|
||||
name => new Template
|
||||
ValidateName(request).Map(name => new Template
|
||||
{
|
||||
TemplateGroupId = request.TemplateGroupId,
|
||||
Name = name
|
||||
|
||||
@@ -13,8 +13,7 @@ public class ErasePlayoutHistoryHandler(IDbContextFactory<TvContext> dbContextFa
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
Option<Playout> maybePlayout = await dbContext.Playouts
|
||||
.Filter(
|
||||
p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
|
||||
.Filter(p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
|
||||
p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml)
|
||||
.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
|
||||
.Include(p => p.Items)
|
||||
.Include(p => p.PlayoutHistory)
|
||||
.Filter(
|
||||
p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
|
||||
.Filter(p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
|
||||
p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml)
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
|
||||
|
||||
|
||||
@@ -71,8 +71,7 @@ public class ReplaceTemplateItemsHandler(IDbContextFactory<TvContext> dbContextF
|
||||
.ToListAsync()
|
||||
.Map(list => list.ToDictionary(b => b.Id, b => b));
|
||||
|
||||
var allTemplateItems = request.Items.Map(
|
||||
i =>
|
||||
var allTemplateItems = request.Items.Map(i =>
|
||||
{
|
||||
Block block = allBlocks[i.BlockId];
|
||||
return new BlockTemplateItem(
|
||||
|
||||
@@ -88,8 +88,8 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
}
|
||||
|
||||
Option<Deco> maybeExisting = await dbContext.Decos
|
||||
.FirstOrDefaultAsync(
|
||||
d => d.Id != request.DecoId && d.DecoGroupId == request.DecoGroupId && d.Name == request.Name)
|
||||
.FirstOrDefaultAsync(d =>
|
||||
d.Id != request.DecoId && d.DecoGroupId == request.DecoGroupId && d.Name == request.Name)
|
||||
.Map(Optional);
|
||||
|
||||
return maybeExisting.IsSome
|
||||
|
||||
@@ -18,8 +18,7 @@ public class GetDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbContextF
|
||||
.Filter(i => i.DecoTemplateId == request.DecoTemplateId)
|
||||
.Include(i => i.Deco)
|
||||
.ToListAsync(cancellationToken)
|
||||
.Map(
|
||||
items => items
|
||||
.Map(items => items
|
||||
.Map(Mapper.ProjectToViewModel)
|
||||
.Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero)
|
||||
.ToList());
|
||||
|
||||
@@ -16,8 +16,7 @@ public class GetTemplateItemsHandler(IDbContextFactory<TvContext> dbContextFacto
|
||||
.Filter(i => i.TemplateId == request.TemplateId)
|
||||
.Include(i => i.Block)
|
||||
.ToListAsync(cancellationToken)
|
||||
.Map(
|
||||
items => items
|
||||
.Map(items => items
|
||||
.Map(Mapper.ProjectToViewModel)
|
||||
.Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero)
|
||||
.ToList());
|
||||
|
||||
@@ -30,5 +30,6 @@ public class QuerySearchIndexAllItemsHandler : IRequestHandler<QuerySearchIndexA
|
||||
await GetIds(LuceneSearchIndex.ImageType, request.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);
|
||||
|
||||
result.AddRange(
|
||||
schedules.SelectMany(
|
||||
s => new[]
|
||||
schedules.SelectMany(s => new[]
|
||||
{
|
||||
new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.Schedule),
|
||||
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);
|
||||
return await dbContext.ArtistMetadata
|
||||
.AsNoTracking()
|
||||
.Where(
|
||||
a => EF.Functions.Like(
|
||||
.Where(a => EF.Functions.Like(
|
||||
EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation),
|
||||
$"%{request.Query}%"))
|
||||
.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);
|
||||
return await dbContext.Collections
|
||||
.AsNoTracking()
|
||||
.Where(
|
||||
c => EF.Functions.Like(
|
||||
.Where(c => EF.Functions.Like(
|
||||
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
||||
$"%{request.Query}%"))
|
||||
.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);
|
||||
return await dbContext.MovieMetadata
|
||||
.AsNoTracking()
|
||||
.Where(
|
||||
s => EF.Functions.Like(
|
||||
.Where(s => EF.Functions.Like(
|
||||
EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation),
|
||||
$"%{request.Query}%"))
|
||||
.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);
|
||||
return await dbContext.MultiCollections
|
||||
.AsNoTracking()
|
||||
.Where(
|
||||
c => EF.Functions.Like(
|
||||
.Where(c => EF.Functions.Like(
|
||||
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
||||
$"%{request.Query}%"))
|
||||
.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);
|
||||
return await dbContext.SmartCollections
|
||||
.AsNoTracking()
|
||||
.Where(
|
||||
c => EF.Functions.Like(
|
||||
.Where(c => EF.Functions.Like(
|
||||
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
|
||||
$"%{request.Query}%"))
|
||||
.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);
|
||||
return await dbContext.ShowMetadata
|
||||
.AsNoTracking()
|
||||
.Where(
|
||||
s => EF.Functions.Like(
|
||||
.Where(s => EF.Functions.Like(
|
||||
EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation),
|
||||
$"%{request.Query}%"))
|
||||
.OrderBy(s => EF.Functions.Collate(s.Title, TvContext.CaseInsensitiveCollation))
|
||||
|
||||
@@ -174,7 +174,9 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
PlaylistStart = _transcodedUntil;
|
||||
|
||||
// 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();
|
||||
_state = initialWorkAhead ? HlsSessionState.SeekAndWorkAhead : HlsSessionState.SeekAndRealtime;
|
||||
@@ -583,8 +585,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
Directory.GetFiles(
|
||||
Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber),
|
||||
"live*.mp4"))
|
||||
.Map(
|
||||
file =>
|
||||
.Map(file =>
|
||||
{
|
||||
string fileName = Path.GetFileName(file);
|
||||
var sequenceNumber = int.Parse(
|
||||
|
||||
@@ -126,7 +126,9 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
|
||||
PlaylistStart = _transcodedUntil;
|
||||
|
||||
// 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
|
||||
// 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.Watermark)
|
||||
.SelectOneAsync(c => c.Number, c => c.Number == request.ChannelNumber)
|
||||
.MapT(
|
||||
channel =>
|
||||
.MapT(channel =>
|
||||
{
|
||||
channel.StreamingMode = request.Mode.ToLowerInvariant() switch
|
||||
{
|
||||
|
||||
+3
-3
@@ -227,8 +227,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
|
||||
Option<ChannelWatermark> maybeGlobalWatermark = await dbContext.ConfigElements
|
||||
.GetValue<int>(ConfigElementKey.FFmpegGlobalWatermarkId)
|
||||
.BindT(
|
||||
watermarkId => dbContext.ChannelWatermarks
|
||||
.BindT(watermarkId => dbContext.ChannelWatermarks
|
||||
.SelectOneAsync(w => w.Id, w => w.Id == watermarkId));
|
||||
|
||||
Option<ChannelWatermark> playoutItemWatermark = Optional(playoutItemWithPath.PlayoutItem.Watermark);
|
||||
@@ -261,7 +260,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
// override watermark as song_progress_overlay.png
|
||||
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;
|
||||
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
|
||||
Option<Playout> requestedPlayout = await dbContext.Playouts
|
||||
.AsNoTracking()
|
||||
.Filter(
|
||||
p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
|
||||
p.ProgramSchedule.Items.Any(
|
||||
psi => psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
|
||||
.Filter(p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
|
||||
p.ProgramSchedule.Items.Any(psi =>
|
||||
psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId.IfNone(-1));
|
||||
|
||||
playoutIdsToCheck.AddRange(requestedPlayout.Map(p => p.Id));
|
||||
@@ -111,10 +110,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
||||
{
|
||||
playoutIdsToCheck = dbContext.Playouts
|
||||
.AsNoTracking()
|
||||
.Filter(
|
||||
p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
|
||||
p.ProgramSchedule.Items.Any(
|
||||
psi => psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
|
||||
.Filter(p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
|
||||
p.ProgramSchedule.Items.Any(psi =>
|
||||
psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
|
||||
.Map(p => p.Id)
|
||||
.ToList();
|
||||
}
|
||||
@@ -216,10 +214,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
||||
List<int> episodeIds = await dbContext.EpisodeMetadata
|
||||
.AsNoTracking()
|
||||
.Filter(em => mediaItemIds.Contains(em.EpisodeId))
|
||||
.Filter(
|
||||
em => em.Subtitles.Any(
|
||||
s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
||||
.Filter(em => em.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" &&
|
||||
s.Codec != "dvdsub" &&
|
||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
||||
.Map(em => em.EpisodeId)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -228,10 +225,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
||||
List<int> movieIds = await dbContext.MovieMetadata
|
||||
.AsNoTracking()
|
||||
.Filter(mm => mediaItemIds.Contains(mm.MovieId))
|
||||
.Filter(
|
||||
mm => mm.Subtitles.Any(
|
||||
s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
||||
.Filter(mm => mm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" &&
|
||||
s.Codec != "dvdsub" &&
|
||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
||||
.Map(mm => mm.MovieId)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -240,10 +236,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
||||
List<int> musicVideoIds = await dbContext.MusicVideoMetadata
|
||||
.AsNoTracking()
|
||||
.Filter(mm => mediaItemIds.Contains(mm.MusicVideoId))
|
||||
.Filter(
|
||||
mm => mm.Subtitles.Any(
|
||||
s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
||||
.Filter(mm => mm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" &&
|
||||
s.Codec != "dvdsub" &&
|
||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
||||
.Map(mm => mm.MusicVideoId)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -252,10 +247,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
||||
List<int> otherVideoIds = await dbContext.OtherVideoMetadata
|
||||
.AsNoTracking()
|
||||
.Filter(ovm => mediaItemIds.Contains(ovm.OtherVideoId))
|
||||
.Filter(
|
||||
ovm => ovm.Subtitles.Any(
|
||||
s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
||||
.Filter(ovm => ovm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded &&
|
||||
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" &&
|
||||
s.Codec != "dvdsub" &&
|
||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs"))
|
||||
.Map(ovm => ovm.OtherVideoId)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -284,11 +278,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
||||
// find each subtitle that needs extraction
|
||||
IEnumerable<Subtitle> subtitles = allSubtitles
|
||||
.Filter(s => s.SubtitleKind == SubtitleKind.Embedded)
|
||||
.Filter(
|
||||
s => s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
||||
.Filter(s => s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" &&
|
||||
s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")
|
||||
.Filter(
|
||||
s => s.IsExtracted == false || string.IsNullOrWhiteSpace(s.Path) ||
|
||||
.Filter(s => s.IsExtracted == false || string.IsNullOrWhiteSpace(s.Path) ||
|
||||
FileDoesntExist(mediaItem.Id, s));
|
||||
|
||||
// find cache paths for each subtitle
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Globalization;
|
||||
using ErsatzTV.Application.MediaCards;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Emby;
|
||||
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 => 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.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.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()
|
||||
.Map(
|
||||
m => (m.ContentRating ?? string.Empty).Split("/").Map(s => s.Trim())
|
||||
.Map(m => (m.ContentRating ?? string.Empty).Split("/").Map(s => s.Trim())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x)).ToList()).IfNone([]),
|
||||
LanguagesForShow(languages),
|
||||
show.ShowMetadata.HeadOrNone()
|
||||
.Map(
|
||||
m => m.Actors.OrderBy(a => a.Order).ThenBy(a => a.Id)
|
||||
.Map(m => m.Actors.OrderBy(a => a.Order).ThenBy(a => a.Id)
|
||||
.Map(a => MediaCards.Mapper.ProjectToViewModel(a, maybeJellyfin, maybeEmby))
|
||||
.ToList())
|
||||
.IfNone([]));
|
||||
@@ -104,9 +103,10 @@ internal static class Mapper
|
||||
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
||||
|
||||
return languages
|
||||
.Map(
|
||||
lang => allCultures.Filter(
|
||||
ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase)))
|
||||
.Map(lang => allCultures.Filter(ci => string.Equals(
|
||||
ci.ThreeLetterISOLanguageName,
|
||||
lang,
|
||||
StringComparison.OrdinalIgnoreCase)))
|
||||
.Flatten()
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
@@ -38,8 +38,7 @@ public class CreateWatermarkHandler : IRequestHandler<CreateWatermark, Either<Ba
|
||||
|
||||
private static Validation<BaseError, ChannelWatermark> Validate(CreateWatermark request) =>
|
||||
ValidateName(request)
|
||||
.Map(
|
||||
_ =>
|
||||
.Map(_ =>
|
||||
{
|
||||
var watermark = new ChannelWatermark
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Shouldly;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.Domain;
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Emby;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.FFmpeg.Runtime;
|
||||
using Shouldly;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.Emby;
|
||||
|
||||
|
||||
@@ -14,12 +14,6 @@ public class CustomStreamSelectorTests
|
||||
[TestFixture]
|
||||
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]
|
||||
public void SetUp()
|
||||
{
|
||||
@@ -41,6 +35,14 @@ 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]
|
||||
public async Task Should_Select_eng_Audio_Exact_Match()
|
||||
{
|
||||
@@ -697,7 +699,7 @@ items:
|
||||
MediaStreamKind = MediaStreamKind.Audio,
|
||||
Channels = 2,
|
||||
Language = "ja",
|
||||
Title = "Some Title",
|
||||
Title = "Some Title"
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
@@ -714,14 +716,14 @@ items:
|
||||
MediaStreamKind = MediaStreamKind.Audio,
|
||||
Channels = 6,
|
||||
Language = englishLanguage,
|
||||
Title = "Movie Title",
|
||||
Title = "Movie Title"
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
Index = 3,
|
||||
MediaStreamKind = MediaStreamKind.Audio,
|
||||
Channels = 2,
|
||||
Title = "Who Knows",
|
||||
Title = "Who Knows"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using Shouldly;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ using ErsatzTV.Core.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Infrastructure.Scripting;
|
||||
using Shouldly;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class FFmpegStreamSelectorTests
|
||||
MediaStreamKind = MediaStreamKind.Audio,
|
||||
Channels = 2,
|
||||
Language = "ja",
|
||||
Title = "Some Title",
|
||||
Title = "Some Title"
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
@@ -63,7 +63,12 @@ public class FFmpegStreamSelectorTests
|
||||
Substitute.For<ILocalFileSystem>(),
|
||||
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();
|
||||
foreach (MediaStream stream in selectedStream)
|
||||
{
|
||||
@@ -86,7 +91,7 @@ public class FFmpegStreamSelectorTests
|
||||
MediaStreamKind = MediaStreamKind.Audio,
|
||||
Channels = 2,
|
||||
Language = "ja",
|
||||
Title = "Some Title",
|
||||
Title = "Some Title"
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
@@ -118,7 +123,12 @@ public class FFmpegStreamSelectorTests
|
||||
Substitute.For<ILocalFileSystem>(),
|
||||
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();
|
||||
foreach (MediaStream stream in selectedStream)
|
||||
{
|
||||
@@ -143,8 +153,8 @@ public class FFmpegStreamSelectorTests
|
||||
{
|
||||
StreamIndex = 1,
|
||||
SubtitleKind = SubtitleKind.Sidecar,
|
||||
Language = "he",
|
||||
},
|
||||
Language = "he"
|
||||
}
|
||||
};
|
||||
|
||||
var channel = new Channel(Guid.NewGuid());
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using ErsatzTV.Core.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using Shouldly;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using ErsatzTV.Core.FFmpeg;
|
||||
using Shouldly;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
|
||||
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>> GetSmartCollectionItems(int id) => _data[id].ToList().AsTask();
|
||||
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>> GetPlaylistItems(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(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> AddActor(ShowMetadata metadata, Actor actor) => throw new NotSupportedException();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using ErsatzTV.Core.Iptv;
|
||||
using Shouldly;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
|
||||
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