Browse Source

cleanup (#1708)

* fix blazor naming

* code cleanup

* update dependencies
pull/1709/head
Jason Dove 2 years ago committed by GitHub
parent
commit
a87ec2d75d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .config/dotnet-tools.json
  2. 64
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  3. 5
      ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs
  4. 2
      ErsatzTV.Application/ErsatzTV.Application.csproj
  5. 4
      ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs
  6. 8
      ErsatzTV.Application/MediaCollections/Commands/AddMovieToPlaylistHandler.cs
  7. 4
      ErsatzTV.Application/MediaCollections/Commands/AddMusicVideoToCollectionHandler.cs
  8. 4
      ErsatzTV.Application/MediaCollections/Commands/AddOtherVideoToCollectionHandler.cs
  9. 4
      ErsatzTV.Application/MediaCollections/Commands/CreatePlaylistGroupHandler.cs
  10. 4
      ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs
  11. 9
      ErsatzTV.Application/MediaCollections/Commands/ReplacePlaylistItemsHandler.cs
  12. 2
      ErsatzTV.Application/MediaCollections/Mapper.cs
  13. 4
      ErsatzTV.Application/MediaCollections/Queries/GetAllPlaylistGroupsHandler.cs
  14. 6
      ErsatzTV.Application/MediaItems/Mapper.cs
  15. 3
      ErsatzTV.Application/Scheduling/Commands/CreateDecoTemplate.cs
  16. 10
      ErsatzTV.Application/Scheduling/Commands/ReplaceDecoTemplateItemsHandler.cs
  17. 6
      ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs
  18. 3
      ErsatzTV.Application/Scheduling/Commands/UpdateDefaultDecoHandler.cs
  19. 3
      ErsatzTV.Application/Scheduling/Mapper.cs
  20. 4
      ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateByIdHandler.cs
  21. 4
      ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateItemsHandler.cs
  22. 2
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  23. 3
      ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs
  24. 4
      ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs
  25. 10
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  26. 6
      ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumber.cs
  27. 2
      ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs
  28. 2
      ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
  29. 2
      ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs
  30. 2
      ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs
  31. 36
      ErsatzTV.Core.Tests/Scheduling/PlayoutTemplateSelectorTests.cs
  32. 1
      ErsatzTV.Core/Domain/Collection/Playlist.cs
  33. 1
      ErsatzTV.Core/Domain/Scheduling/PlayoutTemplate.cs
  34. 3
      ErsatzTV.Core/Emby/EmbyPathReplacementService.cs
  35. 2
      ErsatzTV.Core/ErsatzTV.Core.csproj
  36. 2
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  37. 2
      ErsatzTV.Core/FileSystemLayout.cs
  38. 3
      ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs
  39. 3
      ErsatzTV.Core/Plex/PlexPathReplacementService.cs
  40. 2
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutShuffledMediaCollectionEnumerator.cs
  41. 2
      ErsatzTV.Core/Scheduling/CollectionKey.cs
  42. 6
      ErsatzTV.Core/Scheduling/MediaItemsForCollection.cs
  43. 109
      ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs
  44. 4
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  45. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  46. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  47. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  48. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs
  49. 4
      ErsatzTV.Core/Scheduling/PlayoutTemplateSelector.cs
  50. 2
      ErsatzTV.Core/Scheduling/SuperShuffle.cs
  51. 4
      ErsatzTV.FFmpeg/Encoder/Amf/EncoderH264Amf.cs
  52. 4
      ErsatzTV.FFmpeg/Encoder/EncoderLibx264.cs
  53. 2
      ErsatzTV.FFmpeg/Encoder/EncoderLibx265.cs
  54. 2
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs
  55. 6
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs
  56. 4
      ErsatzTV.FFmpeg/Encoder/Qsv/EncoderH264Qsv.cs
  57. 2
      ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs
  58. 2
      ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs
  59. 4
      ErsatzTV.FFmpeg/Encoder/VideoToolbox/EncoderH264VideoToolbox.cs
  60. 5
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  61. 6
      ErsatzTV.FFmpeg/Preset/AvailablePresets.cs
  62. 4
      ErsatzTV.FFmpeg/Preset/VideoPreset.cs
  63. 2
      ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj
  64. 3
      ErsatzTV.Infrastructure/Data/Configurations/Collection/PlaylistConfiguration.cs
  65. 2
      ErsatzTV.Infrastructure/Data/Configurations/Collection/PlaylistItemConfiguration.cs
  66. 2
      ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs
  67. 2
      ErsatzTV.Infrastructure/Data/Configurations/Scheduling/DecoConfiguration.cs
  68. 34
      ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs
  69. 6
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  70. 4
      ErsatzTV.Infrastructure/Plex/PlexEtag.cs
  71. 2
      ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs
  72. 10
      ErsatzTV.Infrastructure/Search/CustomMultiFieldQueryParser.cs
  73. 6
      ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs
  74. 2
      ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs
  75. 12
      ErsatzTV.Infrastructure/Search/SearchQueryParser.cs
  76. 2
      ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj
  77. 2
      ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs
  78. 2
      ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs
  79. 6
      ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs
  80. 4
      ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs
  81. 4
      ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs
  82. 2
      ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs
  83. 10
      ErsatzTV/Controllers/IptvController.cs
  84. 4
      ErsatzTV/ErsatzTV.csproj
  85. 40
      ErsatzTV/Pages/Artist.razor
  86. 11
      ErsatzTV/Pages/ArtistList.razor
  87. 4
      ErsatzTV/Pages/BlockEditor.razor
  88. 155
      ErsatzTV/Pages/BlockPlayoutEditor.razor
  89. 2
      ErsatzTV/Pages/Blocks.razor
  90. 4
      ErsatzTV/Pages/ChannelEditor.razor
  91. 2
      ErsatzTV/Pages/Channels.razor
  92. 18
      ErsatzTV/Pages/CollectionEditor.razor
  93. 4
      ErsatzTV/Pages/Collections.razor
  94. 287
      ErsatzTV/Pages/DecoEditor.razor
  95. 2
      ErsatzTV/Pages/DecoTemplateEditor.razor
  96. 2
      ErsatzTV/Pages/DecoTemplates.razor
  97. 2
      ErsatzTV/Pages/Decos.razor
  98. 14
      ErsatzTV/Pages/EmbyLibrariesEditor.razor
  99. 11
      ErsatzTV/Pages/EmbyMediaSourceEditor.razor
  100. 10
      ErsatzTV/Pages/EmbyMediaSources.razor
  101. Some files were not shown because too many files have changed in this diff Show More

2
.config/dotnet-tools.json

@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
"version": "2023.3.3",
"version": "2024.1.1",
"commands": [
"jb"
]

64
ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs

@ -956,68 +956,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -956,68 +956,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
};
}
private static string GetDescription(PlayoutItem playoutItem)
{
if (!string.IsNullOrWhiteSpace(playoutItem.CustomTitle))
{
return string.Empty;
}
return playoutItem.MediaItem switch
{
Movie m => m.MovieMetadata.HeadOrNone().Map(mm => mm.Plot ?? string.Empty).IfNone(string.Empty),
Episode e => e.EpisodeMetadata.HeadOrNone().Map(em => em.Plot ?? string.Empty)
.IfNone(string.Empty),
MusicVideo mv => mv.MusicVideoMetadata.HeadOrNone().Map(mvm => mvm.Plot ?? string.Empty)
.IfNone(string.Empty),
OtherVideo ov => ov.OtherVideoMetadata.HeadOrNone().Map(ovm => ovm.Plot ?? string.Empty)
.IfNone(string.Empty),
_ => string.Empty
};
}
private Option<ContentRating> GetContentRating(PlayoutItem playoutItem)
{
try
{
return playoutItem.MediaItem switch
{
Movie m => m.MovieMetadata
.HeadOrNone()
.Match(mm => ParseContentRating(mm.ContentRating, "MPAA"), () => None),
Episode e => e.Season.Show.ShowMetadata
.HeadOrNone()
.Match(sm => ParseContentRating(sm.ContentRating, "VCHIP"), () => None),
_ => None
};
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get content rating for playout item {Item}", GetTitle(playoutItem));
return None;
}
}
private static Option<ContentRating> ParseContentRating(string contentRating, string system)
{
Option<string> maybeFirst = (contentRating ?? string.Empty).Split('/').HeadOrNone();
return maybeFirst.Map(
first =>
{
string[] split = first.Split(':');
if (split.Length == 2)
{
return split[0].Equals("us", StringComparison.OrdinalIgnoreCase)
? new ContentRating(system, split[1].ToUpperInvariant())
: new ContentRating(None, split[1].ToUpperInvariant());
}
return string.IsNullOrWhiteSpace(first)
? Option<ContentRating>.None
: new ContentRating(None, first);
}).Flatten();
}
private static string GetPrioritizedArtworkPath(Metadata metadata)
{
Option<string> maybeArtwork = Optional(metadata.Artwork).Flatten()
@ -1179,6 +1117,4 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -1179,6 +1117,4 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
]
};
}
private sealed record ContentRating(Option<string> System, string Value);
}

5
ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs

@ -132,11 +132,6 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList> @@ -132,11 +132,6 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList>
.Distinct()
.ToList();
private static string GetIconUrl(ChannelResult channel) =>
string.IsNullOrWhiteSpace(channel.ArtworkPath)
? "{RequestBase}/iptv/images/ersatztv-500.png{AccessTokenUri}"
: $"{{RequestBase}}/iptv/logos/{channel.ArtworkPath}.jpg{{AccessTokenUri}}";
// ReSharper disable once ClassNeverInstantiated.Local
private sealed record ChannelResult(string Number, string Name, string Categories, string ArtworkPath);
}

2
ErsatzTV.Application/ErsatzTV.Application.csproj

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.9.28">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

4
ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs

@ -33,7 +33,9 @@ public class AddEpisodeToCollectionHandler : @@ -33,7 +33,9 @@ public class AddEpisodeToCollectionHandler :
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Parameters> validation = await Validate(dbContext, request);
return await validation.Apply(parameters => ApplyAddTelevisionEpisodeRequest(dbContext, parameters));
return await LanguageExtensions.Apply(
validation,
parameters => ApplyAddTelevisionEpisodeRequest(dbContext, parameters));
}
private async Task<Unit> ApplyAddTelevisionEpisodeRequest(TvContext dbContext, Parameters parameters)

8
ErsatzTV.Application/MediaCollections/Commands/AddMovieToPlaylistHandler.cs

@ -1,9 +1,5 @@ @@ -1,9 +1,5 @@
using System.Threading.Channels;
using ErsatzTV.Application.Playouts;
using ErsatzTV.Core;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
@ -32,7 +28,7 @@ public class AddMovieToPlaylistHandler(IDbContextFactory<TvContext> dbContextFac @@ -32,7 +28,7 @@ public class AddMovieToPlaylistHandler(IDbContextFactory<TvContext> dbContextFac
PlaybackOrder = PlaybackOrder.Shuffle,
IncludeInProgramGuide = true
};
parameters.Playlist.Items.Add(playlistItem);
await dbContext.SaveChangesAsync();
return Unit.Default;

4
ErsatzTV.Application/MediaCollections/Commands/AddMusicVideoToCollectionHandler.cs

@ -33,7 +33,9 @@ public class AddMusicVideoToCollectionHandler : @@ -33,7 +33,9 @@ public class AddMusicVideoToCollectionHandler :
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Parameters> validation = await Validate(dbContext, request);
return await validation.Apply(parameters => ApplyAddMusicVideoRequest(dbContext, parameters));
return await LanguageExtensions.Apply(
validation,
parameters => ApplyAddMusicVideoRequest(dbContext, parameters));
}
private async Task<Unit> ApplyAddMusicVideoRequest(TvContext dbContext, Parameters parameters)

4
ErsatzTV.Application/MediaCollections/Commands/AddOtherVideoToCollectionHandler.cs

@ -33,7 +33,9 @@ public class AddOtherVideoToCollectionHandler : @@ -33,7 +33,9 @@ public class AddOtherVideoToCollectionHandler :
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Parameters> validation = await Validate(dbContext, request);
return await validation.Apply(parameters => ApplyAddOtherVideoRequest(dbContext, parameters));
return await LanguageExtensions.Apply(
validation,
parameters => ApplyAddOtherVideoRequest(dbContext, parameters));
}
private async Task<Unit> ApplyAddOtherVideoRequest(TvContext dbContext, Parameters parameters)

4
ErsatzTV.Application/MediaCollections/Commands/CreatePlaylistGroupHandler.cs

@ -17,7 +17,9 @@ public class CreatePlaylistGroupHandler(IDbContextFactory<TvContext> dbContextFa @@ -17,7 +17,9 @@ public class CreatePlaylistGroupHandler(IDbContextFactory<TvContext> dbContextFa
return await validation.Apply(profile => PersistPlaylistGroup(dbContext, profile));
}
private static async Task<PlaylistGroupViewModel> PersistPlaylistGroup(TvContext dbContext, PlaylistGroup playlistGroup)
private static async Task<PlaylistGroupViewModel> PersistPlaylistGroup(
TvContext dbContext,
PlaylistGroup playlistGroup)
{
await dbContext.PlaylistGroups.AddAsync(playlistGroup);
await dbContext.SaveChangesAsync();

4
ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs

@ -46,7 +46,7 @@ public class PreviewPlaylistPlayoutHandler( @@ -46,7 +46,7 @@ public class PreviewPlaylistPlayoutHandler(
playoutBuilder.DebugPlaylist = playout.ProgramSchedule.Items[0].Playlist;
await playoutBuilder.Build(playout, PlayoutBuildMode.Reset, cancellationToken);
int maxItems = 0;
var maxItems = 0;
Dictionary<PlaylistItem, List<MediaItem>> map =
await mediaCollectionRepository.GetPlaylistItemMap(playout.ProgramSchedule.Items[0].Playlist);
foreach (PlaylistItem item in playout.ProgramSchedule.Items[0].Playlist.Items)
@ -94,7 +94,7 @@ public class PreviewPlaylistPlayoutHandler( @@ -94,7 +94,7 @@ public class PreviewPlaylistPlayoutHandler(
playoutItem.MediaItem = mediaItem;
}
}
return playout.Items.OrderBy(i => i.StartOffset).Map(Scheduling.Mapper.ProjectToViewModel).ToList();
}

9
ErsatzTV.Application/MediaCollections/Commands/ReplacePlaylistItemsHandler.cs

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
@ -60,10 +59,14 @@ public class ReplacePlaylistItemsHandler(IDbContextFactory<TvContext> dbContextF @@ -60,10 +59,14 @@ public class ReplacePlaylistItemsHandler(IDbContextFactory<TvContext> dbContextF
.SelectOneAsync(b => b.Id, b => b.Id == playlistId)
.Map(o => o.ToValidation<BaseError>("[PlaylistId] does not exist."));
private static Validation<BaseError, Playlist> CollectionTypesMustBeValid(ReplacePlaylistItems request, Playlist playlist) =>
private static Validation<BaseError, Playlist> CollectionTypesMustBeValid(
ReplacePlaylistItems request,
Playlist playlist) =>
request.Items.Map(item => CollectionTypeMustBeValid(item, playlist)).Sequence().Map(_ => playlist);
private static Validation<BaseError, Playlist> CollectionTypeMustBeValid(ReplacePlaylistItem item, Playlist playlist)
private static Validation<BaseError, Playlist> CollectionTypeMustBeValid(
ReplacePlaylistItem item,
Playlist playlist)
{
switch (item.CollectionType)
{

2
ErsatzTV.Application/MediaCollections/Mapper.cs

@ -48,7 +48,7 @@ internal static class Mapper @@ -48,7 +48,7 @@ internal static class Mapper
internal static PlaylistGroupViewModel ProjectToViewModel(PlaylistGroup playlistGroup) =>
new(playlistGroup.Id, playlistGroup.Name, playlistGroup.Playlists.Count);
internal static PlaylistViewModel ProjectToViewModel(Playlist playlist) =>
new(playlist.Id, playlist.PlaylistGroupId, playlist.Name);

4
ErsatzTV.Application/MediaCollections/Queries/GetAllPlaylistGroupsHandler.cs

@ -7,7 +7,9 @@ namespace ErsatzTV.Application.MediaCollections; @@ -7,7 +7,9 @@ namespace ErsatzTV.Application.MediaCollections;
public class GetAllPlaylistGroupsHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<GetAllPlaylistGroups, List<PlaylistGroupViewModel>>
{
public async Task<List<PlaylistGroupViewModel>> Handle(GetAllPlaylistGroups request, CancellationToken cancellationToken)
public async Task<List<PlaylistGroupViewModel>> Handle(
GetAllPlaylistGroups request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);

6
ErsatzTV.Application/MediaItems/Mapper.cs

@ -25,10 +25,10 @@ internal static class Mapper @@ -25,10 +25,10 @@ internal static class Mapper
internal static NamedMediaItemViewModel ProjectToViewModel(OtherVideo otherVideo) =>
new(otherVideo.Id, otherVideo.OtherVideoMetadata.HeadOrNone().Match(ov => ov.Title, () => "???"));
internal static NamedMediaItemViewModel ProjectToViewModel(Song song) =>
new(song.Id, SongTitle(song));
internal static NamedMediaItemViewModel ProjectToViewModel(Image image) =>
new(image.Id, image.ImageMetadata.HeadOrNone().Match(i => i.Title, () => "???"));
@ -68,7 +68,7 @@ internal static class Mapper @@ -68,7 +68,7 @@ internal static class Mapper
private static string SeasonDescription(Season season) =>
season.SeasonNumber == 0 ? "Specials" : $"Season {season.SeasonNumber}";
private static string EpisodeTitle(Episode e)
{
string showTitle = e.Season.Show.ShowMetadata.HeadOrNone()

3
ErsatzTV.Application/Scheduling/Commands/CreateDecoTemplate.cs

@ -2,4 +2,5 @@ using ErsatzTV.Core; @@ -2,4 +2,5 @@ using ErsatzTV.Core;
namespace ErsatzTV.Application.Scheduling;
public record CreateDecoTemplate(int DecoTemplateGroupId, string Name) : IRequest<Either<BaseError, DecoTemplateViewModel>>;
public record CreateDecoTemplate(int DecoTemplateGroupId, string Name)
: IRequest<Either<BaseError, DecoTemplateViewModel>>;

10
ErsatzTV.Application/Scheduling/Commands/ReplaceDecoTemplateItemsHandler.cs

@ -27,7 +27,7 @@ public class ReplaceDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbCont @@ -27,7 +27,7 @@ public class ReplaceDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbCont
decoTemplate.DateUpdated = DateTime.UtcNow;
dbContext.RemoveRange(decoTemplate.Items);
// drop items that are invalid
decoTemplate.Items = request.Items
.Map(i => BuildItem(decoTemplate, i))
@ -60,10 +60,14 @@ public class ReplaceDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbCont @@ -60,10 +60,14 @@ public class ReplaceDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbCont
EndTime = item.EndTime
};
private static Task<Validation<BaseError, DecoTemplate>> Validate(TvContext dbContext, ReplaceDecoTemplateItems request) =>
private static Task<Validation<BaseError, DecoTemplate>> Validate(
TvContext dbContext,
ReplaceDecoTemplateItems request) =>
DecoTemplateMustExist(dbContext, request.DecoTemplateId);
private static Task<Validation<BaseError, DecoTemplate>> DecoTemplateMustExist(TvContext dbContext, int decoTemplateId) =>
private static Task<Validation<BaseError, DecoTemplate>> DecoTemplateMustExist(
TvContext dbContext,
int decoTemplateId) =>
dbContext.DecoTemplates
.Include(b => b.Items)
.SelectOneAsync(b => b.Id, b => b.Id == decoTemplateId)

6
ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs

@ -22,11 +22,11 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -22,11 +22,11 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
UpdateDeco request)
{
existing.Name = request.Name;
// watermark
existing.WatermarkMode = request.WatermarkMode;
existing.WatermarkId = request.WatermarkMode is DecoMode.Override ? request.WatermarkId : null;
// dead air fallback
existing.DeadAirFallbackMode = request.DeadAirFallbackMode;
existing.DeadAirFallbackCollectionType = request.DeadAirFallbackCollectionType;
@ -58,7 +58,7 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -58,7 +58,7 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
dbContext.Decos
.SelectOneAsync(d => d.Id, d => d.Id == request.DecoId)
.Map(o => o.ToValidation<BaseError>("Deco does not exist"));
private static async Task<Validation<BaseError, string>> ValidateDecoName(
TvContext dbContext,
UpdateDeco request)

3
ErsatzTV.Application/Scheduling/Commands/UpdateDefaultDecoHandler.cs

@ -3,7 +3,8 @@ using Microsoft.EntityFrameworkCore; @@ -3,7 +3,8 @@ using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Scheduling;
public class UpdateDefaultDecoHandler(IDbContextFactory<TvContext> dbContextFactory) : IRequestHandler<UpdateDefaultDeco>
public class UpdateDefaultDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<UpdateDefaultDeco>
{
public async Task Handle(UpdateDefaultDeco request, CancellationToken cancellationToken)
{

3
ErsatzTV.Application/Scheduling/Mapper.cs

@ -45,7 +45,7 @@ internal static class Mapper @@ -45,7 +45,7 @@ internal static class Mapper
DateTime endTime = startTime.AddMinutes(templateItem.Block.Minutes);
return new TemplateItemViewModel(templateItem.BlockId, templateItem.Block.Name, startTime, endTime);
}
internal static DecoGroupViewModel ProjectToViewModel(DecoGroup decoGroup) =>
new(decoGroup.Id, decoGroup.Name, decoGroup.Decos.Count);
@ -84,6 +84,7 @@ internal static class Mapper @@ -84,6 +84,7 @@ internal static class Mapper
{
endTime = endTime.AddDays(1);
}
return new DecoTemplateItemViewModel(decoTemplateItem.DecoId, decoTemplateItem.Deco.Name, startTime, endTime);
}

4
ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateByIdHandler.cs

@ -7,7 +7,9 @@ namespace ErsatzTV.Application.Scheduling; @@ -7,7 +7,9 @@ namespace ErsatzTV.Application.Scheduling;
public class GetDecoTemplateByIdHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<GetDecoTemplateById, Option<DecoTemplateViewModel>>
{
public async Task<Option<DecoTemplateViewModel>> Handle(GetDecoTemplateById request, CancellationToken cancellationToken)
public async Task<Option<DecoTemplateViewModel>> Handle(
GetDecoTemplateById request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.DecoTemplates

4
ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateItemsHandler.cs

@ -6,7 +6,9 @@ namespace ErsatzTV.Application.Scheduling; @@ -6,7 +6,9 @@ namespace ErsatzTV.Application.Scheduling;
public class GetDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<GetDecoTemplateItems, List<DecoTemplateItemViewModel>>
{
public async Task<List<DecoTemplateItemViewModel>> Handle(GetDecoTemplateItems request, CancellationToken cancellationToken)
public async Task<List<DecoTemplateItemViewModel>> Handle(
GetDecoTemplateItems request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);

2
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -21,13 +21,13 @@ namespace ErsatzTV.Application.Streaming; @@ -21,13 +21,13 @@ namespace ErsatzTV.Application.Streaming;
public class HlsSessionWorker : IHlsSessionWorker
{
private static int _workAheadCount;
private readonly SemaphoreSlim _slim = new(1, 1);
private readonly IClient _client;
private readonly IConfigElementRepository _configElementRepository;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<HlsSessionWorker> _logger;
private readonly IMediator _mediator;
private readonly SemaphoreSlim _slim = new(1, 1);
private readonly object _sync = new();
private readonly Option<int> _targetFramerate;
private CancellationTokenSource _cancellationTokenSource;

3
ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs

@ -16,14 +16,13 @@ namespace ErsatzTV.Application.Streaming; @@ -16,14 +16,13 @@ namespace ErsatzTV.Application.Streaming;
public class HlsSessionWorkerV2 : IHlsSessionWorker
{
private readonly SemaphoreSlim _slim = new(1, 1);
//private static int _workAheadCount;
private readonly string _host;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<HlsSessionWorkerV2> _logger;
private readonly IMediator _mediator;
private readonly string _scheme;
private readonly SemaphoreSlim _slim = new(1, 1);
private readonly object _sync = new();
private readonly Option<int> _targetFramerate;
private CancellationTokenSource _cancellationTokenSource;

4
ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs

@ -28,7 +28,9 @@ public class GetHlsPlaylistByChannelNumberHandler : @@ -28,7 +28,9 @@ public class GetHlsPlaylistByChannelNumberHandler :
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
DateTimeOffset now = DateTimeOffset.Now;
Validation<BaseError, Parameters> validation = await Validate(dbContext, request, now);
return await validation.Apply(parameters => GetPlaylist(dbContext, request, parameters, now));
return await LanguageExtensions.Apply(
validation,
parameters => GetPlaylist(dbContext, request, parameters, now));
}
private Task<string> GetPlaylist(

10
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -91,7 +91,6 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -91,7 +91,6 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
.ThenInclude(t => t.Items)
.ThenInclude(i => i.Deco)
.ThenInclude(d => d.Watermark)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).EpisodeMetadata)
.ThenInclude(em => em.Subtitles)
@ -186,10 +185,9 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -186,10 +185,9 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
.ThenInclude(t => t.Items)
.ThenInclude(i => i.Deco)
.ThenInclude(d => d.Watermark)
.SelectOneAsync(p => p.ChannelId, p => p.ChannelId == channel.Id);
foreach (var playout in maybePlayout)
foreach (Playout playout in maybePlayout)
{
maybePlayoutItem = await CheckForFallbackFiller(dbContext, channel, playout, now);
}
@ -489,6 +487,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -489,6 +487,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
.GetValue<int>(ConfigElementKey.FFmpegGlobalFallbackFillerId)
.BindT(fillerId => dbContext.FillerPresets.SelectOneAsync(w => w.Id, w => w.Id == fillerId));
}
break;
}
@ -780,12 +779,17 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -780,12 +779,17 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
private sealed record DecoEntries(Option<Deco> TemplateDeco, Option<Deco> PlayoutDeco);
private abstract record WatermarkResult;
private sealed record InheritWatermark : WatermarkResult;
private sealed record DisableWatermark : WatermarkResult;
private sealed record CustomWatermark(ChannelWatermark Watermark) : WatermarkResult;
private abstract record DeadAirFallbackResult;
private sealed record InheritDeadAirFallback : DeadAirFallbackResult;
private sealed record DisableDeadAirFallback : DeadAirFallbackResult;
private sealed record CustomDeadAirFallback(

6
ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumber.cs

@ -2,7 +2,11 @@ @@ -2,7 +2,11 @@
public record GetWrappedProcessByChannelNumber : FFmpegProcessRequest
{
public GetWrappedProcessByChannelNumber(string scheme, string host, string accessToken, string channelNumber) : base(
public GetWrappedProcessByChannelNumber(
string scheme,
string host,
string accessToken,
string channelNumber) : base(
channelNumber,
"ts",
DateTimeOffset.Now,

2
ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs

@ -164,7 +164,7 @@ public class EmbyPathReplacementServiceTests @@ -164,7 +164,7 @@ public class EmbyPathReplacementServiceTests
result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv");
}
[Test]
public async Task EmbyWindows_To_EtvLinux_UncPathWithMixedCaseServerName()
{

2
ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj

@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.9.28">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

2
ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs

@ -164,7 +164,7 @@ public class JellyfinPathReplacementServiceTests @@ -164,7 +164,7 @@ public class JellyfinPathReplacementServiceTests
result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv");
}
[Test]
public async Task JellyfinWindows_To_EtvLinux_UncPathWithMixedCaseServerName()
{

2
ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs

@ -140,7 +140,7 @@ public class PlexPathReplacementServiceTests @@ -140,7 +140,7 @@ public class PlexPathReplacementServiceTests
result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv");
}
[Test]
public async Task PlexWindows_To_EtvLinux_UncPathWithMixedCaseServerName()
{

36
ErsatzTV.Core.Tests/Scheduling/PlayoutTemplateSelectorTests.cs

@ -11,7 +11,7 @@ public static class PlayoutTemplateSelectorTests @@ -11,7 +11,7 @@ public static class PlayoutTemplateSelectorTests
public class GetPlayoutTemplateFor
{
private static readonly TimeSpan Offset = TimeSpan.FromHours(-5);
[Test]
public void LimitToDateRange_Before_Start_Should_Not_Return_Template()
{
@ -33,7 +33,7 @@ public static class PlayoutTemplateSelectorTests @@ -33,7 +33,7 @@ public static class PlayoutTemplateSelectorTests
result.IsNone.Should().BeTrue();
}
[Test]
public void LimitToDateRange_On_Start_Should_Return_Template()
{
@ -55,7 +55,7 @@ public static class PlayoutTemplateSelectorTests @@ -55,7 +55,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_In_Range_Should_Return_Template()
{
@ -77,7 +77,7 @@ public static class PlayoutTemplateSelectorTests @@ -77,7 +77,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_On_End_Should_Return_Template()
{
@ -99,7 +99,7 @@ public static class PlayoutTemplateSelectorTests @@ -99,7 +99,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_After_End_Should_Not_Return_Template()
{
@ -121,7 +121,7 @@ public static class PlayoutTemplateSelectorTests @@ -121,7 +121,7 @@ public static class PlayoutTemplateSelectorTests
result.IsNone.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Before_Invalid_Start_Date_Should_Not_Return_Template()
{
@ -143,7 +143,7 @@ public static class PlayoutTemplateSelectorTests @@ -143,7 +143,7 @@ public static class PlayoutTemplateSelectorTests
result.IsNone.Should().BeTrue();
}
[Test]
public void LimitToDateRange_After_Invalid_Start_Date_Should_Return_Template()
{
@ -165,7 +165,7 @@ public static class PlayoutTemplateSelectorTests @@ -165,7 +165,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Before_Invalid_End_Date_Should_Return_Template()
{
@ -187,7 +187,7 @@ public static class PlayoutTemplateSelectorTests @@ -187,7 +187,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_After_Invalid_End_Date_Should_Not_Return_Template()
{
@ -209,7 +209,7 @@ public static class PlayoutTemplateSelectorTests @@ -209,7 +209,7 @@ public static class PlayoutTemplateSelectorTests
result.IsNone.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Start_Should_Not_Return_Template()
{
@ -231,7 +231,7 @@ public static class PlayoutTemplateSelectorTests @@ -231,7 +231,7 @@ public static class PlayoutTemplateSelectorTests
result.IsNone.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_On_Start_Should_Return_Template()
{
@ -253,7 +253,7 @@ public static class PlayoutTemplateSelectorTests @@ -253,7 +253,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_In_Range_Should_Return_Template()
{
@ -275,7 +275,7 @@ public static class PlayoutTemplateSelectorTests @@ -275,7 +275,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_On_End_Should_Return_Template()
{
@ -297,7 +297,7 @@ public static class PlayoutTemplateSelectorTests @@ -297,7 +297,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_End_Should_Not_Return_Template()
{
@ -319,7 +319,7 @@ public static class PlayoutTemplateSelectorTests @@ -319,7 +319,7 @@ public static class PlayoutTemplateSelectorTests
result.IsNone.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Invalid_Start_Date_Should_Not_Return_Template()
{
@ -341,7 +341,7 @@ public static class PlayoutTemplateSelectorTests @@ -341,7 +341,7 @@ public static class PlayoutTemplateSelectorTests
result.IsNone.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_Invalid_Start_Date_Should_Return_Template()
{
@ -363,7 +363,7 @@ public static class PlayoutTemplateSelectorTests @@ -363,7 +363,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Invalid_End_Date_Should_Return_Template()
{
@ -385,7 +385,7 @@ public static class PlayoutTemplateSelectorTests @@ -385,7 +385,7 @@ public static class PlayoutTemplateSelectorTests
result.IsSome.Should().BeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_Invalid_End_Date_Should_Not_Return_Template()
{

1
ErsatzTV.Core/Domain/Collection/Playlist.cs

@ -6,6 +6,7 @@ public class Playlist @@ -6,6 +6,7 @@ public class Playlist
public int PlaylistGroupId { get; set; }
public PlaylistGroup PlaylistGroup { get; set; }
public string Name { get; set; }
public ICollection<PlaylistItem> Items { get; set; }
//public DateTime DateUpdated { get; set; }
}

1
ErsatzTV.Core/Domain/Scheduling/PlayoutTemplate.cs

@ -18,6 +18,7 @@ public class PlayoutTemplate @@ -18,6 +18,7 @@ public class PlayoutTemplate
public int StartDay { get; set; }
public int EndMonth { get; set; }
public int EndDay { get; set; }
public DateTime DateUpdated { get; set; }
//public ICollection<DateTimeOffset> AdditionalDays { get; set; }

3
ErsatzTV.Core/Emby/EmbyPathReplacementService.cs

@ -86,7 +86,8 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService @@ -86,7 +86,8 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService
foreach (EmbyPathReplacement replacement in maybeReplacement)
{
string finalPath = Regex.Replace(path,
string finalPath = Regex.Replace(
path,
Regex.Escape(replacement.EmbyPath),
Regex.Replace(replacement.LocalPath ?? string.Empty, "\\$[0-9]+", @"$$$0"),
RegexOptions.IgnoreCase);

2
ErsatzTV.Core/ErsatzTV.Core.csproj

@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.9.28">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

2
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -448,7 +448,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -448,7 +448,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Option<TimeSpan>.None,
AudioFilter.None);
var videoFormat = GetVideoFormat(playbackSettings);
string videoFormat = GetVideoFormat(playbackSettings);
var desiredState = new FrameState(
playbackSettings.RealtimeOutput,

2
ErsatzTV.Core/FileSystemLayout.cs

@ -62,7 +62,7 @@ public static class FileSystemLayout @@ -62,7 +62,7 @@ public static class FileSystemLayout
public static readonly string AudioStreamSelectorScriptsFolder =
Path.Combine(ScriptsFolder, "audio-stream-selector");
public static readonly string MacOsOldAppDataFolder = Path.Combine(
Environment.GetEnvironmentVariable("HOME") ?? string.Empty,
".local",

3
ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs

@ -86,7 +86,8 @@ public class JellyfinPathReplacementService : IJellyfinPathReplacementService @@ -86,7 +86,8 @@ public class JellyfinPathReplacementService : IJellyfinPathReplacementService
foreach (JellyfinPathReplacement replacement in maybeReplacement)
{
string finalPath = Regex.Replace(path,
string finalPath = Regex.Replace(
path,
Regex.Escape(replacement.JellyfinPath),
Regex.Replace(replacement.LocalPath ?? string.Empty, "\\$[0-9]+", @"$$$0"),
RegexOptions.IgnoreCase);

3
ErsatzTV.Core/Plex/PlexPathReplacementService.cs

@ -52,7 +52,8 @@ public class PlexPathReplacementService : IPlexPathReplacementService @@ -52,7 +52,8 @@ public class PlexPathReplacementService : IPlexPathReplacementService
foreach (PlexPathReplacement replacement in maybeReplacement)
{
string finalPath = Regex.Replace(path,
string finalPath = Regex.Replace(
path,
Regex.Escape(replacement.PlexPath),
Regex.Replace(replacement.LocalPath ?? string.Empty, "\\$[0-9]+", @"$$$0"),
RegexOptions.IgnoreCase);

2
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutShuffledMediaCollectionEnumerator.cs

@ -16,7 +16,7 @@ public class BlockPlayoutShuffledMediaCollectionEnumerator : IMediaCollectionEnu @@ -16,7 +16,7 @@ public class BlockPlayoutShuffledMediaCollectionEnumerator : IMediaCollectionEnu
CollectionEnumeratorState state)
{
CurrentIncludeInProgramGuide = Option<bool>.None;
_mediaItems = mediaItems;
_mediaItemCount = _mediaItems.Sum(i => 1 + Optional(i.Additional).Flatten().Count());

2
ErsatzTV.Core/Scheduling/CollectionKey.cs

@ -13,7 +13,7 @@ public class CollectionKey : Record<CollectionKey> @@ -13,7 +13,7 @@ public class CollectionKey : Record<CollectionKey>
public int? MediaItemId { get; set; }
public int? PlaylistId { get; set; }
public string FakeCollectionKey { get; set; }
public static CollectionKey ForPlaylistItem(PlaylistItem item) =>
item.CollectionType switch
{

6
ErsatzTV.Core/Scheduling/MediaItemsForCollection.cs

@ -46,36 +46,42 @@ public static class MediaItemsForCollection @@ -46,36 +46,42 @@ public static class MediaItemsForCollection
{
result.AddRange(await mediaCollectionRepository.GetMovie(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.Episode:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetEpisode(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.MusicVideo:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetMusicVideo(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.OtherVideo:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetOtherVideo(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.Song:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetSong(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.Image:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetImage(mediaItemId));
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(collectionKey));

109
ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs

@ -7,10 +7,52 @@ namespace ErsatzTV.Core.Scheduling; @@ -7,10 +7,52 @@ namespace ErsatzTV.Core.Scheduling;
public class PlaylistEnumerator : IMediaCollectionEnumerator
{
private IList<IMediaCollectionEnumerator> _sortedEnumerators;
private IList<bool> _playAll;
private int _enumeratorIndex;
private System.Collections.Generic.HashSet<int> _idsToIncludeInEPG;
private IList<bool> _playAll;
private IList<IMediaCollectionEnumerator> _sortedEnumerators;
private PlaylistEnumerator()
{
}
public void ResetState(CollectionEnumeratorState state) =>
// seed doesn't matter here
State.Index = state.Index;
public CollectionEnumeratorState State { get; private set; }
public Option<MediaItem> Current => _sortedEnumerators[_enumeratorIndex].Current;
public Option<bool> CurrentIncludeInProgramGuide
{
get
{
foreach (MediaItem mediaItem in Current)
{
return _idsToIncludeInEPG.Contains(mediaItem.Id);
}
return Option<bool>.None;
}
}
public int Count { get; private set; }
public Option<TimeSpan> MinimumDuration { get; private set; }
public void MoveNext()
{
_sortedEnumerators[_enumeratorIndex].MoveNext();
// if we aren't playing all, or if we just finished playing all, move to the next enumerator
if (!_playAll[_enumeratorIndex] || _sortedEnumerators[_enumeratorIndex].State.Index == 0)
{
_enumeratorIndex = (_enumeratorIndex + 1) % _sortedEnumerators.Count;
}
State.Index = (State.Index + 1) % Count;
}
public static async Task<PlaylistEnumerator> Create(
IMediaCollectionRepository mediaCollectionRepository,
@ -39,7 +81,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -39,7 +81,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
result._idsToIncludeInEPG.Add(mediaItem.Id);
}
}
var collectionKey = CollectionKey.ForPlaylistItem(playlistItem);
if (enumeratorMap.TryGetValue(collectionKey, out IMediaCollectionEnumerator enumerator))
{
@ -78,7 +120,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -78,7 +120,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
CollectionKey.ForPlaylistItem(playlistItem)),
initState,
// TODO: fix this
randomStartPoint: false,
false,
cancellationToken);
break;
case PlaybackOrder.SeasonEpisode:
@ -107,7 +149,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -107,7 +149,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
result.State = new CollectionEnumeratorState { Seed = state.Seed };
result._enumeratorIndex = 0;
// TODO: how do we end up with index > count?
if (state.Index < result.Count)
{
@ -120,62 +162,19 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -120,62 +162,19 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
return result;
}
private PlaylistEnumerator()
{
}
public void ResetState(CollectionEnumeratorState state) =>
// seed doesn't matter here
State.Index = state.Index;
public CollectionEnumeratorState State { get; private set; }
public Option<MediaItem> Current => _sortedEnumerators[_enumeratorIndex].Current;
public Option<bool> CurrentIncludeInProgramGuide
{
get
{
foreach (MediaItem mediaItem in Current)
{
return _idsToIncludeInEPG.Contains(mediaItem.Id);
}
return Option<bool>.None;
}
}
public int Count { get; private set; }
public Option<TimeSpan> MinimumDuration { get; private set; }
private static int LCM(IEnumerable<int> numbers) => numbers.Aggregate(lcm);
public void MoveNext()
{
_sortedEnumerators[_enumeratorIndex].MoveNext();
// if we aren't playing all, or if we just finished playing all, move to the next enumerator
if (!_playAll[_enumeratorIndex] || _sortedEnumerators[_enumeratorIndex].State.Index == 0)
{
_enumeratorIndex = (_enumeratorIndex + 1) % _sortedEnumerators.Count;
}
State.Index = (State.Index + 1) % Count;
}
private static int LCM(IEnumerable<int> numbers)
{
return numbers.Aggregate(lcm);
}
private static int lcm(int a, int b)
{
return Math.Abs(a * b) / GCD(a, b);
}
private static int lcm(int a, int b) => Math.Abs(a * b) / GCD(a, b);
private static int GCD(int a, int b)
{
while (true)
{
if (b == 0) return a;
if (b == 0)
{
return a;
}
int a1 = a;
a = b;
b = a1 % b;

4
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -19,11 +19,11 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -19,11 +19,11 @@ public class PlayoutBuilder : IPlayoutBuilder
private readonly IArtistRepository _artistRepository;
private readonly IConfigElementRepository _configElementRepository;
private readonly ILocalFileSystem _localFileSystem;
private ILogger<PlayoutBuilder> _logger;
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory;
private readonly ITelevisionRepository _televisionRepository;
private Playlist _debugPlaylist;
private ILogger<PlayoutBuilder> _logger;
public PlayoutBuilder(
IConfigElementRepository configElementRepository,
@ -959,7 +959,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -959,7 +959,7 @@ public class PlayoutBuilder : IPlayoutBuilder
}
state ??= new CollectionEnumeratorState { Seed = Random.Next(), Index = 0 };
if (collectionKey.CollectionType is ProgramScheduleItemCollectionType.Playlist)
{
foreach (int playlistId in Optional(collectionKey.PlaylistId))

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -107,7 +107,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche @@ -107,7 +107,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
GuideGroup = nextState.NextGuideGroup,
FillerKind = (scheduleItem.GuideMode == GuideMode.Filler || contentEnumerator.CurrentIncludeInProgramGuide == false)
FillerKind = scheduleItem.GuideMode == GuideMode.Filler ||
contentEnumerator.CurrentIncludeInProgramGuide == false
? FillerKind.GuideMode
: FillerKind.None,
CustomTitle = scheduleItem.CustomTitle,

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -57,7 +57,8 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul @@ -57,7 +57,8 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
GuideGroup = nextState.NextGuideGroup,
FillerKind = (scheduleItem.GuideMode == GuideMode.Filler || contentEnumerator.CurrentIncludeInProgramGuide == false)
FillerKind = scheduleItem.GuideMode == GuideMode.Filler ||
contentEnumerator.CurrentIncludeInProgramGuide == false
? FillerKind.GuideMode
: FillerKind.None,
CustomTitle = scheduleItem.CustomTitle,

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -65,7 +65,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche @@ -65,7 +65,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
GuideGroup = nextState.NextGuideGroup,
FillerKind = (scheduleItem.GuideMode == GuideMode.Filler || contentEnumerator.CurrentIncludeInProgramGuide == false)
FillerKind = scheduleItem.GuideMode == GuideMode.Filler ||
contentEnumerator.CurrentIncludeInProgramGuide == false
? FillerKind.GuideMode
: FillerKind.None,
CustomTitle = scheduleItem.CustomTitle,

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -42,7 +42,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI @@ -42,7 +42,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
GuideGroup = playoutBuilderState.NextGuideGroup,
FillerKind = (scheduleItem.GuideMode == GuideMode.Filler || contentEnumerator.CurrentIncludeInProgramGuide == false)
FillerKind = scheduleItem.GuideMode == GuideMode.Filler ||
contentEnumerator.CurrentIncludeInProgramGuide == false
? FillerKind.GuideMode
: FillerKind.None,
CustomTitle = scheduleItem.CustomTitle,

4
ErsatzTV.Core/Scheduling/PlayoutTemplateSelector.cs

@ -47,7 +47,7 @@ public static class PlayoutTemplateSelector @@ -47,7 +47,7 @@ public static class PlayoutTemplateSelector
0,
DateTimeKind.Local);
}
if (reverse)
{
(start, end) = (end, start);
@ -61,7 +61,7 @@ public static class PlayoutTemplateSelector @@ -61,7 +61,7 @@ public static class PlayoutTemplateSelector
continue;
}
}
bool daysOfWeek = template.DaysOfWeek.Contains(date.DayOfWeek);
if (!daysOfWeek)
{

2
ErsatzTV.Core/Scheduling/SuperShuffle.cs

@ -4,9 +4,9 @@ namespace ErsatzTV.Core.Scheduling; @@ -4,9 +4,9 @@ namespace ErsatzTV.Core.Scheduling;
public class SuperShuffle
{
private readonly int userSID = 1;
private int randR, halfN, rx, rkey;
private int si, r1, r2, r3, r4;
private readonly int userSID = 1;
public int Shuffle(int inx, int shuffleId, int listSize)
{

4
ErsatzTV.FFmpeg/Encoder/Amf/EncoderH264Amf.cs

@ -6,7 +6,7 @@ public class EncoderH264Amf(Option<string> maybeVideoProfile) : EncoderBase @@ -6,7 +6,7 @@ public class EncoderH264Amf(Option<string> maybeVideoProfile) : EncoderBase
{
public override string Name => "h264_amf";
public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions
{
get
@ -16,7 +16,7 @@ public class EncoderH264Amf(Option<string> maybeVideoProfile) : EncoderBase @@ -16,7 +16,7 @@ public class EncoderH264Amf(Option<string> maybeVideoProfile) : EncoderBase
return
[
"-c:v", Name,
"-profile:v", videoProfile.ToLowerInvariant(),
"-profile:v", videoProfile.ToLowerInvariant()
];
}

4
ErsatzTV.FFmpeg/Encoder/EncoderLibx264.cs

@ -6,13 +6,13 @@ public class EncoderLibx264(Option<string> maybeVideoProfile, Option<string> may @@ -6,13 +6,13 @@ public class EncoderLibx264(Option<string> maybeVideoProfile, Option<string> may
{
public override string Name => "libx264";
public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions
{
get
{
var result = new List<string>(base.OutputOptions);
foreach (string videoProfile in maybeVideoProfile)
{
result.Add("-profile:v");

2
ErsatzTV.FFmpeg/Encoder/EncoderLibx265.cs

@ -18,7 +18,7 @@ public class EncoderLibx265(FrameState currentState, Option<string> maybeVideoPr @@ -18,7 +18,7 @@ public class EncoderLibx265(FrameState currentState, Option<string> maybeVideoPr
"-tag:v", "hvc1",
"-x265-params", "log-level=error"
};
foreach (string videoPreset in maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))

2
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs

@ -12,7 +12,7 @@ public class EncoderH264Nvenc(Option<string> maybeVideoProfile, Option<string> m @@ -12,7 +12,7 @@ public class EncoderH264Nvenc(Option<string> maybeVideoProfile, Option<string> m
get
{
var result = new List<string>(base.OutputOptions);
foreach (string videoProfile in maybeVideoProfile)
{
result.Add("-profile:v");

6
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs

@ -5,8 +5,8 @@ namespace ErsatzTV.FFmpeg.Encoder.Nvenc; @@ -5,8 +5,8 @@ namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderHevcNvenc : EncoderBase
{
private readonly Option<string> _maybeVideoPreset;
private readonly bool _bFrames;
private readonly Option<string> _maybeVideoPreset;
public EncoderHevcNvenc(IHardwareCapabilities hardwareCapabilities, Option<string> maybeVideoPreset)
{
@ -30,7 +30,7 @@ public class EncoderHevcNvenc : EncoderBase @@ -30,7 +30,7 @@ public class EncoderHevcNvenc : EncoderBase
"-tag:v", "hvc1",
"-b_ref_mode", _bFrames ? "1" : "0"
};
foreach (string videoPreset in _maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))
@ -39,7 +39,7 @@ public class EncoderHevcNvenc : EncoderBase @@ -39,7 +39,7 @@ public class EncoderHevcNvenc : EncoderBase
result.Add(videoPreset);
}
}
return result.ToArray();
}
}

4
ErsatzTV.FFmpeg/Encoder/Qsv/EncoderH264Qsv.cs

@ -12,13 +12,13 @@ public class EncoderH264Qsv(Option<string> maybeVideoProfile, Option<string> may @@ -12,13 +12,13 @@ public class EncoderH264Qsv(Option<string> maybeVideoProfile, Option<string> may
get
{
var result = new List<string> { "-c:v", Name, "-low_power", "0", "-look_ahead", "0" };
foreach (string videoProfile in maybeVideoProfile)
{
result.Add("-profile:v");
result.Add(videoProfile.ToLowerInvariant());
}
foreach (string videoPreset in maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))

2
ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs

@ -12,7 +12,7 @@ public class EncoderHevcQsv(Option<string> maybeVideoPreset) : EncoderBase @@ -12,7 +12,7 @@ public class EncoderHevcQsv(Option<string> maybeVideoPreset) : EncoderBase
get
{
var result = new List<string> { "-c:v", Name, "-low_power", "0", "-look_ahead", "0" };
foreach (string videoPreset in maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))

2
ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs

@ -22,7 +22,7 @@ public class EncoderH264Vaapi(Option<string> maybeVideoProfile, RateControlMode @@ -22,7 +22,7 @@ public class EncoderH264Vaapi(Option<string> maybeVideoProfile, RateControlMode
result.Add("-sei");
result.Add("-a53_cc");
foreach (string videoProfile in maybeVideoProfile)
{
result.Add("-profile:v");

4
ErsatzTV.FFmpeg/Encoder/VideoToolbox/EncoderH264VideoToolbox.cs

@ -6,7 +6,7 @@ public class EncoderH264VideoToolbox(Option<string> maybeVideoProfile) : Encoder @@ -6,7 +6,7 @@ public class EncoderH264VideoToolbox(Option<string> maybeVideoProfile) : Encoder
{
public override string Name => "h264_videotoolbox";
public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions
{
get
@ -16,7 +16,7 @@ public class EncoderH264VideoToolbox(Option<string> maybeVideoProfile) : Encoder @@ -16,7 +16,7 @@ public class EncoderH264VideoToolbox(Option<string> maybeVideoProfile) : Encoder
return
[
"-c:v", Name,
"-profile:v", videoProfile.ToLowerInvariant(),
"-profile:v", videoProfile.ToLowerInvariant()
];
}

5
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -156,7 +156,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -156,7 +156,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
isFmp4Hls = segmentTemplate.Contains("m4s");
}
}
if (ffmpegState.OutputFormat == OutputFormatKind.Mp4 || isFmp4Hls)
{
outputOption = new Mp4OutputOptions();
@ -574,7 +574,8 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -574,7 +574,8 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
return desiredState.VideoFormat switch
{
VideoFormat.Hevc => new EncoderLibx265(
currentState with { FrameDataLocation = FrameDataLocation.Software }, desiredState.VideoPreset),
currentState with { FrameDataLocation = FrameDataLocation.Software },
desiredState.VideoPreset),
VideoFormat.H264 => new EncoderLibx264(desiredState.VideoProfile, desiredState.VideoPreset),
VideoFormat.Mpeg2Video => new EncoderMpeg2Video(),

6
ErsatzTV.FFmpeg/Preset/AvailablePresets.cs

@ -6,9 +6,8 @@ public static class AvailablePresets @@ -6,9 +6,8 @@ public static class AvailablePresets
{
public static ICollection<string> ForAccelAndFormat(
HardwareAccelerationMode hardwareAccelerationMode,
string videoFormat)
{
return (hardwareAccelerationMode, videoFormat) switch
string videoFormat) =>
(hardwareAccelerationMode, videoFormat) switch
{
(HardwareAccelerationMode.Nvenc, VideoFormat.H264 or VideoFormat.Hevc) =>
[
@ -27,5 +26,4 @@ public static class AvailablePresets @@ -27,5 +26,4 @@ public static class AvailablePresets
_ => Array.Empty<string>()
};
}
}

4
ErsatzTV.FFmpeg/Preset/VideoPreset.cs

@ -3,11 +3,11 @@ namespace ErsatzTV.FFmpeg.Preset; @@ -3,11 +3,11 @@ namespace ErsatzTV.FFmpeg.Preset;
public static class VideoPreset
{
public const string Unset = "";
// NVENC
public const string LowLatencyHighPerformance = "llhp";
public const string LowLatencyHighQuality = "llhq";
// x264
public const string VeryFast = "veryfast";
}

2
ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.1.0">
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

3
ErsatzTV.Infrastructure/Data/Configurations/Collection/PlaylistConfiguration.cs

@ -9,7 +9,7 @@ public class PlaylistConfiguration : IEntityTypeConfiguration<Playlist> @@ -9,7 +9,7 @@ public class PlaylistConfiguration : IEntityTypeConfiguration<Playlist>
public void Configure(EntityTypeBuilder<Playlist> builder)
{
builder.ToTable("Playlist");
builder.HasIndex(d => new { d.PlaylistGroupId, d.Name })
.IsUnique();
@ -17,6 +17,5 @@ public class PlaylistConfiguration : IEntityTypeConfiguration<Playlist> @@ -17,6 +17,5 @@ public class PlaylistConfiguration : IEntityTypeConfiguration<Playlist>
.WithOne(pi => pi.Playlist)
.HasForeignKey(pi => pi.PlaylistId)
.OnDelete(DeleteBehavior.Cascade);
}
}

2
ErsatzTV.Infrastructure/Data/Configurations/Collection/PlaylistItemConfiguration.cs

@ -9,7 +9,7 @@ public class PlaylistItemConfiguration : IEntityTypeConfiguration<PlaylistItem> @@ -9,7 +9,7 @@ public class PlaylistItemConfiguration : IEntityTypeConfiguration<PlaylistItem>
public void Configure(EntityTypeBuilder<PlaylistItem> builder)
{
builder.ToTable("PlaylistItem");
builder.HasOne(i => i.Collection)
.WithMany()
.HasForeignKey(i => i.CollectionId)

2
ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs

@ -33,7 +33,7 @@ public class ProgramScheduleItemConfiguration : IEntityTypeConfiguration<Program @@ -33,7 +33,7 @@ public class ProgramScheduleItemConfiguration : IEntityTypeConfiguration<Program
.HasForeignKey(i => i.SmartCollectionId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired(false);
builder.HasOne(i => i.Playlist)
.WithMany()
.HasForeignKey(i => i.PlaylistId)

2
ErsatzTV.Infrastructure/Data/Configurations/Scheduling/DecoConfiguration.cs

@ -24,7 +24,7 @@ public class DecoConfiguration : IEntityTypeConfiguration<Deco> @@ -24,7 +24,7 @@ public class DecoConfiguration : IEntityTypeConfiguration<Deco>
.HasForeignKey(d => d.WatermarkId)
.OnDelete(DeleteBehavior.SetNull)
.IsRequired(false);
builder.HasOne(d => d.DeadAirFallbackCollection)
.WithMany()
.HasForeignKey(d => d.DeadAirFallbackCollectionId)

34
ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs

@ -47,7 +47,6 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -47,7 +47,6 @@ public class MediaCollectionRepository : IMediaCollectionRepository
case ProgramScheduleItemCollectionType.Collection:
foreach (int collectionId in Optional(playlistItem.CollectionId))
{
mediaItems.AddRange(await GetMovieItems(dbContext, collectionId));
mediaItems.AddRange(await GetShowItems(dbContext, collectionId));
mediaItems.AddRange(await GetSeasonItems(dbContext, collectionId));
@ -100,7 +99,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -100,7 +99,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Movie:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -132,7 +131,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -132,7 +131,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Song:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -140,7 +139,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -140,7 +139,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Image:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -171,7 +170,6 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -171,7 +170,6 @@ public class MediaCollectionRepository : IMediaCollectionRepository
case ProgramScheduleItemCollectionType.Collection:
foreach (int collectionId in Optional(playlistItem.CollectionId))
{
mediaItems.AddRange(await GetMovieItems(dbContext, collectionId));
mediaItems.AddRange(await GetShowItems(dbContext, collectionId));
mediaItems.AddRange(await GetSeasonItems(dbContext, collectionId));
@ -224,7 +222,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -224,7 +222,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Movie:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -256,7 +254,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -256,7 +254,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Song:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -264,7 +262,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -264,7 +262,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Image:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -501,7 +499,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -501,7 +499,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
return result;
}
public async Task<List<MediaItem>> GetPlaylistItems(int id)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -531,7 +529,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -531,7 +529,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.TelevisionShow:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -539,7 +537,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -539,7 +537,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.TelevisionSeason:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -547,7 +545,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -547,7 +545,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Artist:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -563,7 +561,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -563,7 +561,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.SmartCollection:
foreach (int smartCollectionId in Optional(playlistItem.SmartCollectionId))
{
@ -571,7 +569,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -571,7 +569,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Movie:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -579,7 +577,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -579,7 +577,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Episode:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -603,7 +601,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -603,7 +601,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Song:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -611,7 +609,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -611,7 +609,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
}
break;
case ProgramScheduleItemCollectionType.Image:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
@ -897,7 +895,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -897,7 +895,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
return await GetMovieItems(dbContext, ids);
}
private static Task<List<Movie>> GetMovieItems(TvContext dbContext, IEnumerable<int> movieIds) =>
dbContext.Movies
.Include(m => m.MovieMetadata)

6
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -13,8 +13,8 @@ @@ -13,8 +13,8 @@
<PackageReference Include="Blurhash.ImageSharp" Version="3.0.0" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.13.10" />
<PackageReference Include="Jint" Version="3.1.0" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.13.12" />
<PackageReference Include="Jint" Version="3.1.1" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />
@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.9.28">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

4
ErsatzTV.Infrastructure/Plex/PlexEtag.cs

@ -38,7 +38,7 @@ public class PlexEtag @@ -38,7 +38,7 @@ public class PlexEtag
{
bw.Write((byte)FieldKey.Key);
bw.Write(part.Key);
bw.Write((byte)FieldKey.PartId);
bw.Write(part.Id);
@ -219,7 +219,7 @@ public class PlexEtag @@ -219,7 +219,7 @@ public class PlexEtag
{
bw.Write((byte)FieldKey.Key);
bw.Write(part.Key);
bw.Write((byte)FieldKey.PartId);
bw.Write(part.Id);

2
ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs

@ -27,7 +27,7 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato @@ -27,7 +27,7 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato
CancellationToken cancellationToken)
{
CurrentIncludeInProgramGuide = Option<bool>.None;
_logger = logger;
_cancellationToken = cancellationToken;

10
ErsatzTV.Infrastructure/Search/CustomMultiFieldQueryParser.cs

@ -12,9 +12,6 @@ namespace ErsatzTV.Infrastructure.Search; @@ -12,9 +12,6 @@ namespace ErsatzTV.Infrastructure.Search;
public class CustomMultiFieldQueryParser : MultiFieldQueryParser
{
private readonly LuceneVersion _matchVersion;
private readonly Analyzer _analyzer;
private static readonly List<string> NumericFields =
[
LuceneSearchIndex.MinutesField,
@ -26,6 +23,9 @@ public class CustomMultiFieldQueryParser : MultiFieldQueryParser @@ -26,6 +23,9 @@ public class CustomMultiFieldQueryParser : MultiFieldQueryParser
LuceneSearchIndex.VideoBitDepthField
];
private readonly Analyzer _analyzer;
private readonly LuceneVersion _matchVersion;
public CustomMultiFieldQueryParser(
LuceneVersion matchVersion,
string[] fields,
@ -102,14 +102,14 @@ public class CustomMultiFieldQueryParser : MultiFieldQueryParser @@ -102,14 +102,14 @@ public class CustomMultiFieldQueryParser : MultiFieldQueryParser
{
AllowLeadingWildcard = true
};
string queryToParse = string.IsNullOrWhiteSpace(field)
? $"\"{queryText}\""
: $"{field}:\"{queryText}\"";
return parser.Parse(queryToParse);
}
return base.GetFieldQuery(field, queryText, slop);
}

6
ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs

@ -732,7 +732,7 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -732,7 +732,7 @@ public sealed class LuceneSearchIndex : ISearchIndex
{
doc.Add(new TextField(ShowStudioField, studio.Name, Field.Store.NO));
}
if (!string.IsNullOrWhiteSpace(showMetadata.ContentRating))
{
foreach (string contentRating in (showMetadata.ContentRating ?? string.Empty).Split("/")
@ -1023,7 +1023,7 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -1023,7 +1023,7 @@ public sealed class LuceneSearchIndex : ISearchIndex
{
doc.Add(new TextField(ShowStudioField, studio.Name, Field.Store.NO));
}
if (!string.IsNullOrWhiteSpace(showMetadata.ContentRating))
{
foreach (string contentRating in (showMetadata.ContentRating ?? string.Empty).Split("/")
@ -1033,7 +1033,7 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -1033,7 +1033,7 @@ public sealed class LuceneSearchIndex : ISearchIndex
}
}
}
if (!string.IsNullOrWhiteSpace(metadata.Title))
{
doc.Add(new TextField(TitleField, metadata.Title, Field.Store.NO));

2
ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs

@ -117,7 +117,7 @@ public class ElasticSearchItem : MinimalElasticSearchItem @@ -117,7 +117,7 @@ public class ElasticSearchItem : MinimalElasticSearchItem
[JsonPropertyName(LuceneSearchIndex.ShowStudioField)]
public List<string> ShowStudio { get; set; }
[JsonPropertyName(LuceneSearchIndex.ShowContentRatingField)]
public List<string> ShowContentRating { get; set; }

12
ErsatzTV.Infrastructure/Search/SearchQueryParser.cs

@ -4,17 +4,15 @@ using Lucene.Net.Analysis.Miscellaneous; @@ -4,17 +4,15 @@ using Lucene.Net.Analysis.Miscellaneous;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Search;
using Serilog;
using Query = Lucene.Net.Search.Query;
namespace ErsatzTV.Infrastructure.Search;
public static class SearchQueryParser
{
static SearchQueryParser()
{
BooleanQuery.MaxClauseCount = 1024 * 4;
}
static SearchQueryParser() => BooleanQuery.MaxClauseCount = 1024 * 4;
internal static Analyzer AnalyzerWrapper()
{
using var defaultAnalyzer = new CustomAnalyzer(LuceneSearchIndex.AppLuceneVersion);
@ -47,7 +45,7 @@ public static class SearchQueryParser @@ -47,7 +45,7 @@ public static class SearchQueryParser
public static Query ParseQuery(string query)
{
using Analyzer analyzerWrapper = AnalyzerWrapper();
QueryParser parser = new CustomMultiFieldQueryParser(
LuceneSearchIndex.AppLuceneVersion,
[LuceneSearchIndex.TitleField],
@ -55,7 +53,7 @@ public static class SearchQueryParser @@ -55,7 +53,7 @@ public static class SearchQueryParser
parser.AllowLeadingWildcard = true;
Query result = ParseQuery(query, parser);
Serilog.Log.Logger.Debug("Search query parsed from [{Query}] to [{ParsedQuery}]", query, result.ToString());
Log.Logger.Debug("Search query parsed from [{Query}] to [{ParsedQuery}]", query, result.ToString());
return result;
}

2
ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.1.0">
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

2
ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs

@ -21,10 +21,10 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner @@ -21,10 +21,10 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner
private readonly IClient _client;
private readonly IImageRepository _imageRepository;
private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILogger<ImageFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
public ImageFolderScanner(

2
ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs

@ -21,11 +21,11 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -21,11 +21,11 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
private readonly IArtistRepository _artistRepository;
private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
private readonly ILogger<MusicVideoFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
private readonly IMusicVideoRepository _musicVideoRepository;

6
ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs

@ -20,11 +20,11 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -20,11 +20,11 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
{
private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
private readonly ILogger<OtherVideoFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
private readonly IOtherVideoRepository _otherVideoRepository;
@ -158,7 +158,9 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -158,7 +158,9 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
{
if (allFiles.Any(allTrashedItems.Contains))
{
_logger.LogDebug("Previously trashed items are now present in folder {Folder}", otherVideoFolder);
_logger.LogDebug(
"Previously trashed items are now present in folder {Folder}",
otherVideoFolder);
}
else
{

4
ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs

@ -20,10 +20,10 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -20,10 +20,10 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
{
private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILogger<SongFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
private readonly ISongRepository _songRepository;
@ -140,7 +140,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -140,7 +140,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
libraryPath,
maybeParentFolder,
songFolder);
if (knownFolder.Etag == etag)
{
if (allFiles.Any(allTrashedItems.Contains))

4
ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs

@ -21,11 +21,11 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -21,11 +21,11 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private readonly IClient _client;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
private readonly ILogger<TelevisionFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
private readonly IMetadataRepository _metadataRepository;
private readonly ITelevisionRepository _televisionRepository;
@ -88,7 +88,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -88,7 +88,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
{
await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath);
}
ImmutableHashSet<string> allTrashedItems = await _mediaItemRepository.GetAllTrashedItems(libraryPath);
var allShowFolders = _localFileSystem.ListSubdirectories(libraryPath.Path)

2
ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs

@ -433,7 +433,7 @@ public class PlexTelevisionLibraryScanner : @@ -433,7 +433,7 @@ public class PlexTelevisionLibraryScanner :
result.IsUpdated = true;
}
}
if (fullMetadata.SortTitle != existingMetadata.SortTitle || fullMetadata.Title != existingMetadata.Title)
{
existingMetadata.Title = fullMetadata.Title;

10
ErsatzTV/Controllers/IptvController.cs

@ -101,7 +101,11 @@ public class IptvController : ControllerBase @@ -101,7 +101,11 @@ public class IptvController : ControllerBase
FFmpegProcessRequest request = mode switch
{
"ts-legacy" => new GetConcatProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber),
_ => new GetWrappedProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), Request.Query["access_token"], channelNumber)
_ => new GetWrappedProcessByChannelNumber(
Request.Scheme,
Request.Host.ToString(),
Request.Query["access_token"],
channelNumber)
};
return await _mediator.Send(request)
@ -271,7 +275,7 @@ public class IptvController : ControllerBase @@ -271,7 +275,7 @@ public class IptvController : ControllerBase
var variantPlaylist =
$"{Request.Scheme}://{Request.Host}/iptv/session/{channelNumber}/{file}{AccessTokenQuery()}";
try
{
if (mode == "segmenter-v2")
@ -290,7 +294,7 @@ public class IptvController : ControllerBase @@ -290,7 +294,7 @@ public class IptvController : ControllerBase
ex,
"Failed to return ffmpeg multi-variant playlist; falling back to generated playlist");
}
Option<ResolutionViewModel> maybeResolution = await _mediator.Send(new GetChannelResolution(channelNumber));
string resolution = string.Empty;
foreach (ResolutionViewModel res in maybeResolution)

4
ErsatzTV/ErsatzTV.csproj

@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
<PackageReference Include="FluentValidation" Version="11.9.1" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Heron.MudCalendar" Version="1.1.1" />
<PackageReference Include="Heron.MudCalendar" Version="1.1.2" />
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
<PackageReference Include="LanguageExt.Core" Version="4.4.8" />
<PackageReference Include="Markdig" Version="0.37.0" />
@ -33,7 +33,7 @@ @@ -33,7 +33,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.9.28">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

40
ErsatzTV/Pages/Artist.razor

@ -1,16 +1,16 @@ @@ -1,16 +1,16 @@
@page "/media/music/artists/{ArtistId:int}"
@using ErsatzTV.Extensions
@using System.Globalization
@using ErsatzTV.Application.Artists
@using ErsatzTV.Application.MediaCards
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.ProgramSchedules
@using System.Globalization
@using ErsatzTV.Extensions
@implements IDisposable
@inject IMediator _mediator
@inject IDialogService _dialog
@inject NavigationManager _navigationManager
@inject ILogger<Artist> _logger
@inject ISnackbar _snackbar
@inject IMediator Mediator
@inject IDialogService Dialog
@inject NavigationManager NavigationManager
@inject ILogger<Artist> Logger
@inject ISnackbar Snackbar
<MudContainer MaxWidth="MaxWidth.False" Style="padding: 0" Class="fanart-container">
<div class="fanart-tint"></div>
@ -223,7 +223,7 @@ @@ -223,7 +223,7 @@
private async Task RefreshData()
{
await _mediator.Send(new GetArtistById(ArtistId), _cts.Token).IfSomeAsync(
await Mediator.Send(new GetArtistById(ArtistId), _cts.Token).IfSomeAsync(
vm =>
{
_artist = vm;
@ -233,7 +233,7 @@ @@ -233,7 +233,7 @@
_sortedMoods = _artist.Moods.OrderBy(m => m).ToList();
});
_musicVideos = await _mediator.Send(new GetMusicVideoCards(ArtistId, 1, 100), _cts.Token);
_musicVideos = await Mediator.Send(new GetMusicVideoCards(ArtistId, 1, 100), _cts.Token);
}
private async Task AddToCollection()
@ -241,12 +241,12 @@ @@ -241,12 +241,12 @@
var parameters = new DialogParameters { { "EntityType", "artist" }, { "EntityName", _artist.Name } };
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialog = await _dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result;
if (!result.Canceled && result.Data is MediaCollectionViewModel collection)
{
await _mediator.Send(new AddArtistToCollection(collection.Id, ArtistId), _cts.Token);
_navigationManager.NavigateTo($"media/collections/{collection.Id}");
await Mediator.Send(new AddArtistToCollection(collection.Id, ArtistId), _cts.Token);
NavigationManager.NavigateTo($"media/collections/{collection.Id}");
}
}
@ -255,12 +255,12 @@ @@ -255,12 +255,12 @@
var parameters = new DialogParameters { { "EntityType", "artist" }, { "EntityName", _artist.Name } };
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialog = await _dialog.ShowAsync<AddToScheduleDialog>("Add To Schedule", parameters, options);
IDialogReference dialog = await Dialog.ShowAsync<AddToScheduleDialog>("Add To Schedule", parameters, options);
DialogResult result = await dialog.Result;
if (!result.Canceled && result.Data is ProgramScheduleViewModel schedule)
{
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
_navigationManager.NavigateTo($"schedules/{schedule.Id}/items");
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
NavigationManager.NavigateTo($"schedules/{schedule.Id}/items");
}
}
@ -269,19 +269,19 @@ @@ -269,19 +269,19 @@
var parameters = new DialogParameters { { "EntityType", "music video" }, { "EntityName", musicVideo.Title } };
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialog = await _dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result;
if (!result.Canceled && result.Data is MediaCollectionViewModel collection)
{
var request = new AddMusicVideoToCollection(collection.Id, musicVideo.MusicVideoId);
Either<BaseError, Unit> addResult = await _mediator.Send(request, _cts.Token);
Either<BaseError, Unit> addResult = await Mediator.Send(request, _cts.Token);
addResult.Match(
Left: error =>
{
_snackbar.Add($"Unexpected error adding music video to collection: {error.Value}");
_logger.LogError("Unexpected error adding music video to collection: {Error}", error.Value);
Snackbar.Add($"Unexpected error adding music video to collection: {error.Value}");
Logger.LogError("Unexpected error adding music video to collection: {Error}", error.Value);
},
Right: _ => _snackbar.Add($"Added {musicVideo.Title} to collection {collection.Name}", Severity.Success));
Right: _ => Snackbar.Add($"Added {musicVideo.Title} to collection {collection.Name}", Severity.Success));
}
}

11
ErsatzTV/Pages/ArtistList.razor

@ -1,12 +1,11 @@ @@ -1,12 +1,11 @@
@page "/media/music/artists"
@page "/media/music/artists/page/{PageNumber:int}"
@using ErsatzTV.Extensions
@using ErsatzTV.Application.MediaCards
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.Search
@using ErsatzTV.Extensions
@inherits MultiSelectBase<MusicVideoList>
@inject NavigationManager _navigationManager
@inject ChannelWriter<IBackgroundServiceRequest> _channel
@inject NavigationManager NavigationManager
<MudPaper Square="true" Style="display: flex; height: 64px; left: 240px; padding: 0; position: fixed; right: 0; z-index: 100;">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6">
@ -86,7 +85,7 @@ @@ -86,7 +85,7 @@
PageNumber = 1;
}
_query = _navigationManager.Uri.GetSearchQuery();
_query = NavigationManager.Uri.GetSearchQuery();
await RefreshData();
}
@ -106,7 +105,7 @@ @@ -106,7 +105,7 @@
uri = $"{uri}?{key}={value}";
}
_navigationManager.NavigateTo(uri);
NavigationManager.NavigateTo(uri);
}
private void NextPage()
@ -118,7 +117,7 @@ @@ -118,7 +117,7 @@
uri = $"{uri}?{key}={value}";
}
_navigationManager.NavigateTo(uri);
NavigationManager.NavigateTo(uri);
}
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)

4
ErsatzTV/Pages/BlockEditor.razor

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
@page "/blocks/{Id:int}"
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Application.Search
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Application.Search
@using ErsatzTV.Core.Domain.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager

155
ErsatzTV/Pages/BlockPlayoutEditor.razor

@ -1,89 +1,88 @@ @@ -1,89 +1,88 @@
@page "/playouts/block/{Id:int}"
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Application.Channels
@using ErsatzTV.Application.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<BlockPlayoutEditor> Logger
@inject ISnackbar Snackbar
@inject IMediator Mediator
@inject IEntityLocker EntityLocker;
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h4" Class="mb-4">Edit Block Playout - @_channelName</MudText>
<MudGrid Class="mt-4">
<MudCard Class="mr-6 mb-6" Style="width: 400px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">Playout Templates</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Primary" Href="@($"playouts/{Id}/templates")" Class="mt-4">
Edit Templates
</MudButton>
</MudCardContent>
</MudCard>
<MudCard Class="mr-6 mb-6" Style="width: 400px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">Playout Items and History</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<div>
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Warning" OnClick="@(_ => EraseItems(eraseHistory: false))" Class="mt-4">
Erase Items
</MudButton>
</div>
<div>
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Error" OnClick="@(_ => EraseItems(eraseHistory: true))" Class="mt-4">
Erase Items and History
<MudText Typo="Typo.h4" Class="mb-4">Edit Block Playout - @_channelName</MudText>
<MudGrid Class="mt-4">
<MudCard Class="mr-6 mb-6" Style="width: 400px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">Playout Templates</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Primary" Href="@($"playouts/{Id}/templates")" Class="mt-4">
Edit Templates
</MudButton>
</div>
</MudCardContent>
</MudCard>
<MudCard Class="mr-6 mb-6" Style="width: 400px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">Default Deco</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudElement HtmlTag="div" Class="mt-3">
<MudSwitch T="bool" Label="Enable Default Deco" @bind-Value="_enableDefaultDeco" Color="Color.Primary"/>
</MudElement>
@if (_enableDefaultDeco)
{
<MudElement HtmlTag="div" Class="mt-2">
<MudSelect T="DecoGroupViewModel"
Label="Deco Group"
Value="@_selectedDefaultDecoGroup"
ValueChanged="@(vm => UpdateDefaultDecoTemplateGroupItems(vm))">
@foreach (DecoGroupViewModel decoGroup in _decoGroups)
{
<MudSelectItem Value="@decoGroup">@decoGroup.Name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
</MudCard>
<MudCard Class="mr-6 mb-6" Style="width: 400px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">Playout Items and History</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<div>
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Warning" OnClick="@(_ => EraseItems(eraseHistory: false))" Class="mt-4">
Erase Items
</MudButton>
</div>
<div>
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Error" OnClick="@(_ => EraseItems(eraseHistory: true))" Class="mt-4">
Erase Items and History
</MudButton>
</div>
</MudCardContent>
</MudCard>
<MudCard Class="mr-6 mb-6" Style="width: 400px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">Default Deco</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudElement HtmlTag="div" Class="mt-3">
<MudSwitch T="bool" Label="Enable Default Deco" @bind-Value="_enableDefaultDeco" Color="Color.Primary"/>
</MudElement>
<MudElement HtmlTag="div" Class="mt-2">
<MudSelect Label="Deco"
@bind-Value="_defaultDeco"
For="@(() => _defaultDeco)">
@foreach (DecoViewModel deco in _decos)
{
<MudSelectItem Value="@deco">@deco.Name</MudSelectItem>
}
</MudSelect>
</MudElement>
}
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveDefaultDeco())">
Save Changes
</MudButton>
</MudCardActions>
</MudCard>
</MudGrid>
@if (_enableDefaultDeco)
{
<MudElement HtmlTag="div" Class="mt-2">
<MudSelect T="DecoGroupViewModel"
Label="Deco Group"
Value="@_selectedDefaultDecoGroup"
ValueChanged="@(vm => UpdateDefaultDecoTemplateGroupItems(vm))">
@foreach (DecoGroupViewModel decoGroup in _decoGroups)
{
<MudSelectItem Value="@decoGroup">@decoGroup.Name</MudSelectItem>
}
</MudSelect>
</MudElement>
<MudElement HtmlTag="div" Class="mt-2">
<MudSelect Label="Deco"
@bind-Value="_defaultDeco"
For="@(() => _defaultDeco)">
@foreach (DecoViewModel deco in _decos)
{
<MudSelectItem Value="@deco">@deco.Name</MudSelectItem>
}
</MudSelect>
</MudElement>
}
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveDefaultDeco())">
Save Changes
</MudButton>
</MudCardActions>
</MudCard>
</MudGrid>
</MudContainer>
@code {
@ -119,10 +118,10 @@ @@ -119,10 +118,10 @@
{
_channelName = name;
}
_decoGroups.Clear();
_decoGroups.AddRange(await Mediator.Send(new GetAllDecoGroups(), _cts.Token));
Option<DecoViewModel> maybeDefaultDeco = await Mediator.Send(new GetDecoByPlayoutId(Id), _cts.Token);
foreach (DecoViewModel defaultDeco in maybeDefaultDeco)
{
@ -132,6 +131,7 @@ @@ -132,6 +131,7 @@
_defaultDeco = defaultDeco;
}
}
private async Task UpdateDefaultDecoTemplateGroupItems(DecoGroupViewModel decoGroup)
{
_selectedDefaultDecoGroup = decoGroup;
@ -154,4 +154,5 @@ @@ -154,4 +154,5 @@
int? decoId = _enableDefaultDeco ? _defaultDeco?.Id : null;
await Mediator.Send(new UpdateDefaultDeco(Id, decoId), _cts.Token);
}
}

2
ErsatzTV/Pages/Blocks.razor

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
@page "/blocks"
@using S = System.Collections.Generic
@using ErsatzTV.Application.Scheduling
@using S = System.Collections.Generic
@implements IDisposable
@inject ILogger<Blocks> Logger
@inject ISnackbar Snackbar

4
ErsatzTV/Pages/ChannelEditor.razor

@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
@page "/channels/{Id:int?}"
@page "/channels/add"
@using static Prelude
@using ErsatzTV.Application.Channels
@using ErsatzTV.Application.FFmpegProfiles
@using ErsatzTV.Application.Filler
@using ErsatzTV.Application.Images
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.Templates
@using ErsatzTV.Application.Watermarks
@using ErsatzTV.Application.Channels
@using ErsatzTV.Core.Domain.Filler
@using static Prelude
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<ChannelEditor> Logger

2
ErsatzTV/Pages/Channels.razor

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
@page "/channels"
@using System.Globalization
@using ErsatzTV.Application.Channels
@using ErsatzTV.Application.Configuration
@using ErsatzTV.Application.FFmpegProfiles
@using ErsatzTV.Core.Interfaces.FFmpeg
@using System.Globalization
@implements IDisposable
@inject IDialogService Dialog
@inject IMediator Mediator

18
ErsatzTV/Pages/CollectionEditor.razor

@ -2,10 +2,10 @@ @@ -2,10 +2,10 @@
@page "/media/collections/add"
@using ErsatzTV.Application.MediaCollections
@implements IDisposable
@inject NavigationManager _navigationManager
@inject ILogger<CollectionEditor> _logger
@inject ISnackbar _snackbar
@inject IMediator _mediator
@inject NavigationManager NavigationManager
@inject ILogger<CollectionEditor> Logger
@inject ISnackbar Snackbar
@inject IMediator Mediator
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<div style="max-width: 400px;">
@ -47,7 +47,7 @@ @@ -47,7 +47,7 @@
{
if (IsEdit)
{
Option<MediaCollectionViewModel> maybeCollection = await _mediator.Send(new GetCollectionById(Id), _cts.Token);
Option<MediaCollectionViewModel> maybeCollection = await Mediator.Send(new GetCollectionById(Id), _cts.Token);
maybeCollection.IfSome(
collection =>
{
@ -74,15 +74,15 @@ @@ -74,15 +74,15 @@
_messageStore.Clear();
if (_editContext.Validate())
{
Seq<BaseError> errorMessage = IsEdit ? (await _mediator.Send(new UpdateCollection(Id, _model.Name), _cts.Token)).LeftToSeq() : (await _mediator.Send(new CreateCollection(_model.Name), _cts.Token)).LeftToSeq();
Seq<BaseError> errorMessage = IsEdit ? (await Mediator.Send(new UpdateCollection(Id, _model.Name), _cts.Token)).LeftToSeq() : (await Mediator.Send(new CreateCollection(_model.Name), _cts.Token)).LeftToSeq();
errorMessage.HeadOrNone().Match(
error =>
{
_snackbar.Add(error.Value, Severity.Error);
_logger.LogError("Error saving collection: {Error}", error.Value);
Snackbar.Add(error.Value, Severity.Error);
Logger.LogError("Error saving collection: {Error}", error.Value);
},
() => _navigationManager.NavigateTo(_model.Id > 0 ? $"media/collections/{_model.Id}" : "media/collections"));
() => NavigationManager.NavigateTo(_model.Id > 0 ? $"media/collections/{_model.Id}" : "media/collections"));
}
}

4
ErsatzTV/Pages/Collections.razor

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
@page "/media/collections"
@using ErsatzTV.Extensions
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.Configuration
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Extensions
@implements IDisposable
@inject IDialogService Dialog
@inject IMediator Mediator

287
ErsatzTV/Pages/DecoEditor.razor

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
@page "/decos/{Id:int}"
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Application.Watermarks
@using ErsatzTV.Application.Artists
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Application.Television
@using ErsatzTV.Application.Artists
@using ErsatzTV.Application.Watermarks
@using ErsatzTV.Core.Domain.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@ -13,154 +13,154 @@ @@ -13,154 +13,154 @@
@inject IMediator Mediator
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="display: flex; flex-direction: row">
<MudStack Class="mr-6">
<MudText Typo="Typo.h4" Class="mb-4">Edit Deco</MudText>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardContent>
<MudTextField Label="Name" @bind-Value="_deco.Name" For="@(() => _deco.Name)"/>
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" StartIcon="@Icons.Material.Filled.Save">
Save Changes
</MudButton>
</MudCardActions>
</MudCard>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Watermark</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudSelect Label="Watermark Mode" @bind-Value="_deco.WatermarkMode" For="@(() => _deco.WatermarkMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
<MudSelect Disabled="@(_deco.WatermarkMode != DecoMode.Override)" Label="Watermark Override" @bind-Value="_deco.WatermarkId" For="@(() => _deco.WatermarkId)"
Clearable="true">
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
@foreach (WatermarkViewModel watermark in _watermarks)
{
<MudSelectItem T="int?" Value="@watermark.Id">@watermark.Name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
</MudCard>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardHeader>
<CardHeaderContent>
<MudTooltip Style="max-width: 350px">
<ChildContent>
<MudText Typo="Typo.h6" Class="d-flex align-center justify-center">
Dead Air Fallback
&nbsp;
<MudIcon Icon="@Icons.Material.Filled.Info"/>
</MudText>
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2">When no playout item is found for the current time, *one* item will be randomly selected from this collection and looped and cut to exactly fit until the start of the next playout item.</MudText>
<MudText Typo="Typo.body2" Class="mt-3">This replaces the "Channel is Offline" image that would otherwise display.</MudText>
</TooltipContent>
</MudTooltip>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudSelect Label="Dead Air Fallback Mode" @bind-Value="_deco.DeadAirFallbackMode" For="@(() => _deco.DeadAirFallbackMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
<MudSelect Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Dead Air Fallback Collection Type"
@bind-Value="_deco.DeadAirFallbackCollectionType"
For="@(() => _deco.DeadAirFallbackCollectionType)">
@foreach (ProgramScheduleItemCollectionType collectionType in Enum.GetValues<ProgramScheduleItemCollectionType>())
{
<MudSelectItem Value="@collectionType">@collectionType</MudSelectItem>
}
</MudSelect>
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Collection)
{
<MudSelect Class="mt-3"
T="MediaCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Collection"
@bind-value="_deco.DeadAirFallbackCollection">
@foreach (MediaCollectionViewModel collection in _mediaCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
<MudStack Class="mr-6">
<MudText Typo="Typo.h4" Class="mb-4">Edit Deco</MudText>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardContent>
<MudTextField Label="Name" @bind-Value="_deco.Name" For="@(() => _deco.Name)"/>
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" StartIcon="@Icons.Material.Filled.Save">
Save Changes
</MudButton>
</MudCardActions>
</MudCard>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Watermark</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudSelect Label="Watermark Mode" @bind-Value="_deco.WatermarkMode" For="@(() => _deco.WatermarkMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.MultiCollection)
{
<MudSelect Class="mt-3"
T="MultiCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Multi Collection"
@bind-value="_deco.DeadAirFallbackMultiCollection">
@foreach (MultiCollectionViewModel collection in _multiCollections)
<MudSelect Disabled="@(_deco.WatermarkMode != DecoMode.Override)" Label="Watermark Override" @bind-Value="_deco.WatermarkId" For="@(() => _deco.WatermarkId)"
Clearable="true">
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
@foreach (WatermarkViewModel watermark in _watermarks)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
<MudSelectItem T="int?" Value="@watermark.Id">@watermark.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.SmartCollection)
{
<MudSelect Class="mt-3"
T="SmartCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Smart Collection"
@bind-value="_deco.DeadAirFallbackSmartCollection">
@foreach (SmartCollectionViewModel collection in _smartCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudCardContent>
</MudCard>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardHeader>
<CardHeaderContent>
<MudTooltip Style="max-width: 350px">
<ChildContent>
<MudText Typo="Typo.h6" Class="d-flex align-center justify-center">
Dead Air Fallback
&nbsp;
<MudIcon Icon="@Icons.Material.Filled.Info"/>
</MudText>
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2">When no playout item is found for the current time, *one* item will be randomly selected from this collection and looped and cut to exactly fit until the start of the next playout item.</MudText>
<MudText Typo="Typo.body2" Class="mt-3">This replaces the "Channel is Offline" image that would otherwise display.</MudText>
</TooltipContent>
</MudTooltip>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudSelect Label="Dead Air Fallback Mode" @bind-Value="_deco.DeadAirFallbackMode" For="@(() => _deco.DeadAirFallbackMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Television Show"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel show in _televisionShows)
<MudSelect Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Dead Air Fallback Collection Type"
@bind-Value="_deco.DeadAirFallbackCollectionType"
For="@(() => _deco.DeadAirFallbackCollectionType)">
@foreach (ProgramScheduleItemCollectionType collectionType in Enum.GetValues<ProgramScheduleItemCollectionType>())
{
<MudSelectItem Value="@show">@show.Name</MudSelectItem>
<MudSelectItem Value="@collectionType">@collectionType</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Television Season"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel season in _televisionSeasons)
{
<MudSelectItem Value="@season">@season.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Artist)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Artist"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel artist in _artists)
{
<MudSelectItem Value="@artist">@artist.Name</MudSelectItem>
}
</MudSelect>
}
</MudCardContent>
</MudCard>
</MudStack>
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Collection)
{
<MudSelect Class="mt-3"
T="MediaCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Collection"
@bind-value="_deco.DeadAirFallbackCollection">
@foreach (MediaCollectionViewModel collection in _mediaCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.MultiCollection)
{
<MudSelect Class="mt-3"
T="MultiCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Multi Collection"
@bind-value="_deco.DeadAirFallbackMultiCollection">
@foreach (MultiCollectionViewModel collection in _multiCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.SmartCollection)
{
<MudSelect Class="mt-3"
T="SmartCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Smart Collection"
@bind-value="_deco.DeadAirFallbackSmartCollection">
@foreach (SmartCollectionViewModel collection in _smartCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Television Show"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel show in _televisionShows)
{
<MudSelectItem Value="@show">@show.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Television Season"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel season in _televisionSeasons)
{
<MudSelectItem Value="@season">@season.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Artist)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Artist"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel artist in _artists)
{
<MudSelectItem Value="@artist">@artist.Name</MudSelectItem>
}
</MudSelect>
}
</MudCardContent>
</MudCard>
</MudStack>
</MudContainer>
@code {
@ -273,4 +273,5 @@ @@ -273,4 +273,5 @@
},
() => NavigationManager.NavigateTo("/decos"));
}
}

2
ErsatzTV/Pages/DecoTemplateEditor.razor

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
@page "/deco-templates/{Id:int}"
@using ErsatzTV.Application.Scheduling
@using System.Globalization
@using ErsatzTV.Application.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<DecoTemplateEditor> Logger

2
ErsatzTV/Pages/DecoTemplates.razor

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
@page "/deco-templates"
@using S = System.Collections.Generic
@using ErsatzTV.Application.Scheduling
@using S = System.Collections.Generic
@implements IDisposable
@inject ILogger<DecoTemplates> Logger
@inject ISnackbar Snackbar

2
ErsatzTV/Pages/Decos.razor

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
@page "/decos"
@using S = System.Collections.Generic
@using ErsatzTV.Application.Scheduling
@using S = System.Collections.Generic
@implements IDisposable
@inject ILogger<Decos> Logger
@inject ISnackbar Snackbar

14
ErsatzTV/Pages/EmbyLibrariesEditor.razor

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
@page "/media/sources/emby/{Id:int}/libraries"
@using ErsatzTV.Application.MediaSources
@using ErsatzTV.Application.Emby
@using ErsatzTV.Application.MediaSources
@implements IDisposable
@inject IMediator _mediator
@inject ChannelWriter<IScannerBackgroundServiceRequest> _scannerWorkerChannel
@inject IMediator Mediator
@inject ChannelWriter<IScannerBackgroundServiceRequest> ScannerWorkerChannel
<RemoteMediaSourceLibrariesEditor
Id="@Id"
@ -30,11 +30,11 @@ @@ -30,11 +30,11 @@
libraries.Map(l => new EmbyLibraryPreference(l.Id, l.ShouldSyncItems)).ToList());
private Task<List<RemoteMediaSourceLibraryEditViewModel>> GetLibrariesBySourceId(int mediaSourceId) =>
_mediator.Send(new GetEmbyLibrariesBySourceId(Id))
Mediator.Send(new GetEmbyLibrariesBySourceId(Id))
.Map(list => list.Map(ProjectToEditViewModel).OrderBy(x => x.MediaKind).ThenBy(x => x.Name).ToList());
private Task<Option<RemoteMediaSourceViewModel>> GetMediaSourceById(int mediaSourceId) =>
_mediator.Send(new GetEmbyMediaSourceById(Id))
Mediator.Send(new GetEmbyMediaSourceById(Id))
.MapT(vm => new RemoteMediaSourceViewModel(vm.Id, vm.Name, vm.Address));
private RemoteMediaSourceLibraryEditViewModel ProjectToEditViewModel(EmbyLibraryViewModel library) => new()
@ -47,8 +47,8 @@ @@ -47,8 +47,8 @@
private async Task<Unit> SynchronizeLibraryByIdIfNeeded(RemoteMediaSourceLibrariesEditor.SynchronizeParameters parameters)
{
await _scannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraries(parameters.MediaSourceId), _cts.Token);
await _scannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraryByIdIfNeeded(parameters.LibraryId), _cts.Token);
await ScannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraries(parameters.MediaSourceId), _cts.Token);
await ScannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraryByIdIfNeeded(parameters.LibraryId), _cts.Token);
return Unit.Default;
}

11
ErsatzTV/Pages/EmbyMediaSourceEditor.razor

@ -1,11 +1,8 @@ @@ -1,11 +1,8 @@
@page "/media/sources/emby/edit"
@using ErsatzTV.Core.Emby
@using ErsatzTV.Application.Emby
@using ErsatzTV.Core.Emby
@implements IDisposable
@inject IMediator _mediator
@inject NavigationManager _navigationManager
@inject ISnackbar _snackbar
@inject ILogger<EmbyMediaSourceEditor> _logger
@inject IMediator Mediator
<RemoteMediaSourceEditor
Name="Emby"
@ -23,7 +20,7 @@ @@ -23,7 +20,7 @@
private async Task<Unit> LoadSecrets(RemoteMediaSourceEditViewModel viewModel)
{
EmbySecrets secrets = await _mediator.Send(new GetEmbySecrets(), _cts.Token);
EmbySecrets secrets = await Mediator.Send(new GetEmbySecrets(), _cts.Token);
viewModel.Address = secrets.Address;
viewModel.ApiKey = secrets.ApiKey;
return Unit.Default;
@ -32,7 +29,7 @@ @@ -32,7 +29,7 @@
private async Task<Either<BaseError, Unit>> SaveSecrets(RemoteMediaSourceEditViewModel viewModel)
{
var secrets = new EmbySecrets { Address = viewModel.Address, ApiKey = viewModel.ApiKey };
return await _mediator.Send(new SaveEmbySecrets(secrets), _cts.Token);
return await Mediator.Send(new SaveEmbySecrets(secrets), _cts.Token);
}
}

10
ErsatzTV/Pages/EmbyMediaSources.razor

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
@page "/media/sources/emby"
@using ErsatzTV.Core.Interfaces.Emby
@using ErsatzTV.Application.Emby
@using ErsatzTV.Core.Emby
@using ErsatzTV.Core.Interfaces.Emby
@implements IDisposable
@inject IEmbySecretStore _embySecretStore
@inject ChannelWriter<IScannerBackgroundServiceRequest> _scannerWorkerChannel
@inject IEmbySecretStore EmbySecretStore
@inject ChannelWriter<IScannerBackgroundServiceRequest> ScannerWorkerChannel
<RemoteMediaSources
TViewModel="EmbyMediaSourceViewModel"
@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
GetAllMediaSourcesCommand="@(new GetAllEmbyMediaSources())"
DisconnectCommand="@(new DisconnectEmby())"
RefreshLibrariesCommand="@(mediaSourceId => RefreshLibraries(mediaSourceId))"
SecretStore="@_embySecretStore"/>
SecretStore="@EmbySecretStore"/>
@code {
private readonly CancellationTokenSource _cts = new();
@ -26,6 +26,6 @@ @@ -26,6 +26,6 @@
}
private async Task RefreshLibraries(int mediaSourceId) =>
await _scannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraries(mediaSourceId), _cts.Token);
await ScannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraries(mediaSourceId), _cts.Token);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save