Browse Source

code analysis and cleanup (#1411)

* cleanup scanner project

* cleanup infrastructure projects

* cleanup ffmpeg project

* cleanup core project

* cleanup app project

* cleanup main project

* update dependencies

* code cleanup
pull/1412/head
Jason Dove 2 years ago committed by GitHub
parent
commit
245c4ec359
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .config/dotnet-tools.json
  2. 5
      .editorconfig
  3. 3
      ErsatzTV.Application/.editorconfig
  4. 2
      ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs
  5. 22
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  6. 3
      ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs
  7. 2
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  8. 2
      ErsatzTV.Application/Channels/Mapper.cs
  9. 2
      ErsatzTV.Application/Channels/Queries/GetChannelByIdHandler.cs
  10. 6
      ErsatzTV.Application/Configuration/Commands/UpdatePlayoutSettingsHandler.cs
  11. 2
      ErsatzTV.Application/Configuration/Queries/GetConfigElementByKeyHandler.cs
  12. 3
      ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs
  13. 5
      ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs
  14. 2
      ErsatzTV.Application/Emby/Commands/SaveEmbySecretsHandler.cs
  15. 4
      ErsatzTV.Application/Emby/Commands/SynchronizeEmbyLibrariesHandler.cs
  16. 2
      ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs
  17. 2
      ErsatzTV.Application/ErsatzTV.Application.csproj
  18. 4
      ErsatzTV.Application/FFmpegProfiles/Commands/CopyFFmpegProfileHandler.cs
  19. 2
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  20. 4
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  21. 9
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs
  22. 4
      ErsatzTV.Application/Filler/Commands/DeleteFillerPresetHandler.cs
  23. 4
      ErsatzTV.Application/Filler/Commands/UpdateFillerPresetHandler.cs
  24. 8
      ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCountHandler.cs
  25. 5
      ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs
  26. 2
      ErsatzTV.Application/Jellyfin/Commands/SaveJellyfinSecretsHandler.cs
  27. 2
      ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserIdHandler.cs
  28. 2
      ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinLibrariesHandler.cs
  29. 2
      ErsatzTV.Application/Jellyfin/Queries/GetJellyfinConnectionParametersHandler.cs
  30. 4
      ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs
  31. 2
      ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs
  32. 7
      ErsatzTV.Application/Libraries/Mapper.cs
  33. 2
      ErsatzTV.Application/Logs/Mapper.cs
  34. 2
      ErsatzTV.Application/Maintenance/Commands/ReleaseMemory.cs
  35. 25
      ErsatzTV.Application/MediaCards/Mapper.cs
  36. 1
      ErsatzTV.Application/MediaCards/Queries/GetTelevisionEpisodeCardsHandler.cs
  37. 2
      ErsatzTV.Application/MediaCollections/Commands/AddArtistToCollectionHandler.cs
  38. 2
      ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs
  39. 2
      ErsatzTV.Application/MediaCollections/Commands/AddMovieToCollectionHandler.cs
  40. 2
      ErsatzTV.Application/MediaCollections/Commands/AddMusicVideoToCollectionHandler.cs
  41. 2
      ErsatzTV.Application/MediaCollections/Commands/AddOtherVideoToCollectionHandler.cs
  42. 2
      ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollectionHandler.cs
  43. 2
      ErsatzTV.Application/MediaCollections/Commands/AddShowToCollectionHandler.cs
  44. 2
      ErsatzTV.Application/MediaCollections/Commands/AddSongToCollectionHandler.cs
  45. 2
      ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs
  46. 10
      ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs
  47. 3
      ErsatzTV.Application/MediaCollections/Queries/GetPagedCollectionsHandler.cs
  48. 3
      ErsatzTV.Application/MediaCollections/Queries/GetPagedMultiCollectionsHandler.cs
  49. 3
      ErsatzTV.Application/MediaCollections/Queries/GetPagedSmartCollectionsHandler.cs
  50. 3
      ErsatzTV.Application/MediaCollections/Queries/GetPagedTraktListsHandler.cs
  51. 4
      ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs
  52. 2
      ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfoHandler.cs
  53. 5
      ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs
  54. 6
      ErsatzTV.Application/Movies/Mapper.cs
  55. 3
      ErsatzTV.Application/Movies/MovieViewModel.cs
  56. 2
      ErsatzTV.Application/Playouts/Commands/CreatePlayoutHandler.cs
  57. 4
      ErsatzTV.Application/Playouts/Mapper.cs
  58. 5
      ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs
  59. 2
      ErsatzTV.Application/Plex/Commands/SynchronizePlexLibrariesHandler.cs
  60. 7
      ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs
  61. 2
      ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs
  62. 4
      ErsatzTV.Application/Plex/Queries/GetPlexLibrariesBySourceIdHandler.cs
  63. 6
      ErsatzTV.Application/ProgramSchedules/Commands/CopyProgramScheduleHandler.cs
  64. 4
      ErsatzTV.Application/ProgramSchedules/Commands/DeleteProgramScheduleHandler.cs
  65. 4
      ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
  66. 8
      ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItemsHandler.cs
  67. 2
      ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs
  68. 1
      ErsatzTV.Application/Search/Queries/QuerySearchIndexMoviesHandler.cs
  69. 1
      ErsatzTV.Application/Search/Queries/SearchArtistsHandler.cs
  70. 5
      ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs
  71. 5
      ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs
  72. 5
      ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs
  73. 9
      ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs
  74. 36
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  75. 6
      ErsatzTV.Application/Streaming/PtsAndDuration.cs
  76. 8
      ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs
  77. 8
      ErsatzTV.Application/Streaming/Queries/GetLastPtsDurationHandler.cs
  78. 2
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  79. 11
      ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs
  80. 10
      ErsatzTV.Application/Television/Mapper.cs
  81. 5
      ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs
  82. 2
      ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
  83. 78
      ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs
  84. 2
      ErsatzTV.Core/Domain/Channel.cs
  85. 5
      ErsatzTV.Core/Domain/Collection/Collection.cs
  86. 5
      ErsatzTV.Core/Domain/Collection/EmbyCollection.cs
  87. 5
      ErsatzTV.Core/Domain/Collection/JellyfinCollection.cs
  88. 5
      ErsatzTV.Core/Domain/Collection/MultiCollection.cs
  89. 5
      ErsatzTV.Core/Domain/Collection/SmartCollection.cs
  90. 7
      ErsatzTV.Core/Domain/Collection/TraktListItemGuid.cs
  91. 5
      ErsatzTV.Core/Domain/MediaItem/MediaStream.cs
  92. 6
      ErsatzTV.Core/Domain/Metadata/MetadataGuid.cs
  93. 12
      ErsatzTV.Core/Emby/EmbyPathReplacementService.cs
  94. 2
      ErsatzTV.Core/ErsatzTV.Core.csproj
  95. 9
      ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs
  96. 9
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  97. 18
      ErsatzTV.Core/FFmpeg/FFmpegLocator.cs
  98. 12
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  99. 8
      ErsatzTV.Core/FFmpeg/FFmpegProcess.cs
  100. 3
      ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs
  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.2.1",
"version": "2023.2.0",
"commands": [
"jb"
]

5
.editorconfig

@ -83,3 +83,8 @@ tab_width=4 @@ -83,3 +83,8 @@ tab_width=4
[*.yml]
indent_style = space
indent_size = 2
[*.{cs,vb}]
# disable CA1848: Use the LoggerMessage delegates
dotnet_diagnostic.ca1848.severity = none

3
ErsatzTV.Application/.editorconfig

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
[*.{cs,vb}]
# disable CA1711: Identifiers should not have incorrect suffix
dotnet_diagnostic.ca1711.severity = none

2
ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs

@ -35,7 +35,7 @@ public class DeleteChannelHandler : IRequestHandler<DeleteChannel, Either<BaseEr @@ -35,7 +35,7 @@ public class DeleteChannelHandler : IRequestHandler<DeleteChannel, Either<BaseEr
private async Task<Unit> DoDeletion(TvContext dbContext, Channel channel, CancellationToken cancellationToken)
{
dbContext.Channels.Remove(channel);
await dbContext.SaveChangesAsync();
await dbContext.SaveChangesAsync(cancellationToken);
// delete channel data from channel guide cache
string cacheFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{channel.Number}.xml");

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

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using System.Globalization;
using System.Xml;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
@ -136,10 +137,13 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -136,10 +137,13 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
PlayoutItem finishItem = sorted[finishIndex];
i = finishIndex;
string start = startItem.StartOffset.ToString("yyyyMMddHHmmss zzz").Replace(":", string.Empty);
string start = startItem.StartOffset.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
.Replace(":", string.Empty);
string stop = displayItem.GuideFinishOffset.HasValue
? displayItem.GuideFinishOffset.Value.ToString("yyyyMMddHHmmss zzz").Replace(":", string.Empty)
: finishItem.FinishOffset.ToString("yyyyMMddHHmmss zzz").Replace(":", string.Empty);
? displayItem.GuideFinishOffset.Value.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
.Replace(":", string.Empty)
: finishItem.FinishOffset.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
.Replace(":", string.Empty);
string title = GetTitle(displayItem);
string subtitle = GetSubtitle(displayItem);
@ -182,7 +186,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -182,7 +186,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
if (metadata.Year.HasValue)
{
await xml.WriteStartElementAsync(null, "date", null);
await xml.WriteStringAsync(metadata.Year.Value.ToString());
await xml.WriteStringAsync(metadata.Year.Value.ToString(CultureInfo.InvariantCulture));
await xml.WriteEndElementAsync(); // date
}
@ -220,7 +224,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -220,7 +224,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
if (metadata.Year.HasValue)
{
await xml.WriteStartElementAsync(null, "date", null);
await xml.WriteStringAsync(metadata.Year.Value.ToString());
await xml.WriteStringAsync(metadata.Year.Value.ToString(CultureInfo.InvariantCulture));
await xml.WriteEndElementAsync(); // date
}
@ -370,11 +374,11 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -370,11 +374,11 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
_ => 440
};
if (artworkPath.StartsWith("jellyfin://"))
if (artworkPath.StartsWith("jellyfin://", StringComparison.OrdinalIgnoreCase))
{
artworkPath = JellyfinUrl.PlaceholderProxyForArtwork(artworkPath, artworkKind, height);
}
else if (artworkPath.StartsWith("emby://"))
else if (artworkPath.StartsWith("emby://", StringComparison.OrdinalIgnoreCase))
{
artworkPath = EmbyUrl.PlaceholderProxyForArtwork(artworkPath, artworkKind, height);
}
@ -499,7 +503,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -499,7 +503,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
}).Flatten();
}
private string GetPrioritizedArtworkPath(Metadata metadata)
private static string GetPrioritizedArtworkPath(Metadata metadata)
{
Option<string> maybeArtwork = Optional(metadata.Artwork).Flatten()
.Filter(a => a.ArtworkKind == ArtworkKind.Poster)
@ -517,5 +521,5 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -517,5 +521,5 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
return maybeArtwork.IfNone(string.Empty);
}
private record ContentRating(Option<string> System, string Value);
private sealed record ContentRating(Option<string> System, string Value);
}

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

@ -2,7 +2,6 @@ using System.Data.Common; @@ -2,7 +2,6 @@ using System.Data.Common;
using System.Xml;
using Dapper;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
@ -113,5 +112,5 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList> @@ -113,5 +112,5 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList>
: $"{{RequestBase}}/iptv/logos/{channel.ArtworkPath}.jpg{{AccessTokenUri}}";
// ReSharper disable once ClassNeverInstantiated.Local
private record ChannelResult(string Number, string Name, string Categories, string ArtworkPath);
private sealed record ChannelResult(string Number, string Name, string Categories, string ArtworkPath);
}

2
ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs

@ -92,7 +92,7 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr @@ -92,7 +92,7 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
return ProjectToViewModel(c);
}
private async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, UpdateChannel request) =>
private static async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, UpdateChannel request) =>
(await ChannelMustExist(dbContext, request), ValidateName(request),
await ValidateNumber(dbContext, request),
ValidatePreferredAudioLanguage(request))

2
ErsatzTV.Application/Channels/Mapper.cs

@ -45,6 +45,6 @@ internal static class Mapper @@ -45,6 +45,6 @@ internal static class Mapper
StreamingMode.TransportStreamHybrid => "MPEG-TS",
StreamingMode.HttpLiveStreamingDirect => "HLS Direct",
StreamingMode.HttpLiveStreamingSegmenter => "HLS Segmenter",
_ => throw new ArgumentOutOfRangeException()
_ => throw new ArgumentOutOfRangeException(nameof(channel))
};
}

2
ErsatzTV.Application/Channels/Queries/GetChannelByIdHandler.cs

@ -10,6 +10,6 @@ public class GetChannelByIdHandler : IRequestHandler<GetChannelById, Option<Chan @@ -10,6 +10,6 @@ public class GetChannelByIdHandler : IRequestHandler<GetChannelById, Option<Chan
public GetChannelByIdHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public Task<Option<ChannelViewModel>> Handle(GetChannelById request, CancellationToken cancellationToken) =>
_channelRepository.Get(request.Id)
_channelRepository.GetChannel(request.Id)
.MapT(ProjectToViewModel);
}

6
ErsatzTV.Application/Configuration/Commands/UpdatePlayoutSettingsHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Threading.Channels;
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Playouts;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
@ -45,7 +46,8 @@ public class UpdatePlayoutSettingsHandler : IRequestHandler<UpdatePlayoutSetting @@ -45,7 +46,8 @@ public class UpdatePlayoutSettingsHandler : IRequestHandler<UpdatePlayoutSetting
List<Playout> playouts = await dbContext.Playouts
.Include(p => p.Channel)
.ToListAsync();
foreach (int playoutId in playouts.OrderBy(p => decimal.Parse(p.Channel.Number)).Map(p => p.Id))
foreach (int playoutId in playouts.OrderBy(p => decimal.Parse(p.Channel.Number, CultureInfo.InvariantCulture))
.Map(p => p.Id))
{
await _workerChannel.WriteAsync(new BuildPlayout(playoutId, PlayoutBuildMode.Continue));
}

2
ErsatzTV.Application/Configuration/Queries/GetConfigElementByKeyHandler.cs

@ -13,5 +13,5 @@ public class GetConfigElementByKeyHandler : IRequestHandler<GetConfigElementByKe @@ -13,5 +13,5 @@ public class GetConfigElementByKeyHandler : IRequestHandler<GetConfigElementByKe
public Task<Option<ConfigElementViewModel>> Handle(
GetConfigElementByKey request,
CancellationToken cancellationToken) =>
_configElementRepository.Get(request.Key).MapT(ProjectToViewModel);
_configElementRepository.GetConfigElement(request.Key).MapT(ProjectToViewModel);
}

3
ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
@ -69,7 +70,7 @@ public class CallEmbyCollectionScannerHandler : CallLibraryScannerHandler<Synchr @@ -69,7 +70,7 @@ public class CallEmbyCollectionScannerHandler : CallLibraryScannerHandler<Synchr
{
var arguments = new List<string>
{
"scan-emby-collections", request.EmbyMediaSourceId.ToString()
"scan-emby-collections", request.EmbyMediaSourceId.ToString(CultureInfo.InvariantCulture)
};
if (request.ForceScan)

5
ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Threading.Channels;
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
@ -58,7 +59,7 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler<ISynchron @@ -58,7 +59,7 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler<ISynchron
{
var arguments = new List<string>
{
"scan-emby", request.EmbyLibraryId.ToString()
"scan-emby", request.EmbyLibraryId.ToString(CultureInfo.InvariantCulture)
};
if (request.ForceScan)

2
ErsatzTV.Application/Emby/Commands/SaveEmbySecretsHandler.cs

@ -52,5 +52,5 @@ public class SaveEmbySecretsHandler : IRequestHandler<SaveEmbySecrets, Either<Ba @@ -52,5 +52,5 @@ public class SaveEmbySecretsHandler : IRequestHandler<SaveEmbySecrets, Either<Ba
return Unit.Default;
}
private record Parameters(EmbySecrets Secrets, EmbyServerInformation ServerInformation);
private sealed record Parameters(EmbySecrets Secrets, EmbyServerInformation ServerInformation);
}

4
ErsatzTV.Application/Emby/Commands/SynchronizeEmbyLibrariesHandler.cs

@ -102,9 +102,7 @@ public class SynchronizeEmbyLibrariesHandler : IRequestHandler<SynchronizeEmbyLi @@ -102,9 +102,7 @@ public class SynchronizeEmbyLibrariesHandler : IRequestHandler<SynchronizeEmbyLi
return Unit.Default;
}
private record ConnectionParameters(
EmbyMediaSource EmbyMediaSource,
EmbyConnection ActiveConnection)
private sealed record ConnectionParameters(EmbyMediaSource EmbyMediaSource, EmbyConnection ActiveConnection)
{
public string ApiKey { get; set; }
}

2
ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs

@ -76,7 +76,7 @@ public class GetEmbyConnectionParametersHandler : IRequestHandler<GetEmbyConnect @@ -76,7 +76,7 @@ public class GetEmbyConnectionParametersHandler : IRequestHandler<GetEmbyConnect
.ToValidation<BaseError>("Emby media source requires an api key");
}
private record ConnectionParameters(
private sealed record ConnectionParameters(
EmbyMediaSource EmbyMediaSource,
EmbyConnection ActiveConnection)
{

2
ErsatzTV.Application/ErsatzTV.Application.csproj

@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
<TargetFramework>net7.0</TargetFramework>
<NoWarn>VSTHRD200</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>

4
ErsatzTV.Application/FFmpegProfiles/Commands/CopyFFmpegProfileHandler.cs

@ -23,10 +23,10 @@ public class @@ -23,10 +23,10 @@ public class
_ffmpegProfileRepository.Copy(request.FFmpegProfileId, request.Name)
.Map(ProjectToViewModel);
private Task<Validation<BaseError, CopyFFmpegProfile>> Validate(CopyFFmpegProfile request) =>
private static Task<Validation<BaseError, CopyFFmpegProfile>> Validate(CopyFFmpegProfile request) =>
ValidateName(request).AsTask().MapT(_ => request);
private Validation<BaseError, string> ValidateName(CopyFFmpegProfile request) =>
private static Validation<BaseError, string> ValidateName(CopyFFmpegProfile request) =>
request.NotEmpty(x => x.Name)
.Bind(_ => request.NotLongerThan(50)(x => x.Name));
}

2
ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs

@ -32,7 +32,7 @@ public class CreateFFmpegProfileHandler : @@ -32,7 +32,7 @@ public class CreateFFmpegProfileHandler :
return new CreateFFmpegProfileResult(ffmpegProfile.Id);
}
private async Task<Validation<BaseError, FFmpegProfile>> Validate(
private static async Task<Validation<BaseError, FFmpegProfile>> Validate(
TvContext dbContext,
CreateFFmpegProfile request) =>
(ValidateName(request), ValidateThreadCount(request), await ResolutionMustExist(dbContext, request))

4
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs

@ -20,10 +20,10 @@ public class @@ -20,10 +20,10 @@ public class
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, FFmpegProfile> validation = await Validate(dbContext, request);
return await LanguageExtensions.Apply(validation, p => ApplyUpdateRequest(dbContext, p, request));
return await validation.Apply(p => ApplyUpdateRequest(dbContext, p, request));
}
private async Task<UpdateFFmpegProfileResult> ApplyUpdateRequest(
private static async Task<UpdateFFmpegProfileResult> ApplyUpdateRequest(
TvContext dbContext,
FFmpegProfile p,
UpdateFFmpegProfile update)

9
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
@ -52,10 +53,8 @@ public class UpdateFFmpegSettingsHandler : IRequestHandler<UpdateFFmpegSettings, @@ -52,10 +53,8 @@ public class UpdateFFmpegSettingsHandler : IRequestHandler<UpdateFFmpegSettings,
UseShellExecute = false
};
using var test = new Process
{
StartInfo = startInfo
};
using var test = new Process();
test.StartInfo = startInfo;
test.Start();
string output = await test.StandardOutput.ReadToEndAsync();
@ -71,7 +70,7 @@ public class UpdateFFmpegSettingsHandler : IRequestHandler<UpdateFFmpegSettings, @@ -71,7 +70,7 @@ public class UpdateFFmpegSettingsHandler : IRequestHandler<UpdateFFmpegSettings,
await _configElementRepository.Upsert(ConfigElementKey.FFprobePath, request.Settings.FFprobePath);
await _configElementRepository.Upsert(
ConfigElementKey.FFmpegDefaultProfileId,
request.Settings.DefaultFFmpegProfileId.ToString());
request.Settings.DefaultFFmpegProfileId.ToString(CultureInfo.InvariantCulture));
await _configElementRepository.Upsert(
ConfigElementKey.FFmpegSaveReports,
request.Settings.SaveReports.ToString());

4
ErsatzTV.Application/Filler/Commands/DeleteFillerPresetHandler.cs

@ -17,7 +17,7 @@ public class DeleteFillerPresetHandler : IRequestHandler<DeleteFillerPreset, Eit @@ -17,7 +17,7 @@ public class DeleteFillerPresetHandler : IRequestHandler<DeleteFillerPreset, Eit
DeleteFillerPreset request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, FillerPreset> validation = await FillerPresetMustExist(dbContext, request);
return await validation.Apply(ps => DoDeletion(dbContext, ps));
}
@ -28,7 +28,7 @@ public class DeleteFillerPresetHandler : IRequestHandler<DeleteFillerPreset, Eit @@ -28,7 +28,7 @@ public class DeleteFillerPresetHandler : IRequestHandler<DeleteFillerPreset, Eit
return dbContext.SaveChangesAsync().ToUnit();
}
private Task<Validation<BaseError, FillerPreset>> FillerPresetMustExist(
private static Task<Validation<BaseError, FillerPreset>> FillerPresetMustExist(
TvContext dbContext,
DeleteFillerPreset request) =>
dbContext.FillerPresets

4
ErsatzTV.Application/Filler/Commands/UpdateFillerPresetHandler.cs

@ -17,10 +17,10 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit @@ -17,10 +17,10 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, FillerPreset> validation = await FillerPresetMustExist(dbContext, request);
return await LanguageExtensions.Apply(validation, ps => ApplyUpdateRequest(dbContext, ps, request));
return await validation.Apply(ps => ApplyUpdateRequest(dbContext, ps, request));
}
private async Task<Unit> ApplyUpdateRequest(
private static async Task<Unit> ApplyUpdateRequest(
TvContext dbContext,
FillerPreset existing,
UpdateFillerPreset request)

8
ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCountHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core;
using System.Globalization;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
@ -15,7 +16,10 @@ public class UpdateHDHRTunerCountHandler : IRequestHandler<UpdateHDHRTunerCount, @@ -15,7 +16,10 @@ public class UpdateHDHRTunerCountHandler : IRequestHandler<UpdateHDHRTunerCount,
UpdateHDHRTunerCount request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(_ => _configElementRepository.Upsert(ConfigElementKey.HDHRTunerCount, request.TunerCount.ToString()))
.MapT(
_ => _configElementRepository.Upsert(
ConfigElementKey.HDHRTunerCount,
request.TunerCount.ToString(CultureInfo.InvariantCulture)))
.Bind(v => v.ToEitherAsync());
private static Task<Validation<BaseError, Unit>> Validate(UpdateHDHRTunerCount request) =>

5
ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Threading.Channels;
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
@ -59,7 +60,7 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler<ISync @@ -59,7 +60,7 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler<ISync
{
var arguments = new List<string>
{
"scan-jellyfin", request.JellyfinLibraryId.ToString()
"scan-jellyfin", request.JellyfinLibraryId.ToString(CultureInfo.InvariantCulture)
};
if (request.ForceScan)

2
ErsatzTV.Application/Jellyfin/Commands/SaveJellyfinSecretsHandler.cs

@ -52,5 +52,5 @@ public class SaveJellyfinSecretsHandler : IRequestHandler<SaveJellyfinSecrets, E @@ -52,5 +52,5 @@ public class SaveJellyfinSecretsHandler : IRequestHandler<SaveJellyfinSecrets, E
return Unit.Default;
}
private record Parameters(JellyfinSecrets Secrets, JellyfinServerInformation ServerInformation);
private sealed record Parameters(JellyfinSecrets Secrets, JellyfinServerInformation ServerInformation);
}

2
ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserIdHandler.cs

@ -98,7 +98,7 @@ public class @@ -98,7 +98,7 @@ public class
.ToValidation<BaseError>("Jellyfin media source requires an api key");
}
private record ConnectionParameters(
private sealed record ConnectionParameters(
JellyfinMediaSource JellyfinMediaSource,
JellyfinConnection ActiveConnection)
{

2
ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinLibrariesHandler.cs

@ -104,7 +104,7 @@ public class @@ -104,7 +104,7 @@ public class
return Unit.Default;
}
private record ConnectionParameters(
private sealed record ConnectionParameters(
JellyfinMediaSource JellyfinMediaSource,
JellyfinConnection ActiveConnection)
{

2
ErsatzTV.Application/Jellyfin/Queries/GetJellyfinConnectionParametersHandler.cs

@ -60,7 +60,7 @@ public class GetJellyfinConnectionParametersHandler : IRequestHandler<GetJellyfi @@ -60,7 +60,7 @@ public class GetJellyfinConnectionParametersHandler : IRequestHandler<GetJellyfi
.ToValidation<BaseError>("Jellyfin media source requires an active connection");
}
private record ConnectionParameters(
private sealed record ConnectionParameters(
JellyfinMediaSource JellyfinMediaSource,
JellyfinConnection ActiveConnection);
}

4
ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs

@ -94,7 +94,7 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath, @@ -94,7 +94,7 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
.SelectOneAsync(a => a.Id, a => a.Id == request.TargetLibraryId)
.Map(o => o.ToValidation<BaseError>("LocalLibrary does not exist"));
private async Task<string> GetPath(TvContext dbContext, MediaItem mediaItem) =>
private static async Task<string> GetPath(TvContext dbContext, MediaItem mediaItem) =>
mediaItem switch
{
Movie => await dbContext.Connection.QuerySingleAsync<string>(
@ -115,5 +115,5 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath, @@ -115,5 +115,5 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
_ => null
};
private record Parameters(LibraryPath LibraryPath, LocalLibrary Library);
private sealed record Parameters(LibraryPath LibraryPath, LocalLibrary Library);
}

2
ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs

@ -110,5 +110,5 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase, @@ -110,5 +110,5 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase,
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.ToUpperInvariant();
private record Parameters(LocalLibrary Existing, LocalLibrary Incoming);
private sealed record Parameters(LocalLibrary Existing, LocalLibrary Incoming);
}

7
ErsatzTV.Application/Libraries/Mapper.cs

@ -10,7 +10,12 @@ internal static class Mapper @@ -10,7 +10,12 @@ internal static class Mapper
library switch
{
LocalLibrary l => ProjectToViewModel(l),
PlexLibrary p => new PlexLibraryViewModel(p.Id, p.Name, p.MediaKind, p.MediaSourceId, GetServerName(p.MediaSource)),
PlexLibrary p => new PlexLibraryViewModel(
p.Id,
p.Name,
p.MediaKind,
p.MediaSourceId,
GetServerName(p.MediaSource)),
JellyfinLibrary j => new JellyfinLibraryViewModel(
j.Id,
j.Name,

2
ErsatzTV.Application/Logs/Mapper.cs

@ -3,7 +3,7 @@ using Serilog.Events; @@ -3,7 +3,7 @@ using Serilog.Events;
namespace ErsatzTV.Application.Logs;
internal partial class Mapper
internal sealed partial class Mapper
{
[GeneratedRegex(@"(.*)\[(DBG|INF|WRN|ERR|FTL)\](.*)")]
private static partial Regex LogEntryRegex();

2
ErsatzTV.Application/Maintenance/Commands/ReleaseMemory.cs

@ -2,5 +2,5 @@ namespace ErsatzTV.Application.Maintenance; @@ -2,5 +2,5 @@ namespace ErsatzTV.Application.Maintenance;
public record ReleaseMemory(bool ForceAggressive) : IRequest, IBackgroundServiceRequest
{
public DateTimeOffset RequestTime = DateTimeOffset.Now;
public DateTimeOffset RequestTime { get; } = DateTimeOffset.Now;
}

25
ErsatzTV.Application/MediaCards/Mapper.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core;
using System.Globalization;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Emby;
using ErsatzTV.Core.Extensions;
@ -15,7 +16,7 @@ internal static class Mapper @@ -15,7 +16,7 @@ internal static class Mapper
new(
showMetadata.ShowId,
showMetadata.Title,
showMetadata.Year?.ToString(),
showMetadata.Year?.ToString(CultureInfo.InvariantCulture),
showMetadata.SortTitle,
GetPoster(showMetadata, maybeJellyfin, maybeEmby),
showMetadata.Show.State);
@ -33,7 +34,7 @@ internal static class Mapper @@ -33,7 +34,7 @@ internal static class Mapper
GetSeasonName(season.SeasonNumber),
season.SeasonMetadata.HeadOrNone().Map(sm => GetPoster(sm, maybeJellyfin, maybeEmby))
.IfNone(string.Empty),
season.SeasonNumber == 0 ? "S" : season.SeasonNumber.ToString(),
season.SeasonNumber == 0 ? "S" : season.SeasonNumber.ToString(CultureInfo.InvariantCulture),
season.State);
internal static TelevisionSeasonCardViewModel ProjectToViewModel(
@ -53,7 +54,9 @@ internal static class Mapper @@ -53,7 +54,9 @@ internal static class Mapper
GetSeasonName(seasonMetadata.Season.SeasonNumber),
$"{showTitle}_{seasonMetadata.Season.SeasonNumber:0000}",
GetPoster(seasonMetadata, maybeJellyfin, maybeEmby),
seasonMetadata.Season.SeasonNumber == 0 ? "S" : seasonMetadata.Season.SeasonNumber.ToString(),
seasonMetadata.Season.SeasonNumber == 0
? "S"
: seasonMetadata.Season.SeasonNumber.ToString(CultureInfo.InvariantCulture),
seasonMetadata.Season.State);
}
@ -94,7 +97,7 @@ internal static class Mapper @@ -94,7 +97,7 @@ internal static class Mapper
new(
movieMetadata.MovieId,
movieMetadata.Title,
movieMetadata.Year?.ToString(),
movieMetadata.Year?.ToString(CultureInfo.InvariantCulture),
movieMetadata.SortTitle,
GetPoster(movieMetadata, maybeJellyfin, maybeEmby),
movieMetadata.Movie.State);
@ -181,12 +184,12 @@ internal static class Mapper @@ -181,12 +184,12 @@ internal static class Mapper
{
string artwork = actor.Artwork?.Path ?? string.Empty;
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://"))
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://", StringComparison.OrdinalIgnoreCase))
{
artwork = JellyfinUrl.RelativeProxyForArtwork(artwork)
.SetQueryParam("fillHeight", 440);
}
else if (maybeEmby.IsSome && artwork.StartsWith("emby://"))
else if (maybeEmby.IsSome && artwork.StartsWith("emby://", StringComparison.OrdinalIgnoreCase))
{
artwork = EmbyUrl.RelativeProxyForArtwork(artwork)
.SetQueryParam("maxHeight", 440);
@ -229,12 +232,12 @@ internal static class Mapper @@ -229,12 +232,12 @@ internal static class Mapper
string poster = Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster))
.Match(a => a.Path, string.Empty);
if (maybeJellyfin.IsSome && poster.StartsWith("jellyfin://"))
if (maybeJellyfin.IsSome && poster.StartsWith("jellyfin://", StringComparison.OrdinalIgnoreCase))
{
poster = JellyfinUrl.RelativeProxyForArtwork(poster)
.SetQueryParam("fillHeight", 440);
}
else if (maybeEmby.IsSome && poster.StartsWith("emby://"))
else if (maybeEmby.IsSome && poster.StartsWith("emby://", StringComparison.OrdinalIgnoreCase))
{
poster = EmbyUrl.RelativeProxyForArtwork(poster)
.SetQueryParam("maxHeight", 440);
@ -251,12 +254,12 @@ internal static class Mapper @@ -251,12 +254,12 @@ internal static class Mapper
string thumb = Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail))
.Match(a => a.Path, string.Empty);
if (maybeJellyfin.IsSome && thumb.StartsWith("jellyfin://"))
if (maybeJellyfin.IsSome && thumb.StartsWith("jellyfin://", StringComparison.OrdinalIgnoreCase))
{
thumb = JellyfinUrl.RelativeProxyForArtwork(thumb)
.SetQueryParam("fillHeight", 220);
}
else if (maybeEmby.IsSome && thumb.StartsWith("emby://"))
else if (maybeEmby.IsSome && thumb.StartsWith("emby://", StringComparison.OrdinalIgnoreCase))
{
thumb = EmbyUrl.RelativeProxyForArtwork(thumb)
.SetQueryParam("maxHeight", 220);

1
ErsatzTV.Application/MediaCards/Queries/GetTelevisionEpisodeCardsHandler.cs

@ -4,7 +4,6 @@ using ErsatzTV.Core.Interfaces.Emby; @@ -4,7 +4,6 @@ using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Search;
using static ErsatzTV.Application.MediaCards.Mapper;
namespace ErsatzTV.Application.MediaCards;

2
ErsatzTV.Application/MediaCollections/Commands/AddArtistToCollectionHandler.cs

@ -73,5 +73,5 @@ public class AddArtistToCollectionHandler : @@ -73,5 +73,5 @@ public class AddArtistToCollectionHandler :
.SelectOneAsync(a => a.Id, a => a.Id == request.ArtistId)
.Map(o => o.ToValidation<BaseError>("Artist does not exist"));
private record Parameters(Collection Collection, Artist Artist);
private sealed record Parameters(Collection Collection, Artist Artist);
}

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

@ -75,5 +75,5 @@ public class AddEpisodeToCollectionHandler : @@ -75,5 +75,5 @@ public class AddEpisodeToCollectionHandler :
.SelectOneAsync(e => e.Id, e => e.Id == request.EpisodeId)
.Map(o => o.ToValidation<BaseError>("Episode does not exist"));
private record Parameters(Collection Collection, Episode Episode);
private sealed record Parameters(Collection Collection, Episode Episode);
}

2
ErsatzTV.Application/MediaCollections/Commands/AddMovieToCollectionHandler.cs

@ -73,5 +73,5 @@ public class AddMovieToCollectionHandler : @@ -73,5 +73,5 @@ public class AddMovieToCollectionHandler :
.SelectOneAsync(m => m.Id, e => e.Id == request.MovieId)
.Map(o => o.ToValidation<BaseError>("Movie does not exist"));
private record Parameters(Collection Collection, Movie Movie);
private sealed record Parameters(Collection Collection, Movie Movie);
}

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

@ -75,5 +75,5 @@ public class AddMusicVideoToCollectionHandler : @@ -75,5 +75,5 @@ public class AddMusicVideoToCollectionHandler :
.SelectOneAsync(m => m.Id, e => e.Id == request.MusicVideoId)
.Map(o => o.ToValidation<BaseError>("MusicVideo does not exist"));
private record Parameters(Collection Collection, MusicVideo MusicVideo);
private sealed record Parameters(Collection Collection, MusicVideo MusicVideo);
}

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

@ -75,5 +75,5 @@ public class AddOtherVideoToCollectionHandler : @@ -75,5 +75,5 @@ public class AddOtherVideoToCollectionHandler :
.SelectOneAsync(m => m.Id, e => e.Id == request.OtherVideoId)
.Map(o => o.ToValidation<BaseError>("OtherVideo does not exist"));
private record Parameters(Collection Collection, OtherVideo OtherVideo);
private sealed record Parameters(Collection Collection, OtherVideo OtherVideo);
}

2
ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollectionHandler.cs

@ -73,5 +73,5 @@ public class AddSeasonToCollectionHandler : @@ -73,5 +73,5 @@ public class AddSeasonToCollectionHandler :
.SelectOneAsync(m => m.Id, e => e.Id == request.SeasonId)
.Map(o => o.ToValidation<BaseError>("Season does not exist"));
private record Parameters(Collection Collection, Season Season);
private sealed record Parameters(Collection Collection, Season Season);
}

2
ErsatzTV.Application/MediaCollections/Commands/AddShowToCollectionHandler.cs

@ -73,5 +73,5 @@ public class AddShowToCollectionHandler : @@ -73,5 +73,5 @@ public class AddShowToCollectionHandler :
.SelectOneAsync(m => m.Id, e => e.Id == request.ShowId)
.Map(o => o.ToValidation<BaseError>("Show does not exist"));
private record Parameters(Collection Collection, Show Show);
private sealed record Parameters(Collection Collection, Show Show);
}

2
ErsatzTV.Application/MediaCollections/Commands/AddSongToCollectionHandler.cs

@ -73,5 +73,5 @@ public class AddSongToCollectionHandler : @@ -73,5 +73,5 @@ public class AddSongToCollectionHandler :
.SelectOneAsync(m => m.Id, e => e.Id == request.SongId)
.Map(o => o.ToValidation<BaseError>("Song does not exist"));
private record Parameters(Collection Collection, Song Song);
private sealed record Parameters(Collection Collection, Song Song);
}

2
ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs

@ -72,5 +72,5 @@ public class AddTraktListHandler : TraktCommandBase, IRequestHandler<AddTraktLis @@ -72,5 +72,5 @@ public class AddTraktListHandler : TraktCommandBase, IRequestHandler<AddTraktLis
// match list items (and update in search index)
}
private record Parameters(string User, string List);
private sealed record Parameters(string User, string List);
}

10
ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs

@ -43,7 +43,7 @@ public abstract class TraktCommandBase @@ -43,7 +43,7 @@ public abstract class TraktCommandBase
.SelectOneAsync(c => c.Id, c => c.Id == traktListId)
.Map(o => o.ToValidation<BaseError>($"TraktList {traktListId} does not exist."));
protected async Task<Either<BaseError, TraktList>> SaveList(TvContext dbContext, TraktList list)
protected static async Task<Either<BaseError, TraktList>> SaveList(TvContext dbContext, TraktList list)
{
Option<TraktList> maybeExisting = await dbContext.TraktLists
.Include(l => l.Items)
@ -199,7 +199,7 @@ public abstract class TraktCommandBase @@ -199,7 +199,7 @@ public abstract class TraktCommandBase
return result;
}
private async Task<Option<int>> IdentifyMovie(TvContext dbContext, TraktListItem item)
private static async Task<Option<int>> IdentifyMovie(TvContext dbContext, TraktListItem item)
{
var guids = item.Guids.Map(g => g.Guid).ToList();
@ -234,7 +234,7 @@ public abstract class TraktCommandBase @@ -234,7 +234,7 @@ public abstract class TraktCommandBase
return None;
}
private async Task<Option<int>> IdentifyShow(TvContext dbContext, TraktListItem item)
private static async Task<Option<int>> IdentifyShow(TvContext dbContext, TraktListItem item)
{
var guids = item.Guids.Map(g => g.Guid).ToList();
@ -269,7 +269,7 @@ public abstract class TraktCommandBase @@ -269,7 +269,7 @@ public abstract class TraktCommandBase
return None;
}
private async Task<Option<int>> IdentifySeason(TvContext dbContext, TraktListItem item)
private static async Task<Option<int>> IdentifySeason(TvContext dbContext, TraktListItem item)
{
var guids = item.Guids.Map(g => g.Guid).ToList();
@ -305,7 +305,7 @@ public abstract class TraktCommandBase @@ -305,7 +305,7 @@ public abstract class TraktCommandBase
return None;
}
private async Task<Option<int>> IdentifyEpisode(TvContext dbContext, TraktListItem item)
private static async Task<Option<int>> IdentifyEpisode(TvContext dbContext, TraktListItem item)
{
var guids = item.Guids.Map(g => g.Guid).ToList();

3
ErsatzTV.Application/MediaCollections/Queries/GetPagedCollectionsHandler.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using Dapper;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;

3
ErsatzTV.Application/MediaCollections/Queries/GetPagedMultiCollectionsHandler.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using Dapper;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;

3
ErsatzTV.Application/MediaCollections/Queries/GetPagedSmartCollectionsHandler.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using Dapper;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;

3
ErsatzTV.Application/MediaCollections/Queries/GetPagedTraktListsHandler.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using Dapper;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;

4
ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs

@ -10,7 +10,9 @@ public class GetAllLanguageCodesHandler : IRequestHandler<GetAllLanguageCodes, L @@ -10,7 +10,9 @@ public class GetAllLanguageCodesHandler : IRequestHandler<GetAllLanguageCodes, L
public GetAllLanguageCodesHandler(IMediaItemRepository mediaItemRepository) =>
_mediaItemRepository = mediaItemRepository;
public async Task<List<LanguageCodeViewModel>> Handle(GetAllLanguageCodes request, CancellationToken cancellationToken)
public async Task<List<LanguageCodeViewModel>> Handle(
GetAllLanguageCodes request,
CancellationToken cancellationToken)
{
List<CultureInfo> cultures = await _mediaItemRepository.GetAllLanguageCodeCultures();
return cultures.Map(c => new LanguageCodeViewModel(c.ThreeLetterISOLanguageName, c.EnglishName)).ToList();

2
ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfoHandler.cs

@ -67,7 +67,7 @@ public class GetMediaItemInfoHandler : IRequestHandler<GetMediaItemInfo, Either< @@ -67,7 +67,7 @@ public class GetMediaItemInfoHandler : IRequestHandler<GetMediaItemInfo, Either<
};
var allStreams = version.Streams.OrderBy(s => s.Index).Map(Project).ToList();
// include external subtitles from local libraries
allStreams.AddRange(subtitles.Filter(s => s.SubtitleKind is SubtitleKind.Sidecar).Map(ProjectToStream));

5
ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Threading.Channels;
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
@ -55,7 +56,7 @@ public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler<IScanLoc @@ -55,7 +56,7 @@ public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler<IScanLoc
{
var arguments = new List<string>
{
"scan-local", request.LibraryId.ToString()
"scan-local", request.LibraryId.ToString(CultureInfo.InvariantCulture)
};
if (request.ForceScan)

6
ErsatzTV.Application/Movies/Mapper.cs

@ -19,7 +19,7 @@ internal static class Mapper @@ -19,7 +19,7 @@ internal static class Mapper
MovieMetadata metadata = Optional(movie.MovieMetadata).Flatten().Head();
return new MovieViewModel(
metadata.Title,
metadata.Year?.ToString(),
metadata.Year?.ToString(CultureInfo.InvariantCulture),
metadata.Plot,
metadata.Genres.Map(g => g.Name).ToList(),
metadata.Tags.Map(t => t.Name).ToList(),
@ -65,7 +65,7 @@ internal static class Mapper @@ -65,7 +65,7 @@ internal static class Mapper
string artwork = Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind))
.Match(a => a.Path, string.Empty);
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://"))
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://", StringComparison.OrdinalIgnoreCase))
{
Url url = JellyfinUrl.RelativeProxyForArtwork(artwork);
if (artworkKind is ArtworkKind.Poster or ArtworkKind.Thumbnail)
@ -75,7 +75,7 @@ internal static class Mapper @@ -75,7 +75,7 @@ internal static class Mapper
artwork = url;
}
else if (maybeEmby.IsSome && artwork.StartsWith("emby://"))
else if (maybeEmby.IsSome && artwork.StartsWith("emby://", StringComparison.OrdinalIgnoreCase))
{
Url url = EmbyUrl.RelativeProxyForArtwork(artwork);
if (artworkKind is ArtworkKind.Poster or ArtworkKind.Thumbnail)

3
ErsatzTV.Application/Movies/MovieViewModel.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using System.Globalization;
using ErsatzTV.Application.MediaCards;
using ErsatzTV.Application.MediaCards;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Movies;

2
ErsatzTV.Application/Playouts/Commands/CreatePlayoutHandler.cs

@ -41,7 +41,7 @@ public class CreatePlayoutHandler : IRequestHandler<CreatePlayout, Either<BaseEr @@ -41,7 +41,7 @@ public class CreatePlayoutHandler : IRequestHandler<CreatePlayout, Either<BaseEr
return new CreatePlayoutResponse(playout.Id);
}
private async Task<Validation<BaseError, Playout>> Validate(TvContext dbContext, CreatePlayout request) =>
private static async Task<Validation<BaseError, Playout>> Validate(TvContext dbContext, CreatePlayout request) =>
(await ValidateChannel(dbContext, request), await ValidateProgramSchedule(dbContext, request),
ValidatePlayoutType(request))
.Apply(

4
ErsatzTV.Application/Playouts/Mapper.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain;
using System.Globalization;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Playouts;
@ -81,6 +82,7 @@ internal static class Mapper @@ -81,6 +82,7 @@ internal static class Mapper
private static string GetDisplayDuration(TimeSpan duration) =>
string.Format(
CultureInfo.InvariantCulture,
duration.TotalHours >= 1 ? @"{0:h\:mm\:ss}" : @"{0:mm\:ss}",
duration);
}

5
ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Threading.Channels;
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
@ -58,7 +59,7 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler<ISynchron @@ -58,7 +59,7 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler<ISynchron
{
var arguments = new List<string>
{
"scan-plex", request.PlexLibraryId.ToString()
"scan-plex", request.PlexLibraryId.ToString(CultureInfo.InvariantCulture)
};
if (request.ForceScan)

2
ErsatzTV.Application/Plex/Commands/SynchronizePlexLibrariesHandler.cs

@ -98,7 +98,7 @@ public class @@ -98,7 +98,7 @@ public class
return Unit.Default;
}
private record ConnectionParameters(
private sealed record ConnectionParameters(
PlexMediaSource PlexMediaSource,
PlexConnection ActiveConnection)
{

7
ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Threading.Channels;
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Locking;
@ -57,7 +58,9 @@ public class SynchronizePlexMediaSourcesHandler : IRequestHandler<SynchronizePle @@ -57,7 +58,9 @@ public class SynchronizePlexMediaSourcesHandler : IRequestHandler<SynchronizePle
foreach (PlexMediaSource removed in allExisting.Filter(
s => servers.All(pms => pms.ClientIdentifier != s.ClientIdentifier)))
{
_logger.LogWarning("Deleting removed Plex server {ServerName}!", removed.Id.ToString());
_logger.LogWarning(
"Deleting removed Plex server {ServerName}!",
removed.Id.ToString(CultureInfo.InvariantCulture));
await _mediaSourceRepository.DeletePlex(removed);
}

2
ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs

@ -80,7 +80,7 @@ public class GetPlexConnectionParametersHandler : IRequestHandler<GetPlexConnect @@ -80,7 +80,7 @@ public class GetPlexConnectionParametersHandler : IRequestHandler<GetPlexConnect
.ToValidation<BaseError>("Plex media source requires a token");
}
private record ConnectionParameters(PlexMediaSource PlexMediaSource, PlexConnection ActiveConnection)
private sealed record ConnectionParameters(PlexMediaSource PlexMediaSource, PlexConnection ActiveConnection)
{
public PlexServerAuthToken PlexServerAuthToken { get; set; }
}

4
ErsatzTV.Application/Plex/Queries/GetPlexLibrariesBySourceIdHandler.cs

@ -8,10 +8,8 @@ public class GetPlexLibrariesBySourceIdHandler : IRequestHandler<GetPlexLibrarie @@ -8,10 +8,8 @@ public class GetPlexLibrariesBySourceIdHandler : IRequestHandler<GetPlexLibrarie
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public GetPlexLibrariesBySourceIdHandler(IDbContextFactory<TvContext> dbContextFactory)
{
public GetPlexLibrariesBySourceIdHandler(IDbContextFactory<TvContext> dbContextFactory) =>
_dbContextFactory = dbContextFactory;
}
public async Task<List<PlexLibraryViewModel>> Handle(
GetPlexLibrariesBySourceId request,

6
ErsatzTV.Application/ProgramSchedules/Commands/CopyProgramScheduleHandler.cs

@ -23,9 +23,7 @@ public class @@ -23,9 +23,7 @@ public class
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, ProgramSchedule> validation = await Validate(dbContext, request);
return await LanguageExtensions.Apply(
validation,
p => PerformCopy(dbContext, p, request, cancellationToken));
return await validation.Apply(p => PerformCopy(dbContext, p, request, cancellationToken));
}
catch (Exception ex)
{
@ -33,7 +31,7 @@ public class @@ -33,7 +31,7 @@ public class
}
}
private async Task<ProgramScheduleViewModel> PerformCopy(
private static async Task<ProgramScheduleViewModel> PerformCopy(
TvContext dbContext,
ProgramSchedule schedule,
CopyProgramSchedule request,

4
ErsatzTV.Application/ProgramSchedules/Commands/DeleteProgramScheduleHandler.cs

@ -17,7 +17,7 @@ public class DeleteProgramScheduleHandler : IRequestHandler<DeleteProgramSchedul @@ -17,7 +17,7 @@ public class DeleteProgramScheduleHandler : IRequestHandler<DeleteProgramSchedul
DeleteProgramSchedule request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, ProgramSchedule> validation = await ProgramScheduleMustExist(dbContext, request);
return await validation.Apply(ps => DoDeletion(dbContext, ps));
}
@ -28,7 +28,7 @@ public class DeleteProgramScheduleHandler : IRequestHandler<DeleteProgramSchedul @@ -28,7 +28,7 @@ public class DeleteProgramScheduleHandler : IRequestHandler<DeleteProgramSchedul
return dbContext.SaveChangesAsync().ToUnit();
}
private Task<Validation<BaseError, ProgramSchedule>> ProgramScheduleMustExist(
private static Task<Validation<BaseError, ProgramSchedule>> ProgramScheduleMustExist(
TvContext dbContext,
DeleteProgramSchedule request) =>
dbContext.ProgramSchedules

4
ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs

@ -111,7 +111,7 @@ public abstract class ProgramScheduleItemCommandBase @@ -111,7 +111,7 @@ public abstract class ProgramScheduleItemCommandBase
return programSchedule;
}
protected Validation<BaseError, ProgramSchedule> CollectionTypeMustBeValid(
protected static Validation<BaseError, ProgramSchedule> CollectionTypeMustBeValid(
IProgramScheduleItemRequest item,
ProgramSchedule programSchedule)
{
@ -166,7 +166,7 @@ public abstract class ProgramScheduleItemCommandBase @@ -166,7 +166,7 @@ public abstract class ProgramScheduleItemCommandBase
return programSchedule;
}
protected ProgramScheduleItem BuildItem(
protected static ProgramScheduleItem BuildItem(
ProgramSchedule programSchedule,
int index,
IProgramScheduleItemRequest item) =>

8
ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItemsHandler.cs

@ -29,7 +29,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase @@ -29,7 +29,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, ProgramSchedule> validation = await Validate(dbContext, request);
return await validation.Apply(ps => PersistItems(dbContext, request, ps));
return await LanguageExtensions.Apply(validation, ps => PersistItems(dbContext, request, ps));
}
private async Task<IEnumerable<ProgramScheduleItemViewModel>> PersistItems(
@ -51,7 +51,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase @@ -51,7 +51,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase
return programSchedule.Items.Map(ProjectToViewModel);
}
private Task<Validation<BaseError, ProgramSchedule>> Validate(
private static Task<Validation<BaseError, ProgramSchedule>> Validate(
TvContext dbContext,
ReplaceProgramScheduleItems request) =>
ProgramScheduleMustExist(dbContext, request.ProgramScheduleId)
@ -66,7 +66,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase @@ -66,7 +66,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase
request.Items.Map(item => PlayoutModeMustBeValid(item, programSchedule)).Sequence()
.Map(_ => programSchedule);
private Validation<BaseError, ProgramSchedule> CollectionTypesMustBeValid(
private static Validation<BaseError, ProgramSchedule> CollectionTypesMustBeValid(
ReplaceProgramScheduleItems request,
ProgramSchedule programSchedule) =>
request.Items.Map(item => CollectionTypeMustBeValid(item, programSchedule)).Sequence()
@ -123,7 +123,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase @@ -123,7 +123,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase
.ToValidation<BaseError>("A collection must not use multiple playback orders");
}
private record CollectionKey(
private sealed record CollectionKey(
ProgramScheduleItemCollectionType CollectionType,
int? CollectionId,
int? MediaItemId,

2
ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs

@ -57,7 +57,7 @@ public class GetProgramScheduleItemsHandler : @@ -57,7 +57,7 @@ public class GetProgramScheduleItemsHandler :
}
// shuffled schedule items supports a limited set of properly values
private ProgramScheduleItemViewModel EnforceProperties(
private static ProgramScheduleItemViewModel EnforceProperties(
Option<ProgramSchedule> maybeProgramSchedule,
ProgramScheduleItemViewModel item)
{

1
ErsatzTV.Application/Search/Queries/QuerySearchIndexMoviesHandler.cs

@ -4,7 +4,6 @@ using ErsatzTV.Core.Domain; @@ -4,7 +4,6 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using ErsatzTV.Core.Search;
using LanguageExt.UnsafeValueAccess;
using static ErsatzTV.Application.MediaCards.Mapper;
namespace ErsatzTV.Application.Search;

1
ErsatzTV.Application/Search/Queries/SearchArtistsHandler.cs

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
using Dapper;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;

5
ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs

@ -19,7 +19,10 @@ public class SearchCollectionsHandler : IRequestHandler<SearchCollections, List< @@ -19,7 +19,10 @@ public class SearchCollectionsHandler : IRequestHandler<SearchCollections, List<
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.Collections
.AsNoTracking()
.Where(c => EF.Functions.Like(EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), $"%{request.Query}%"))
.Where(
c => EF.Functions.Like(
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
$"%{request.Query}%"))
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
.Take(10)
.ToListAsync(cancellationToken)

5
ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs

@ -19,7 +19,10 @@ public class SearchMultiCollectionsHandler : IRequestHandler<SearchMultiCollecti @@ -19,7 +19,10 @@ public class SearchMultiCollectionsHandler : IRequestHandler<SearchMultiCollecti
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.MultiCollections
.AsNoTracking()
.Where(c => EF.Functions.Like(EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), $"%{request.Query}%"))
.Where(
c => EF.Functions.Like(
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
$"%{request.Query}%"))
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
.Take(10)
.ToListAsync(cancellationToken)

5
ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs

@ -19,7 +19,10 @@ public class SearchSmartCollectionsHandler : IRequestHandler<SearchSmartCollecti @@ -19,7 +19,10 @@ public class SearchSmartCollectionsHandler : IRequestHandler<SearchSmartCollecti
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.SmartCollections
.AsNoTracking()
.Where(c => EF.Functions.Like(EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), $"%{request.Query}%"))
.Where(
c => EF.Functions.Like(
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
$"%{request.Query}%"))
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
.Take(10)
.ToListAsync(cancellationToken)

9
ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using System.Globalization;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
@ -30,10 +31,8 @@ public class SearchTelevisionShowsHandler : IRequestHandler<SearchTelevisionShow @@ -30,10 +31,8 @@ public class SearchTelevisionShowsHandler : IRequestHandler<SearchTelevisionShow
.Map(list => list.Map(ToNamedMediaItem).ToList());
}
private static NamedMediaItemViewModel ToNamedMediaItem(ShowMetadata show)
{
return new NamedMediaItemViewModel(
private static NamedMediaItemViewModel ToNamedMediaItem(ShowMetadata show) =>
new(
show.ShowId,
$"{show.Title} ({(show.Year.HasValue ? show.Year.Value.ToString() : "???")})");
}
$"{show.Title} ({(show.Year.HasValue ? show.Year.Value.ToString(CultureInfo.InvariantCulture) : "???")})");
}

36
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Timers;
using Bugsnag;
@ -27,13 +28,14 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -27,13 +28,14 @@ public class HlsSessionWorker : IHlsSessionWorker
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly object _sync = new();
private string _channelNumber;
private bool _disposedValue;
private bool _hasWrittenSegments;
private DateTimeOffset _lastAccess;
private DateTimeOffset _lastDelete = DateTimeOffset.MinValue;
private HlsSessionState _state;
private Option<int> _targetFramerate;
private Timer _timer;
private DateTimeOffset _transcodedUntil;
private HlsSessionState _state;
public HlsSessionWorker(
IHlsPlaylistFilter hlsPlaylistFilter,
@ -100,9 +102,12 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -100,9 +102,12 @@ public class HlsSessionWorker : IHlsSessionWorker
return None;
}
public void PlayoutUpdated()
public void PlayoutUpdated() => _state = HlsSessionState.PlayoutUpdated;
public void Dispose()
{
_state = HlsSessionState.PlayoutUpdated;
Dispose(true);
GC.SuppressFinalize(this);
}
public async Task Run(string channelNumber, TimeSpan idleTimeout, CancellationToken incomingCancellationToken)
@ -198,10 +203,23 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -198,10 +203,23 @@ public class HlsSessionWorker : IHlsSessionWorker
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_timer.Dispose();
}
_disposedValue = true;
}
}
private HlsSessionState NextState(HlsSessionState state, PlayoutItemProcessModel processModel)
{
bool isComplete = processModel?.IsComplete == true;
HlsSessionState result = state switch
{
// playout updates should have the channel start over, transcode method will throttle if needed
@ -224,7 +242,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -224,7 +242,7 @@ public class HlsSessionWorker : IHlsSessionWorker
// realtime will always complete items, so start next at zero
HlsSessionState.ZeroAndRealtime => HlsSessionState.ZeroAndRealtime,
// this will never happen with the enum
_ => throw new InvalidOperationException()
};
@ -277,7 +295,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -277,7 +295,7 @@ public class HlsSessionWorker : IHlsSessionWorker
// _logger.LogInformation("PTS offset: {PtsOffset}", ptsOffset);
_logger.LogInformation("HLS session state: {State}", _state);
DateTimeOffset now = _state is HlsSessionState.SeekAndWorkAhead
? DateTimeOffset.Now
: _transcodedUntil.AddSeconds(_state is HlsSessionState.SeekAndRealtime ? 0 : 1);
@ -453,7 +471,9 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -453,7 +471,9 @@ public class HlsSessionWorker : IHlsSessionWorker
file =>
{
string fileName = Path.GetFileName(file);
var sequenceNumber = int.Parse(fileName.Replace("live", string.Empty).Split('.')[0]);
var sequenceNumber = int.Parse(
fileName.Replace("live", string.Empty).Split('.')[0],
CultureInfo.InvariantCulture);
return new Segment(file, sequenceNumber);
})
.ToList();
@ -542,5 +562,5 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -542,5 +562,5 @@ public class HlsSessionWorker : IHlsSessionWorker
_channelNumber,
"live.m3u8");
private record Segment(string File, int SequenceNumber);
private sealed record Segment(string File, int SequenceNumber);
}

6
ErsatzTV.Application/Streaming/PtsAndDuration.cs

@ -1,11 +1,13 @@ @@ -1,11 +1,13 @@
namespace ErsatzTV.Application.Streaming;
using System.Globalization;
namespace ErsatzTV.Application.Streaming;
public record PtsAndDuration(long Pts, long Duration)
{
public static PtsAndDuration From(string ffprobeLine)
{
string[] split = ffprobeLine.Split("|");
var left = long.Parse(split[0]);
var left = long.Parse(split[0], CultureInfo.InvariantCulture);
if (!long.TryParse(split[1], out long right))
{
// some durations are N/A, so we have to guess at something

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

@ -55,7 +55,7 @@ public class GetHlsPlaylistByChannelNumberHandler : @@ -55,7 +55,7 @@ public class GetHlsPlaylistByChannelNumberHandler :
".AsTask();
}
private Task<Validation<BaseError, Parameters>> Validate(
private static Task<Validation<BaseError, Parameters>> Validate(
TvContext dbContext,
GetHlsPlaylistByChannelNumber request,
DateTimeOffset now) =>
@ -105,9 +105,9 @@ public class GetHlsPlaylistByChannelNumberHandler : @@ -105,9 +105,9 @@ public class GetHlsPlaylistByChannelNumberHandler :
return index;
}
private record ChannelIndexKey(int ChannelId);
private sealed record ChannelIndexKey(int ChannelId);
private record ChannelIndexRecord(long StartTicks, long Index);
private sealed record ChannelIndexRecord(long StartTicks, long Index);
private record Parameters(Channel Channel, PlayoutItem PlayoutItem);
private sealed record Parameters(Channel Channel, PlayoutItem PlayoutItem);
}

8
ErsatzTV.Application/Streaming/Queries/GetLastPtsDurationHandler.cs

@ -137,9 +137,9 @@ public class GetLastPtsDurationHandler : IRequestHandler<GetLastPtsDuration, Eit @@ -137,9 +137,9 @@ public class GetLastPtsDurationHandler : IRequestHandler<GetLastPtsDuration, Eit
}
}
private record RequestParameters(string ChannelNumber, string FFprobePath);
private sealed record RequestParameters(string ChannelNumber, string FFprobePath);
private record TroubleshootingData(IEnumerable<FileInfo> Files, string Playlist, string ProbeOutput)
private sealed record TroubleshootingData(IEnumerable<FileInfo> Files, string Playlist, string ProbeOutput)
{
public string Serialize()
{
@ -151,8 +151,8 @@ public class GetLastPtsDurationHandler : IRequestHandler<GetLastPtsDuration, Eit @@ -151,8 +151,8 @@ public class GetLastPtsDurationHandler : IRequestHandler<GetLastPtsDuration, Eit
return JsonConvert.SerializeObject(data);
}
private record FileData(string FileName, long Bytes, DateTime LastWriteTimeUtc);
private sealed record FileData(string FileName, long Bytes, DateTime LastWriteTimeUtc);
private record InternalData(List<FileData> Files, string EncodedPlaylist, string EncodedProbeOutput);
private sealed record InternalData(List<FileData> Files, string EncodedPlaylist, string EncodedProbeOutput);
}
}

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

@ -545,5 +545,5 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -545,5 +545,5 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
};
}
private record PlayoutItemWithPath(PlayoutItem PlayoutItem, string Path);
private sealed record PlayoutItemWithPath(PlayoutItem PlayoutItem, string Path);
}

11
ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Security.Cryptography;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Channels;
using CliWrap;
@ -18,6 +19,7 @@ using Microsoft.Extensions.Logging; @@ -18,6 +19,7 @@ using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Subtitles;
[SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms")]
public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSubtitles, Either<BaseError, Unit>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
@ -178,7 +180,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu @@ -178,7 +180,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
}
}
private async Task<List<int>> GetMediaItemIdsWithTextSubtitles(
private static async Task<List<int>> GetMediaItemIdsWithTextSubtitles(
TvContext dbContext,
List<int> mediaItemIds,
CancellationToken cancellationToken)
@ -492,11 +494,10 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu @@ -492,11 +494,10 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
return string.Empty;
}
using var md5 = MD5.Create();
byte[] textData = Encoding.UTF8.GetBytes(text);
byte[] hash = md5.ComputeHash(textData);
byte[] hash = MD5.HashData(textData);
return BitConverter.ToString(hash).Replace("-", string.Empty);
}
private record SubtitleToExtract(Subtitle Subtitle, string OutputPath);
private sealed record SubtitleToExtract(Subtitle Subtitle, string OutputPath);
}

10
ErsatzTV.Application/Television/Mapper.cs

@ -17,7 +17,8 @@ internal static class Mapper @@ -17,7 +17,8 @@ internal static class Mapper
new(
show.Id,
show.ShowMetadata.HeadOrNone().Map(m => m.Title ?? string.Empty).IfNone(string.Empty),
show.ShowMetadata.HeadOrNone().Map(m => m.Year?.ToString() ?? string.Empty).IfNone(string.Empty),
show.ShowMetadata.HeadOrNone().Map(m => m.Year?.ToString(CultureInfo.InvariantCulture) ?? string.Empty)
.IfNone(string.Empty),
show.ShowMetadata.HeadOrNone().Map(m => m.Plot ?? string.Empty).IfNone(string.Empty),
show.ShowMetadata.HeadOrNone().Map(m => GetPoster(m, maybeJellyfin, maybeEmby)).IfNone(string.Empty),
show.ShowMetadata.HeadOrNone().Map(m => GetFanArt(m, maybeJellyfin, maybeEmby)).IfNone(string.Empty),
@ -45,7 +46,8 @@ internal static class Mapper @@ -45,7 +46,8 @@ internal static class Mapper
season.Id,
season.ShowId,
season.Show.ShowMetadata.HeadOrNone().Map(m => m.Title ?? string.Empty).IfNone(string.Empty),
season.Show.ShowMetadata.HeadOrNone().Map(m => m.Year?.ToString() ?? string.Empty).IfNone(string.Empty),
season.Show.ShowMetadata.HeadOrNone()
.Map(m => m.Year?.ToString(CultureInfo.InvariantCulture) ?? string.Empty).IfNone(string.Empty),
season.SeasonNumber == 0 ? "Specials" : $"Season {season.SeasonNumber}",
season.SeasonMetadata.HeadOrNone().Map(m => GetPoster(m, maybeJellyfin, maybeEmby))
.IfNone(string.Empty),
@ -73,7 +75,7 @@ internal static class Mapper @@ -73,7 +75,7 @@ internal static class Mapper
string artwork = Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind))
.Match(a => a.Path, string.Empty);
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://"))
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://", StringComparison.OrdinalIgnoreCase))
{
Url url = JellyfinUrl.RelativeProxyForArtwork(artwork);
if (artworkKind == ArtworkKind.Poster)
@ -83,7 +85,7 @@ internal static class Mapper @@ -83,7 +85,7 @@ internal static class Mapper
artwork = url;
}
else if (maybeEmby.IsSome && artwork.StartsWith("emby://"))
else if (maybeEmby.IsSome && artwork.StartsWith("emby://", StringComparison.OrdinalIgnoreCase))
{
Url url = EmbyUrl.RelativeProxyForArtwork(artwork);
if (artworkKind == ArtworkKind.Poster)

5
ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs

@ -75,7 +75,8 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI @@ -75,7 +75,8 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
string nvidiaCapabilities = null;
string vaapiCapabilities = null;
Option<ConfigElement> maybeFFmpegPath = await _configElementRepository.Get(ConfigElementKey.FFmpegPath);
Option<ConfigElement> maybeFFmpegPath =
await _configElementRepository.GetConfigElement(ConfigElementKey.FFmpegPath);
if (maybeFFmpegPath.IsNone)
{
nvidiaCapabilities = "Unable to locate ffmpeg";
@ -172,7 +173,7 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI @@ -172,7 +173,7 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
return result;
}
private string GetDriverName(VaapiDriver driver)
private static string GetDriverName(VaapiDriver driver)
{
switch (driver)
{

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

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.7.30">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

78
ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs

@ -14,16 +14,12 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -14,16 +14,12 @@ public class FFmpegPlaybackSettingsCalculatorTests
[TestFixture]
public class CalculateSettings
{
private readonly FFmpegPlaybackSettingsCalculator _calculator;
public CalculateSettings() => _calculator = new FFmpegPlaybackSettingsCalculator();
[Test]
public void Should_Not_GenPts_ForHlsSegmenter()
{
FFmpegProfile ffmpegProfile = TestProfile();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.HttpLiveStreamingSegmenter,
ffmpegProfile,
TestVersion,
@ -48,7 +44,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -48,7 +44,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegProfile ffmpegProfile = TestProfile() with { ThreadCount = 7 };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -69,7 +65,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -69,7 +65,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
{
FFmpegProfile ffmpegProfile = TestProfile() with { ThreadCount = 7 };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.HttpLiveStreamingSegmenter,
ffmpegProfile,
TestVersion,
@ -90,7 +86,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -90,7 +86,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
{
FFmpegProfile ffmpegProfile = TestProfile();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -113,7 +109,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -113,7 +109,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
{
FFmpegProfile ffmpegProfile = TestProfile();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile,
TestVersion,
@ -136,7 +132,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -136,7 +132,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
{
FFmpegProfile ffmpegProfile = TestProfile();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -157,7 +153,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -157,7 +153,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
{
FFmpegProfile ffmpegProfile = TestProfile();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile,
TestVersion,
@ -180,7 +176,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -180,7 +176,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegProfile ffmpegProfile = TestProfile();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -204,7 +200,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -204,7 +200,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegProfile ffmpegProfile = TestProfile();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile,
TestVersion,
@ -232,7 +228,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -232,7 +228,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
// not anamorphic
var version = new MediaVersion { Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -259,7 +255,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -259,7 +255,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
// not anamorphic
var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -286,7 +282,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -286,7 +282,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
// not anamorphic
var version = new MediaVersion { Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -314,7 +310,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -314,7 +310,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
// not anamorphic
var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -341,7 +337,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -341,7 +337,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
var version = new MediaVersion { Width = 706, Height = 362, SampleAspectRatio = "32:27" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -371,7 +367,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -371,7 +367,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
// not anamorphic
var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile,
version,
@ -401,7 +397,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -401,7 +397,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
// not anamorphic
var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -433,7 +429,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -433,7 +429,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
var version = new MediaVersion
{ Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -464,7 +460,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -464,7 +460,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
var version = new MediaVersion
{ Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile,
version,
@ -495,7 +491,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -495,7 +491,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
var version = new MediaVersion
{ Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -525,7 +521,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -525,7 +521,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
// not anamorphic
var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -556,7 +552,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -556,7 +552,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
var version = new MediaVersion
{ Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -586,7 +582,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -586,7 +582,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
// not anamorphic
var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -617,7 +613,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -617,7 +613,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
var version = new MediaVersion
{ Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -643,7 +639,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -643,7 +639,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioFormat = FFmpegProfileAudioFormat.Aac
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -667,7 +663,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -667,7 +663,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioFormat = FFmpegProfileAudioFormat.Aac
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -691,7 +687,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -691,7 +687,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioFormat = FFmpegProfileAudioFormat.Aac
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile,
TestVersion,
@ -716,7 +712,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -716,7 +712,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioFormat = FFmpegProfileAudioFormat.Ac3
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -741,7 +737,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -741,7 +737,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioFormat = FFmpegProfileAudioFormat.Ac3
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -766,7 +762,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -766,7 +762,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioChannels = 6
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -791,7 +787,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -791,7 +787,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioSampleRate = 48
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -815,7 +811,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -815,7 +811,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioChannels = 6
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -839,7 +835,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -839,7 +835,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
AudioSampleRate = 48
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -869,7 +865,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -869,7 +865,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
SampleAspectRatio = "1:1", Width = 1920, Height = 1080, Duration = TimeSpan.FromMinutes(5)
}; // not pulled from here
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
@ -893,7 +889,7 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -893,7 +889,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
NormalizeLoudness = true
};
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,
@ -913,17 +909,13 @@ public class FFmpegPlaybackSettingsCalculatorTests @@ -913,17 +909,13 @@ public class FFmpegPlaybackSettingsCalculatorTests
[TestFixture]
public class CalculateSettingsQsv
{
private readonly FFmpegPlaybackSettingsCalculator _calculator;
public CalculateSettingsQsv() => _calculator = new FFmpegPlaybackSettingsCalculator();
[Test]
public void Should_UseHardwareAcceleration()
{
FFmpegProfile ffmpegProfile =
TestProfile() with { HardwareAcceleration = HardwareAccelerationKind.Qsv };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
TestVersion,

2
ErsatzTV.Core/Domain/Channel.cs

@ -4,7 +4,7 @@ namespace ErsatzTV.Core.Domain; @@ -4,7 +4,7 @@ namespace ErsatzTV.Core.Domain;
public class Channel
{
public static string NumberValidator = @"^[0-9]+(\.[0-9]{1,2})?$";
public static readonly string NumberValidator = @"^[0-9]+(\.[0-9]{1,2})?$";
public Channel(Guid uniqueId) => UniqueId = uniqueId;
public int Id { get; set; }

5
ErsatzTV.Core/Domain/Collection/Collection.cs

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
namespace ErsatzTV.Core.Domain;
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")]
public class Collection
{
public int Id { get; set; }

5
ErsatzTV.Core/Domain/Collection/EmbyCollection.cs

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
namespace ErsatzTV.Core.Domain;
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")]
public class EmbyCollection
{
public int Id { get; set; }

5
ErsatzTV.Core/Domain/Collection/JellyfinCollection.cs

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
namespace ErsatzTV.Core.Domain;
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")]
public class JellyfinCollection
{
public int Id { get; set; }

5
ErsatzTV.Core/Domain/Collection/MultiCollection.cs

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
namespace ErsatzTV.Core.Domain;
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")]
public class MultiCollection
{
public int Id { get; set; }

5
ErsatzTV.Core/Domain/Collection/SmartCollection.cs

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
namespace ErsatzTV.Core.Domain;
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")]
public class SmartCollection
{
public int Id { get; set; }

7
ErsatzTV.Core/Domain/Collection/TraktListItemGuid.cs

@ -1,9 +1,14 @@ @@ -1,9 +1,14 @@
namespace ErsatzTV.Core.Domain;
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
public class TraktListItemGuid
{
public int Id { get; set; }
[SuppressMessage("Naming", "CA1720:Identifier contains type name")]
public string Guid { get; set; }
public int TraktListItemId { get; set; }
public TraktListItem TraktListItem { get; set; }
}

5
ErsatzTV.Core/Domain/MediaItem/MediaStream.cs

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
namespace ErsatzTV.Core.Domain;
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")]
public class MediaStream
{
public int Id { get; set; }

6
ErsatzTV.Core/Domain/Metadata/MetadataGuid.cs

@ -1,7 +1,11 @@ @@ -1,7 +1,11 @@
namespace ErsatzTV.Core.Domain;
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
public class MetadataGuid
{
public int Id { get; set; }
[SuppressMessage("Naming", "CA1720:Identifier contains type name")]
public string Guid { get; set; }
}

12
ErsatzTV.Core/Emby/EmbyPathReplacementService.cs

@ -49,14 +49,16 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService @@ -49,14 +49,16 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService
};
// we want to target the emby platform with the network path replacement
bool isTargetPlatformWindows = embyMediaSource.OperatingSystem.ToLowerInvariant().StartsWith("windows");
bool isTargetPlatformWindows = embyMediaSource.OperatingSystem.ToLowerInvariant()
.StartsWith("windows", StringComparison.OrdinalIgnoreCase);
return GetReplacementEmbyPath(replacements, path, isTargetPlatformWindows, false);
}
private static bool IsWindows(EmbyMediaSource embyMediaSource, string path)
{
bool isUnc = Uri.TryCreate(path, UriKind.Absolute, out Uri uri) && uri.IsUnc;
return isUnc || embyMediaSource.OperatingSystem.ToLowerInvariant().StartsWith("windows");
return isUnc || embyMediaSource.OperatingSystem.ToLowerInvariant()
.StartsWith("windows", StringComparison.OrdinalIgnoreCase);
}
private string GetReplacementEmbyPath(
@ -75,8 +77,10 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService @@ -75,8 +77,10 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService
}
string separatorChar = IsWindows(r.EmbyMediaSource, path) ? @"\" : @"/";
string prefix = r.EmbyPath.EndsWith(separatorChar) ? r.EmbyPath : r.EmbyPath + separatorChar;
return path.StartsWith(prefix);
string prefix = r.EmbyPath.EndsWith(separatorChar, StringComparison.OrdinalIgnoreCase)
? r.EmbyPath
: r.EmbyPath + separatorChar;
return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
});
foreach (EmbyPathReplacement replacement in maybeReplacement)

2
ErsatzTV.Core/ErsatzTV.Core.csproj

@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
<TargetFramework>net7.0</TargetFramework>
<NoWarn>VSTHRD200</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>

9
ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
@ -96,7 +97,7 @@ public class FFmpegComplexFilterBuilder @@ -96,7 +97,7 @@ public class FFmpegComplexFilterBuilder
var complexFilter = new StringBuilder();
var videoLabel = $"{videoInput}:{(isSong ? "v" : videoStreamIndex.ToString())}";
var videoLabel = $"{videoInput}:{(isSong ? "v" : videoStreamIndex.ToString(CultureInfo.InvariantCulture))}";
string audioLabel = audioStreamIndex.Match(index => $"{audioInput}:{index}", () => "0:a");
var videoFilterQueue = new List<string>();
@ -217,7 +218,7 @@ public class FFmpegComplexFilterBuilder @@ -217,7 +218,7 @@ public class FFmpegComplexFilterBuilder
{
if (videoFilterQueue.Any())
{
complexFilter.Append($"[{videoLabel}]");
complexFilter.Append(CultureInfo.InvariantCulture, $"[{videoLabel}]");
var filters = string.Join(",", videoFilterQueue);
complexFilter.Append(filters);
}
@ -238,7 +239,7 @@ public class FFmpegComplexFilterBuilder @@ -238,7 +239,7 @@ public class FFmpegComplexFilterBuilder
if (watermarkPreprocess.Count > 0)
{
var joined = string.Join(",", watermarkPreprocess);
complexFilter.Append($"{watermarkLabel}{joined}[wmp];");
complexFilter.Append(CultureInfo.InvariantCulture, $"{watermarkLabel}{joined}[wmp];");
watermarkLabel = "[wmp]";
}

9
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -21,12 +21,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -21,12 +21,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
private readonly IFFmpegStreamSelector _ffmpegStreamSelector;
private readonly ILogger<FFmpegLibraryProcessService> _logger;
private readonly IPipelineBuilderFactory _pipelineBuilderFactory;
private readonly FFmpegPlaybackSettingsCalculator _playbackSettingsCalculator;
private readonly ITempFilePool _tempFilePool;
public FFmpegLibraryProcessService(
FFmpegProcessService ffmpegProcessService,
FFmpegPlaybackSettingsCalculator playbackSettingsCalculator,
IFFmpegStreamSelector ffmpegStreamSelector,
ITempFilePool tempFilePool,
IPipelineBuilderFactory pipelineBuilderFactory,
@ -34,7 +32,6 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -34,7 +32,6 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
ILogger<FFmpegLibraryProcessService> logger)
{
_ffmpegProcessService = ffmpegProcessService;
_playbackSettingsCalculator = playbackSettingsCalculator;
_ffmpegStreamSelector = ffmpegStreamSelector;
_tempFilePool = tempFilePool;
_pipelineBuilderFactory = pipelineBuilderFactory;
@ -82,7 +79,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -82,7 +79,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
preferredAudioLanguage,
preferredAudioTitle);
FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.CalculateSettings(
FFmpegPlaybackSettings playbackSettings = FFmpegPlaybackSettingsCalculator.CalculateSettings(
channel.StreamingMode,
channel.FFmpegProfile,
videoVersion,
@ -373,7 +370,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -373,7 +370,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
string vaapiDevice,
Option<int> qsvExtraHardwareFrames)
{
FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.CalculateErrorSettings(
FFmpegPlaybackSettings playbackSettings = FFmpegPlaybackSettingsCalculator.CalculateErrorSettings(
channel.StreamingMode,
channel.FFmpegProfile,
hlsRealtime);
@ -637,7 +634,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -637,7 +634,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
watermarkWidthPercent,
cancellationToken);
private Option<WatermarkInputFile> GetWatermarkInputFile(
private static Option<WatermarkInputFile> GetWatermarkInputFile(
Option<WatermarkOptions> watermarkOptions,
Option<List<FadePoint>> maybeFadePoints)
{

18
ErsatzTV.Core/FFmpeg/FFmpegLocator.cs

@ -15,7 +15,7 @@ public class FFmpegLocator : IFFmpegLocator @@ -15,7 +15,7 @@ public class FFmpegLocator : IFFmpegLocator
public async Task<Option<string>> ValidatePath(string executableBase, ConfigElementKey key)
{
Option<ConfigElement> setting = await _configElementRepository.Get(key);
Option<ConfigElement> setting = await _configElementRepository.GetConfigElement(key);
return await setting.MatchAsync(
async ce =>
@ -45,7 +45,7 @@ public class FFmpegLocator : IFFmpegLocator @@ -45,7 +45,7 @@ public class FFmpegLocator : IFFmpegLocator
() => None);
}
private async Task<Option<string>> LocateExecutableOnPathAsync(string executableBase)
private static async Task<Option<string>> LocateExecutableOnPathAsync(string executableBase)
{
string executable = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? $"{executableBase}.exe"
@ -62,15 +62,13 @@ public class FFmpegLocator : IFFmpegLocator @@ -62,15 +62,13 @@ public class FFmpegLocator : IFFmpegLocator
}
string locateCommand = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "where" : "which";
using var p = new Process
using var p = new Process();
p.StartInfo = new ProcessStartInfo
{
StartInfo = new ProcessStartInfo
{
UseShellExecute = false,
FileName = locateCommand,
Arguments = executable,
RedirectStandardOutput = true
}
UseShellExecute = false,
FileName = locateCommand,
Arguments = executable,
RedirectStandardOutput = true
};
p.Start();
string path = (await p.StandardOutput.ReadToEndAsync()).Trim();

12
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -18,14 +18,14 @@ @@ -18,14 +18,14 @@
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
using System.Globalization;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.FFmpeg.Format;
using Serilog;
namespace ErsatzTV.Core.FFmpeg;
public class FFmpegPlaybackSettingsCalculator
public static class FFmpegPlaybackSettingsCalculator
{
private static readonly List<string> CommonFormatFlags = new()
{
@ -40,7 +40,7 @@ public class FFmpegPlaybackSettingsCalculator @@ -40,7 +40,7 @@ public class FFmpegPlaybackSettingsCalculator
"+igndts"
};
public FFmpegPlaybackSettings CalculateSettings(
public static FFmpegPlaybackSettings CalculateSettings(
StreamingMode streamingMode,
FFmpegProfile ffmpegProfile,
MediaVersion videoVersion,
@ -169,7 +169,7 @@ public class FFmpegPlaybackSettingsCalculator @@ -169,7 +169,7 @@ public class FFmpegPlaybackSettingsCalculator
return result;
}
public FFmpegPlaybackSettings CalculateErrorSettings(
public static FFmpegPlaybackSettings CalculateErrorSettings(
StreamingMode streamingMode,
FFmpegProfile ffmpegProfile,
bool hlsRealtime) =>
@ -273,6 +273,8 @@ public class FFmpegPlaybackSettingsCalculator @@ -273,6 +273,8 @@ public class FFmpegPlaybackSettingsCalculator
private static IDisplaySize SARSize(MediaVersion version)
{
string[] split = version.SampleAspectRatio.Split(":");
return new DisplaySize(int.Parse(split[0]), int.Parse(split[1]));
return new DisplaySize(
int.Parse(split[0], CultureInfo.InvariantCulture),
int.Parse(split[1], CultureInfo.InvariantCulture));
}
}

8
ErsatzTV.Core/FFmpeg/FFmpegProcess.cs

@ -4,14 +4,16 @@ namespace ErsatzTV.Core.FFmpeg; @@ -4,14 +4,16 @@ namespace ErsatzTV.Core.FFmpeg;
public class FFmpegProcess : Process
{
public static int ProcessCount;
private static int _processCount;
public FFmpegProcess() => Interlocked.Increment(ref ProcessCount);
public FFmpegProcess() => Interlocked.Increment(ref _processCount);
public static int ProcessCount => _processCount;
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Interlocked.Decrement(ref ProcessCount);
Interlocked.Decrement(ref _processCount);
}
}

3
ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
// 3. This notice may not be removed or altered from any source distribution.
using System.Diagnostics;
using System.Globalization;
using System.Text;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
@ -164,7 +165,7 @@ internal class FFmpegProcessBuilder @@ -164,7 +165,7 @@ internal class FFmpegProcessBuilder
}
var videoLabel = $"{videoIndex}:{videoStreamIndex}";
var audioLabel = $"{audioIndex}:{maybeIndex.Match(i => i.ToString(), () => "a")}";
var audioLabel = $"{audioIndex}:{maybeIndex.Match(i => i.ToString(CultureInfo.InvariantCulture), () => "a")}";
Option<FFmpegComplexFilter> maybeFilter = _complexFilterBuilder.Build(
audioPath.IsNone,

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

Loading…
Cancel
Save