aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/codeql-analysis.yml2
-rw-r--r--.github/workflows/commands.yml4
-rw-r--r--.github/workflows/openapi.yml8
-rw-r--r--Directory.Packages.props4
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs64
-rw-r--r--Emby.Dlna/PlayTo/Device.cs116
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs4
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs10
-rw-r--r--Emby.Server.Implementations/Localization/Core/cs.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/pr.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/tr.json48
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs33
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs20
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs15
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs5
-rw-r--r--Jellyfin.Networking/Configuration/NetworkConfiguration.cs2
-rw-r--r--Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs16
-rw-r--r--Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs12
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs10
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs8
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs8
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs31
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs90
-rw-r--r--MediaBrowser.Model/Querying/NextUpQuery.cs6
-rw-r--r--MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs4
-rw-r--r--global.json6
-rw-r--r--src/Jellyfin.Drawing.Skia/SkiaEncoder.cs6
-rw-r--r--src/Jellyfin.Drawing/ImageProcessor.cs6
-rw-r--r--src/Jellyfin.Extensions/StreamExtensions.cs6
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs19
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs66
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs44
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs36
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs10
-rw-r--r--tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs2
-rw-r--r--tests/jellyfin-tests.ruleset6
39 files changed, 362 insertions, 386 deletions
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 86d9ed0e1..cdb7f5c73 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
+ uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml
index 3b7f7b85b..02aeefdbd 100644
--- a/.github/workflows/commands.yml
+++ b/.github/workflows/commands.yml
@@ -24,7 +24,7 @@ jobs:
reactions: '+1'
- name: Checkout the latest code
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
+ uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
@@ -51,7 +51,7 @@ jobs:
reactions: eyes
- name: Checkout the latest code
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
+ uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml
index ee64a522e..72f1b1d1f 100644
--- a/.github/workflows/openapi.yml
+++ b/.github/workflows/openapi.yml
@@ -14,7 +14,7 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
+ uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -25,7 +25,7 @@ jobs:
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
+ uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: openapi-head
retention-days: 14
@@ -39,7 +39,7 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
+ uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -59,7 +59,7 @@ jobs:
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
+ uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: openapi-base
retention-days: 14
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 66ce19cb7..6b9b51826 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -47,7 +47,7 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
- <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
+ <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
@@ -87,6 +87,6 @@
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.0" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
- <PackageVersion Include="xunit" Version="2.4.2" />
+ <PackageVersion Include="xunit" Version="2.5.0" />
</ItemGroup>
</Project>
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index f668dc829..5ed982876 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -45,8 +43,8 @@ namespace Emby.Dlna.Didl
private readonly DeviceProfile _profile;
private readonly IImageProcessor _imageProcessor;
private readonly string _serverAddress;
- private readonly string _accessToken;
- private readonly User _user;
+ private readonly string? _accessToken;
+ private readonly User? _user;
private readonly IUserDataManager _userDataManager;
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
@@ -56,10 +54,10 @@ namespace Emby.Dlna.Didl
public DidlBuilder(
DeviceProfile profile,
- User user,
+ User? user,
IImageProcessor imageProcessor,
string serverAddress,
- string accessToken,
+ string? accessToken,
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
@@ -85,7 +83,7 @@ namespace Emby.Dlna.Didl
return url + "&dlnaheaders=true";
}
- public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
+ public string GetItemDidl(BaseItem item, User? user, BaseItem? context, string deviceId, Filter filter, StreamInfo streamInfo)
{
var settings = new XmlWriterSettings
{
@@ -140,12 +138,12 @@ namespace Emby.Dlna.Didl
public void WriteItemElement(
XmlWriter writer,
BaseItem item,
- User user,
- BaseItem context,
+ User? user,
+ BaseItem? context,
StubType? contextStubType,
string deviceId,
Filter filter,
- StreamInfo streamInfo = null)
+ StreamInfo? streamInfo = null)
{
var clientId = GetClientId(item, null);
@@ -190,7 +188,7 @@ namespace Emby.Dlna.Didl
writer.WriteFullEndElement();
}
- private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo? streamInfo = null)
{
if (streamInfo is null)
{
@@ -203,7 +201,7 @@ namespace Emby.Dlna.Didl
Profile = _profile,
DeviceId = deviceId,
MaxBitrate = _profile.MaxStreamingBitrate
- });
+ }) ?? throw new InvalidOperationException("No optimal video stream found");
}
var targetWidth = streamInfo.TargetWidth;
@@ -315,7 +313,7 @@ namespace Emby.Dlna.Didl
var mediaSource = streamInfo.MediaSource;
- if (mediaSource.RunTimeTicks.HasValue)
+ if (mediaSource?.RunTimeTicks.HasValue == true)
{
writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture));
}
@@ -410,7 +408,7 @@ namespace Emby.Dlna.Didl
writer.WriteFullEndElement();
}
- private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
+ private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem? context)
{
if (itemStubType.HasValue)
{
@@ -452,7 +450,7 @@ namespace Emby.Dlna.Didl
/// <param name="episode">The episode.</param>
/// <param name="context">Current context.</param>
/// <returns>Formatted name of the episode.</returns>
- private string GetEpisodeDisplayName(Episode episode, BaseItem context)
+ private string GetEpisodeDisplayName(Episode episode, BaseItem? context)
{
string[] components;
@@ -530,7 +528,7 @@ namespace Emby.Dlna.Didl
private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
- private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo? streamInfo = null)
{
writer.WriteStartElement(string.Empty, "res", NsDidl);
@@ -544,14 +542,14 @@ namespace Emby.Dlna.Didl
MediaSources = sources.ToArray(),
Profile = _profile,
DeviceId = deviceId
- });
+ }) ?? throw new InvalidOperationException("No optimal audio stream found");
}
var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken));
var mediaSource = streamInfo.MediaSource;
- if (mediaSource.RunTimeTicks.HasValue)
+ if (mediaSource?.RunTimeTicks is not null)
{
writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture));
}
@@ -634,7 +632,7 @@ namespace Emby.Dlna.Didl
// Samsung sometimes uses 1 as root
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase);
- public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
+ public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string? requestedId = null)
{
writer.WriteStartElement(string.Empty, "container", NsDidl);
@@ -678,14 +676,14 @@ namespace Emby.Dlna.Didl
writer.WriteFullEndElement();
}
- private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer, StreamInfo streamInfo)
+ private void AddSamsungBookmarkInfo(BaseItem item, User? user, XmlWriter writer, StreamInfo? streamInfo)
{
if (!item.SupportsPositionTicksResume || item is Folder)
{
return;
}
- XmlAttribute secAttribute = null;
+ XmlAttribute? secAttribute = null;
foreach (var attribute in _profile.XmlRootAttributes)
{
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
@@ -695,8 +693,8 @@ namespace Emby.Dlna.Didl
}
}
- // Not a samsung device
- if (secAttribute is null)
+ // Not a samsung device or no user data
+ if (secAttribute is null || user is null)
{
return;
}
@@ -717,7 +715,7 @@ namespace Emby.Dlna.Didl
/// <summary>
/// Adds fields used by both items and folders.
/// </summary>
- private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
+ private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem? context, XmlWriter writer, Filter filter)
{
// Don't filter on dc:title because not all devices will include it in the filter
// MediaMonkey for example won't display content without a title
@@ -795,7 +793,7 @@ namespace Emby.Dlna.Didl
if (item.IsDisplayedAsFolder || stubType.HasValue)
{
- string classType = null;
+ string? classType = null;
if (!_profile.RequiresPlainFolders)
{
@@ -899,7 +897,7 @@ namespace Emby.Dlna.Didl
}
}
- private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
+ private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem? context, XmlWriter writer, Filter filter)
{
AddCommonFields(item, itemStubType, context, writer, filter);
@@ -975,7 +973,7 @@ namespace Emby.Dlna.Didl
private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer)
{
- ImageDownloadInfo imageInfo = GetImageInfo(item);
+ ImageDownloadInfo? imageInfo = GetImageInfo(item);
if (imageInfo is null)
{
@@ -1073,7 +1071,7 @@ namespace Emby.Dlna.Didl
writer.WriteFullEndElement();
}
- private ImageDownloadInfo GetImageInfo(BaseItem item)
+ private ImageDownloadInfo? GetImageInfo(BaseItem item)
{
if (item.HasImage(ImageType.Primary))
{
@@ -1118,7 +1116,7 @@ namespace Emby.Dlna.Didl
return null;
}
- private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
+ private BaseItem? GetFirstParentWithImageBelowUserRoot(BaseItem item)
{
if (item is null)
{
@@ -1148,7 +1146,7 @@ namespace Emby.Dlna.Didl
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{
var imageInfo = item.GetImageInfo(type, 0);
- string tag = null;
+ string? tag = null;
try
{
@@ -1250,7 +1248,7 @@ namespace Emby.Dlna.Didl
{
internal Guid ItemId { get; set; }
- internal string ImageTag { get; set; }
+ internal string? ImageTag { get; set; }
internal ImageType Type { get; set; }
@@ -1260,9 +1258,9 @@ namespace Emby.Dlna.Didl
internal bool IsDirectStream { get; set; }
- internal string Format { get; set; }
+ internal required string Format { get; set; }
- internal ItemImageInfo ItemImageInfo { get; set; }
+ internal required ItemImageInfo ItemImageInfo { get; set; }
}
}
}
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 9c476119d..d21cc6913 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -25,7 +23,7 @@ namespace Emby.Dlna.PlayTo
private readonly ILogger _logger;
private readonly object _timerLock = new object();
- private Timer _timer;
+ private Timer? _timer;
private int _muteVol;
private int _volume;
private DateTime _lastVolumeRefresh;
@@ -40,13 +38,13 @@ namespace Emby.Dlna.PlayTo
_logger = logger;
}
- public event EventHandler<PlaybackStartEventArgs> PlaybackStart;
+ public event EventHandler<PlaybackStartEventArgs>? PlaybackStart;
- public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
+ public event EventHandler<PlaybackProgressEventArgs>? PlaybackProgress;
- public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped;
+ public event EventHandler<PlaybackStoppedEventArgs>? PlaybackStopped;
- public event EventHandler<MediaChangedEventArgs> MediaChanged;
+ public event EventHandler<MediaChangedEventArgs>? MediaChanged;
public DeviceInfo Properties { get; set; }
@@ -75,13 +73,13 @@ namespace Emby.Dlna.PlayTo
public bool IsStopped => TransportState == TransportState.STOPPED;
- public Action OnDeviceUnavailable { get; set; }
+ public Action? OnDeviceUnavailable { get; set; }
- private TransportCommands AvCommands { get; set; }
+ private TransportCommands? AvCommands { get; set; }
- private TransportCommands RendererCommands { get; set; }
+ private TransportCommands? RendererCommands { get; set; }
- public UBaseObject CurrentMediaInfo { get; private set; }
+ public UBaseObject? CurrentMediaInfo { get; private set; }
public void Start()
{
@@ -131,7 +129,7 @@ namespace Emby.Dlna.PlayTo
_volumeRefreshActive = true;
var time = immediate ? 100 : 10000;
- _timer.Change(time, Timeout.Infinite);
+ _timer?.Change(time, Timeout.Infinite);
}
}
@@ -149,7 +147,7 @@ namespace Emby.Dlna.PlayTo
_volumeRefreshActive = false;
- _timer.Change(Timeout.Infinite, Timeout.Infinite);
+ _timer?.Change(Timeout.Infinite, Timeout.Infinite);
}
}
@@ -199,7 +197,7 @@ namespace Emby.Dlna.PlayTo
}
}
- private DeviceService GetServiceRenderingControl()
+ private DeviceService? GetServiceRenderingControl()
{
var services = Properties.Services;
@@ -207,7 +205,7 @@ namespace Emby.Dlna.PlayTo
services.FirstOrDefault(s => (s.ServiceType ?? string.Empty).StartsWith("urn:schemas-upnp-org:service:RenderingControl", StringComparison.OrdinalIgnoreCase));
}
- private DeviceService GetAvTransportService()
+ private DeviceService? GetAvTransportService()
{
var services = Properties.Services;
@@ -240,7 +238,7 @@ namespace Emby.Dlna.PlayTo
Properties.BaseUrl,
service,
command.Name,
- rendererCommands.BuildPost(command, service.ServiceType, value),
+ rendererCommands!.BuildPost(command, service.ServiceType, value), // null checked above
cancellationToken: cancellationToken)
.ConfigureAwait(false);
@@ -265,12 +263,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- var service = GetServiceRenderingControl();
-
- if (service is null)
- {
- throw new InvalidOperationException("Unable to find service");
- }
+ var service = GetServiceRenderingControl() ?? throw new InvalidOperationException("Unable to find service");
// Set it early and assume it will succeed
// Remote control will perform better
@@ -281,7 +274,7 @@ namespace Emby.Dlna.PlayTo
Properties.BaseUrl,
service,
command.Name,
- rendererCommands.BuildPost(command, service.ServiceType, value),
+ rendererCommands!.BuildPost(command, service.ServiceType, value), // null checked above
cancellationToken: cancellationToken)
.ConfigureAwait(false);
}
@@ -296,26 +289,20 @@ namespace Emby.Dlna.PlayTo
return;
}
- var service = GetAvTransportService();
-
- if (service is null)
- {
- throw new InvalidOperationException("Unable to find service");
- }
-
+ var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
- avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"),
+ avCommands!.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"), // null checked above
cancellationToken: cancellationToken)
.ConfigureAwait(false);
RestartTimer(true);
}
- public async Task SetAvTransport(string url, string header, string metaData, CancellationToken cancellationToken)
+ public async Task SetAvTransport(string url, string? header, string metaData, CancellationToken cancellationToken)
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
@@ -335,14 +322,8 @@ namespace Emby.Dlna.PlayTo
{ "CurrentURIMetaData", CreateDidlMeta(metaData) }
};
- var service = GetAvTransportService();
-
- if (service is null)
- {
- throw new InvalidOperationException("Unable to find service");
- }
-
- var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
+ var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
+ var post = avCommands!.BuildPost(command, service.ServiceType, url, dictionary); // null checked above
await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
@@ -372,7 +353,7 @@ namespace Emby.Dlna.PlayTo
* SetNextAvTransport is used to specify to the DLNA device what is the next track to play.
* Without that information, the next track command on the device does not work.
*/
- public async Task SetNextAvTransport(string url, string header, string metaData, CancellationToken cancellationToken = default)
+ public async Task SetNextAvTransport(string url, string? header, string metaData, CancellationToken cancellationToken = default)
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
@@ -380,7 +361,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("{PropertyName} - SetNextAvTransport Uri: {Url} DlnaHeaders: {Header}", Properties.Name, url, header);
- var command = avCommands.ServiceActions.FirstOrDefault(c => string.Equals(c.Name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase));
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => string.Equals(c.Name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase));
if (command is null)
{
return;
@@ -392,14 +373,8 @@ namespace Emby.Dlna.PlayTo
{ "NextURIMetaData", CreateDidlMeta(metaData) }
};
- var service = GetAvTransportService();
-
- if (service is null)
- {
- throw new InvalidOperationException("Unable to find service");
- }
-
- var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
+ var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
+ var post = avCommands!.BuildPost(command, service.ServiceType, url, dictionary); // null checked above
await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header, cancellationToken)
.ConfigureAwait(false);
@@ -423,12 +398,7 @@ namespace Emby.Dlna.PlayTo
return Task.CompletedTask;
}
- var service = GetAvTransportService();
- if (service is null)
- {
- throw new InvalidOperationException("Unable to find service");
- }
-
+ var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
return new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
@@ -460,14 +430,13 @@ namespace Emby.Dlna.PlayTo
return;
}
- var service = GetAvTransportService();
-
+ var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
- avCommands.BuildPost(command, service.ServiceType, 1),
+ avCommands!.BuildPost(command, service.ServiceType, 1), // null checked above
cancellationToken: cancellationToken)
.ConfigureAwait(false);
@@ -484,14 +453,13 @@ namespace Emby.Dlna.PlayTo
return;
}
- var service = GetAvTransportService();
-
+ var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
- avCommands.BuildPost(command, service.ServiceType, 1),
+ avCommands!.BuildPost(command, service.ServiceType, 1), // null checked above
cancellationToken: cancellationToken)
.ConfigureAwait(false);
@@ -500,7 +468,7 @@ namespace Emby.Dlna.PlayTo
RestartTimer(true);
}
- private async void TimerCallback(object sender)
+ private async void TimerCallback(object? sender)
{
if (_disposed)
{
@@ -623,7 +591,7 @@ namespace Emby.Dlna.PlayTo
Properties.BaseUrl,
service,
command.Name,
- rendererCommands.BuildPost(command, service.ServiceType),
+ rendererCommands!.BuildPost(command, service.ServiceType), // null checked above
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result is null || result.Document is null)
@@ -673,7 +641,7 @@ namespace Emby.Dlna.PlayTo
Properties.BaseUrl,
service,
command.Name,
- rendererCommands.BuildPost(command, service.ServiceType),
+ rendererCommands!.BuildPost(command, service.ServiceType), // null checked above
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result is null || result.Document is null)
@@ -728,7 +696,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- private async Task<UBaseObject> GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken)
+ private async Task<UBaseObject?> GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken)
{
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
if (command is null)
@@ -798,7 +766,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- private async Task<(bool Success, UBaseObject Track)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
+ private async Task<(bool Success, UBaseObject? Track)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
{
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
if (command is null)
@@ -871,7 +839,7 @@ namespace Emby.Dlna.PlayTo
return (true, null);
}
- XElement uPnpResponse = null;
+ XElement? uPnpResponse = null;
try
{
@@ -895,7 +863,7 @@ namespace Emby.Dlna.PlayTo
return (true, uTrack);
}
- private XElement ParseResponse(string xml)
+ private XElement? ParseResponse(string xml)
{
// Handle different variations sent back by devices.
try
@@ -929,7 +897,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- private static UBaseObject CreateUBaseObject(XElement container, string trackUri)
+ private static UBaseObject CreateUBaseObject(XElement? container, string? trackUri)
{
ArgumentNullException.ThrowIfNull(container);
@@ -972,7 +940,7 @@ namespace Emby.Dlna.PlayTo
return new string[4];
}
- private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
+ private async Task<TransportCommands?> GetAVProtocolAsync(CancellationToken cancellationToken)
{
if (AvCommands is not null)
{
@@ -1004,7 +972,7 @@ namespace Emby.Dlna.PlayTo
return AvCommands;
}
- private async Task<TransportCommands> GetRenderingProtocolAsync(CancellationToken cancellationToken)
+ private async Task<TransportCommands?> GetRenderingProtocolAsync(CancellationToken cancellationToken)
{
if (RendererCommands is not null)
{
@@ -1054,7 +1022,7 @@ namespace Emby.Dlna.PlayTo
return baseUrl + url;
}
- public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
+ public static async Task<Device?> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
{
var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory);
@@ -1287,7 +1255,7 @@ namespace Emby.Dlna.PlayTo
}
_timer = null;
- Properties = null;
+ Properties = null!;
_disposed = true;
}
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index 86db36337..b1ad15cdc 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -42,7 +42,7 @@ namespace Emby.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly string _serverAddress;
- private readonly string _accessToken;
+ private readonly string? _accessToken;
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
private Device _device;
@@ -59,7 +59,7 @@ namespace Emby.Dlna.PlayTo
IUserManager userManager,
IImageProcessor imageProcessor,
string serverAddress,
- string accessToken,
+ string? accessToken,
IDeviceDiscovery deviceDiscovery,
IUserDataManager userDataManager,
ILocalizationManager localization,
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index 241dff5ae..ef617422c 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -67,7 +65,7 @@ namespace Emby.Dlna.PlayTo
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
}
- private async void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
+ private async void OnDeviceDiscoveryDeviceDiscovered(object? sender, GenericEventArgs<UpnpDeviceInfo> e)
{
if (_disposed)
{
@@ -76,12 +74,12 @@ namespace Emby.Dlna.PlayTo
var info = e.Argument;
- if (!info.Headers.TryGetValue("USN", out string usn))
+ if (!info.Headers.TryGetValue("USN", out string? usn))
{
usn = string.Empty;
}
- if (!info.Headers.TryGetValue("NT", out string nt))
+ if (!info.Headers.TryGetValue("NT", out string? nt))
{
nt = string.Empty;
}
@@ -161,7 +159,7 @@ namespace Emby.Dlna.PlayTo
var uri = info.Location;
_logger.LogDebug("Attempting to create PlayToController from location {0}", uri);
- if (info.Headers.TryGetValue("USN", out string uuid))
+ if (info.Headers.TryGetValue("USN", out string? uuid))
{
uuid = GetUuid(uuid);
}
diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json
index 08db5a30e..f33ea2fc9 100644
--- a/Emby.Server.Implementations/Localization/Core/cs.json
+++ b/Emby.Server.Implementations/Localization/Core/cs.json
@@ -22,7 +22,7 @@
"HeaderFavoriteEpisodes": "Oblíbené epizody",
"HeaderFavoriteShows": "Oblíbené seriály",
"HeaderFavoriteSongs": "Oblíbená hudba",
- "HeaderLiveTV": "Televize",
+ "HeaderLiveTV": "Živý přenos",
"HeaderNextUp": "Další díly",
"HeaderRecordingGroups": "Skupiny nahrávek",
"HomeVideos": "Domácí videa",
diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json
index 87800a2fe..d2446a7fd 100644
--- a/Emby.Server.Implementations/Localization/Core/pr.json
+++ b/Emby.Server.Implementations/Localization/Core/pr.json
@@ -24,5 +24,10 @@
"TaskDownloadMissingSubtitlesDescription": "Scours the seven seas o' the internet for subtitles that be missin' based on the captain's map o' metadata.",
"HeaderAlbumArtists": "Buccaneers o' the musical arts",
"HeaderFavoriteAlbums": "Beloved booty o' musical adventures",
- "HeaderFavoriteArtists": "Treasured scallywags o' the creative seas"
+ "HeaderFavoriteArtists": "Treasured scallywags o' the creative seas",
+ "Channels": "Channels",
+ "Forced": "Pressed",
+ "External": "Outboard",
+ "HeaderFavoriteEpisodes": "Treasured Tales",
+ "HeaderFavoriteShows": "Treasured Tales"
}
diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json
index 9a140f871..3ce928859 100644
--- a/Emby.Server.Implementations/Localization/Core/tr.json
+++ b/Emby.Server.Implementations/Localization/Core/tr.json
@@ -3,19 +3,19 @@
"AppDeviceValues": "Uygulama: {0}, Aygıt: {1}",
"Application": "Uygulama",
"Artists": "Sanatçılar",
- "AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı",
+ "AuthenticationSucceededWithUserName": "{0} kimliği başarıyla doğrulandı",
"Books": "Kitaplar",
"CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi",
"Channels": "Kanallar",
- "ChapterNameValue": "Bölüm {0}",
+ "ChapterNameValue": "{0}. Bölüm",
"Collections": "Koleksiyonlar",
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
"DeviceOnlineWithName": "{0} bağlı",
- "FailedLoginAttemptWithUserName": "{0} adresinden giriş denemesi başarısız oldu",
+ "FailedLoginAttemptWithUserName": "{0} kullanıcısının giriş denemesi başarısız oldu",
"Favorites": "Favoriler",
"Folders": "Klasörler",
"Genres": "Türler",
- "HeaderAlbumArtists": "Albüm Sanatçıları",
+ "HeaderAlbumArtists": "Albüm sanatçıları",
"HeaderContinueWatching": "İzlemeye Devam Et",
"HeaderFavoriteAlbums": "Favori Albümler",
"HeaderFavoriteArtists": "Favori Sanatçılar",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Canlı TV",
"HeaderNextUp": "Gelecek Hafta",
"HeaderRecordingGroups": "Kayıt Grupları",
- "HomeVideos": "Ana sayfa videoları",
+ "HomeVideos": "Ana Sayfa Videoları",
"Inherit": "Devral",
"ItemAddedWithName": "{0} kütüphaneye eklendi",
"ItemRemovedWithName": "{0} kütüphaneden silindi",
@@ -34,14 +34,14 @@
"Latest": "En son",
"MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
"MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} sürümüne güncellendi",
- "MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayar kısmı {0} güncellendi",
- "MessageServerConfigurationUpdated": "Sunucu ayarları güncellendi",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Sunucu yapılandırma bölümü {0} güncellendi",
+ "MessageServerConfigurationUpdated": "Sunucu yapılandırması güncellendi",
"MixedContent": "Karışık içerik",
"Movies": "Filmler",
"Music": "Müzik",
- "MusicVideos": "Müzik videoları",
+ "MusicVideos": "Müzik Videoları",
"NameInstallFailed": "{0} kurulumu başarısız",
- "NameSeasonNumber": "Sezon {0}",
+ "NameSeasonNumber": "{0}. Sezon",
"NameSeasonUnknown": "Bilinmeyen Sezon",
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir sürümü indirmek için hazır.",
"NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
@@ -55,9 +55,9 @@
"NotificationOptionPluginInstalled": "Eklenti yüklendi",
"NotificationOptionPluginUninstalled": "Eklenti kaldırıldı",
"NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi",
- "NotificationOptionServerRestartRequired": "Sunucu yeniden başlatma gerekli",
+ "NotificationOptionServerRestartRequired": "Sunucunun yeniden başlatılması gerekiyor",
"NotificationOptionTaskFailed": "Zamanlanmış görev hatası",
- "NotificationOptionUserLockedOut": "Kullanıcı kitlendi",
+ "NotificationOptionUserLockedOut": "Kullanıcı kilitlendi",
"NotificationOptionVideoPlayback": "Video oynatma başladı",
"NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu",
"Photos": "Fotoğraflar",
@@ -74,36 +74,36 @@
"Songs": "Şarkılar",
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
+ "SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} sağlayıcısından indirilemedi",
"Sync": "Eşzamanlama",
"System": "Sistem",
"TvShows": "Diziler",
"User": "Kullanıcı",
"UserCreatedWithName": "{0} kullanıcısı oluşturuldu",
- "UserDeletedWithName": "Kullanıcı {0} silindi",
- "UserDownloadingItemWithValues": "{0} indiriliyor {1}",
- "UserLockedOutWithName": "Kullanıcı {0} kitlendi",
- "UserOfflineFromDevice": "{0}, {1} ile bağlantısı kesildi",
- "UserOnlineFromDevice": "{0}, {1} çevrimiçi",
- "UserPasswordChangedWithName": "{0} kullanıcısı için şifre değiştirildi",
- "UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
+ "UserDeletedWithName": "{0} kullanıcısı silindi",
+ "UserDownloadingItemWithValues": "{0} {1} medyasını indiriyor",
+ "UserLockedOutWithName": "{0} adlı kullanıcı kilitlendi",
+ "UserOfflineFromDevice": "{0} kullanıcısının {1} ile bağlantısı kesildi",
+ "UserOnlineFromDevice": "{0} kullanıcısı {1} ile çevrimiçi",
+ "UserPasswordChangedWithName": "{0} kullanıcısının parolası değiştirildi",
+ "UserPolicyUpdatedWithName": "{0} için kullanıcı politikası güncellendi",
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
"ValueHasBeenAddedToLibrary": "Medya kütüphanenize {0} eklendi",
"ValueSpecialEpisodeName": "Özel - {0}",
"VersionNumber": "Sürüm {0}",
- "TaskCleanCache": "Geçici dosya klasörünü temizle",
- "TasksChannelsCategory": "İnternet kanalları",
+ "TaskCleanCache": "Geçici Dosya Klasörünü Temizle",
+ "TasksChannelsCategory": "İnternet Kanalları",
"TasksApplicationCategory": "Uygulama",
"TasksLibraryCategory": "Kütüphane",
"TasksMaintenanceCategory": "Bakım",
"TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.",
- "TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.",
+ "TaskDownloadMissingSubtitlesDescription": "Meta veri yapılandırmasına dayalı olarak eksik altyazılar için internette arama yapar.",
"TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
"TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
"TaskRefreshChannels": "Kanalları Yenile",
- "TaskCleanTranscodeDescription": "Bir günden daha eski dönüştürme dosyalarını siler.",
- "TaskCleanTranscode": "Dönüşüm Dizinini Temizle",
+ "TaskCleanTranscodeDescription": "Bir günden daha eski kod dönüştürme dosyalarını siler.",
+ "TaskCleanTranscode": "Kod Dönüştürme Dizinini Temizle",
"TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.",
"TaskUpdatePlugins": "Eklentileri Güncelle",
"TaskRefreshPeople": "Kullanıcıları Yenile",
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index 48584ae0c..1303012e1 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -677,7 +677,7 @@ namespace Emby.Server.Implementations.Plugins
}
catch (JsonException ex)
{
- _logger.LogError(ex, "Error deserializing {Json}.", Encoding.UTF8.GetString(data!));
+ _logger.LogError(ex, "Error deserializing {Json}.", Encoding.UTF8.GetString(data));
}
if (manifest is not null)
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 03ff96b19..b4a622ccf 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1509,35 +1509,20 @@ namespace Emby.Server.Implementations.Session
new DeviceQuery
{
DeviceId = deviceId,
- UserId = user.Id,
- Limit = 1
- }).ConfigureAwait(false)).Items.FirstOrDefault();
-
- var allExistingForDevice = (await _deviceManager.GetDevices(
- new DeviceQuery
- {
- DeviceId = deviceId
+ UserId = user.Id
}).ConfigureAwait(false)).Items;
- foreach (var auth in allExistingForDevice)
+ foreach (var auth in existing)
{
- if (existing is null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal))
+ try
{
- try
- {
- await Logout(auth).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error while logging out.");
- }
+ // Logout any existing sessions for the user on this device
+ await Logout(auth).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error while logging out existing session.");
}
- }
-
- if (existing is not null)
- {
- _logger.LogInformation("Reissuing access token: {Token}", existing.AccessToken);
- return existing.AccessToken;
}
_logger.LogInformation("Creating new access token for user {0}", user.Id);
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index f0e173f0b..ef890aeb4 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -135,13 +135,13 @@ namespace Emby.Server.Implementations.TV
private IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions)
{
- var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false));
+ var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, request.EnableResumable, false));
if (request.EnableRewatching)
{
- allNextUp = allNextUp.Concat(
- seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, true)))
- .OrderByDescending(i => i.LastWatchedDate);
+ allNextUp = allNextUp
+ .Concat(seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false, true)))
+ .OrderByDescending(i => i.LastWatchedDate);
}
// If viewing all next up for all series, remove first episodes
@@ -183,7 +183,7 @@ namespace Emby.Server.Implementations.TV
/// Gets the next up.
/// </summary>
/// <returns>Task{Episode}.</returns>
- private (DateTime LastWatchedDate, Func<Episode?> GetEpisodeFunction) GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching)
+ private (DateTime LastWatchedDate, Func<Episode?> GetEpisodeFunction) GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool includeResumable, bool includePlayed)
{
var lastQuery = new InternalItemsQuery(user)
{
@@ -200,8 +200,8 @@ namespace Emby.Server.Implementations.TV
}
};
- // If rewatching is enabled, sort first by date played and then by season and episode numbers
- lastQuery.OrderBy = rewatching
+ // If including played results, sort first by date played and then by season and episode numbers
+ lastQuery.OrderBy = includePlayed
? new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) }
: new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) };
@@ -216,7 +216,7 @@ namespace Emby.Server.Implementations.TV
IncludeItemTypes = new[] { BaseItemKind.Episode },
OrderBy = new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.Ascending) },
Limit = 1,
- IsPlayed = rewatching,
+ IsPlayed = includePlayed,
IsVirtualItem = false,
ParentIndexNumberNotEquals = 0,
DtoOptions = dtoOptions
@@ -240,7 +240,7 @@ namespace Emby.Server.Implementations.TV
SeriesPresentationUniqueKey = seriesKey,
ParentIndexNumber = 0,
IncludeItemTypes = new[] { BaseItemKind.Episode },
- IsPlayed = rewatching,
+ IsPlayed = includePlayed,
IsVirtualItem = false,
DtoOptions = dtoOptions
})
@@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.TV
nextEpisode = sortedConsideredEpisodes.FirstOrDefault();
}
- if (nextEpisode is not null)
+ if (nextEpisode is not null && !includeResumable)
{
var userData = _userDataManager.GetUserData(user, nextEpisode);
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index ce684e457..065a4ce5c 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -1651,7 +1651,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_encodingHelper.GetInputArgument(state, _encodingOptions, segmentContainer),
threads,
mapArgs,
- GetVideoArguments(state, startNumber, isEventPlaylist),
+ GetVideoArguments(state, startNumber, isEventPlaylist, segmentContainer),
GetAudioArguments(state),
maxMuxingQueueSize,
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
@@ -1703,19 +1703,18 @@ public class DynamicHlsController : BaseJellyfinApiController
}
var audioCodec = _encodingHelper.GetAudioEncoder(state);
+ var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
if (!state.IsOutputVideo)
{
if (EncodingHelper.IsCopyCodec(audioCodec))
{
- var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
-
return "-acodec copy -strict -2" + bitStreamArgs;
}
var audioTranscodeParams = string.Empty;
- audioTranscodeParams += "-acodec " + audioCodec;
+ audioTranscodeParams += "-acodec " + audioCodec + bitStreamArgs;
var audioBitrate = state.OutputAudioBitrate;
var audioChannels = state.OutputAudioChannels;
@@ -1761,7 +1760,6 @@ public class DynamicHlsController : BaseJellyfinApiController
if (EncodingHelper.IsCopyCodec(audioCodec))
{
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
- var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs;
if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
@@ -1772,7 +1770,7 @@ public class DynamicHlsController : BaseJellyfinApiController
return copyArgs;
}
- var args = "-codec:a:0 " + audioCodec + strictArgs;
+ var args = "-codec:a:0 " + audioCodec + bitStreamArgs + strictArgs;
var channels = state.OutputAudioChannels;
@@ -1816,8 +1814,9 @@ public class DynamicHlsController : BaseJellyfinApiController
/// <param name="state">The <see cref="StreamState"/>.</param>
/// <param name="startNumber">The first number in the hls sequence.</param>
/// <param name="isEventPlaylist">Whether the playlist is EVENT or VOD.</param>
+ /// <param name="segmentContainer">The segment container.</param>
/// <returns>The command line arguments for video transcoding.</returns>
- private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist)
+ private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist, string segmentContainer)
{
if (state.VideoStream is null)
{
@@ -1909,7 +1908,7 @@ public class DynamicHlsController : BaseJellyfinApiController
}
// TODO why was this not enabled for VOD?
- if (isEventPlaylist)
+ if (isEventPlaylist && string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase))
{
args += " -flags -global_header";
}
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 7d23281f2..bdbbd1e0d 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -68,7 +68,8 @@ public class TvShowsController : BaseJellyfinApiController
/// <param name="nextUpDateCutoff">Optional. Starting date of shows to show in Next Up section.</param>
/// <param name="enableTotalRecordCount">Whether to enable the total records count. Defaults to true.</param>
/// <param name="disableFirstEpisode">Whether to disable sending the first episode in a series as next up.</param>
- /// <param name="enableRewatching">Whether to include watched episode in next up results.</param>
+ /// <param name="enableResumable">Whether to include resumable episodes in next up results.</param>
+ /// <param name="enableRewatching">Whether to include watched episodes in next up results.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns>
[HttpGet("NextUp")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -86,6 +87,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] DateTime? nextUpDateCutoff,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool disableFirstEpisode = false,
+ [FromQuery] bool enableResumable = true,
[FromQuery] bool enableRewatching = false)
{
userId = RequestHelpers.GetUserId(User, userId);
@@ -104,6 +106,7 @@ public class TvShowsController : BaseJellyfinApiController
EnableTotalRecordCount = enableTotalRecordCount,
DisableFirstEpisode = disableFirstEpisode,
NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue,
+ EnableResumable = enableResumable,
EnableRewatching = enableRewatching
},
options);
diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
index 573c36be7..90ebcd390 100644
--- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
+++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
@@ -164,7 +164,7 @@ namespace Jellyfin.Networking.Configuration
public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty<string>();
/// <summary>
- /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with <seealso cref="IsRemoteIPFilterBlacklist"/>.
+ /// Gets or sets the filter for remote IP connectivity. Used in conjunction with <seealso cref="IsRemoteIPFilterBlacklist"/>.
/// </summary>
public string[] RemoteIPFilter { get; set; } = Array.Empty<string>();
diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs
index bee135efd..0544fe561 100644
--- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs
+++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
@@ -59,21 +59,17 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine
private OldMusicBrainzConfiguration? ReadOld(string path)
{
- using (var xmlReader = XmlReader.Create(path))
- {
- var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration"));
- return serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration;
- }
+ using var xmlReader = XmlReader.Create(path);
+ var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration"));
+ return serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration;
}
private void WriteNew(string path, PluginConfiguration newPluginConfiguration)
{
var pluginConfigurationSerializer = new XmlSerializer(typeof(PluginConfiguration), new XmlRootAttribute("PluginConfiguration"));
var xmlWriterSettings = new XmlWriterSettings { Indent = true };
- using (var xmlWriter = XmlWriter.Create(path, xmlWriterSettings))
- {
- pluginConfigurationSerializer.Serialize(xmlWriter, newPluginConfiguration);
- }
+ using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings);
+ pluginConfigurationSerializer.Serialize(xmlWriter, newPluginConfiguration);
}
#pragma warning disable
diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs
index a4379197c..c6d86b8cd 100644
--- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs
+++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs
@@ -43,10 +43,8 @@ public class MigrateNetworkConfiguration : IMigrationRoutine
try
{
- using (var xmlReader = XmlReader.Create(path))
- {
- oldNetworkConfiguration = (OldNetworkConfiguration?)oldNetworkConfigSerializer.Deserialize(xmlReader);
- }
+ using var xmlReader = XmlReader.Create(path);
+ oldNetworkConfiguration = (OldNetworkConfiguration?)oldNetworkConfigSerializer.Deserialize(xmlReader);
}
catch (InvalidOperationException ex)
{
@@ -97,10 +95,8 @@ public class MigrateNetworkConfiguration : IMigrationRoutine
var networkConfigSerializer = new XmlSerializer(typeof(NetworkConfiguration));
var xmlWriterSettings = new XmlWriterSettings { Indent = true };
- using (var xmlWriter = XmlWriter.Create(path, xmlWriterSettings))
- {
- networkConfigSerializer.Serialize(xmlWriter, networkConfiguration);
- }
+ using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings);
+ networkConfigSerializer.Serialize(xmlWriter, networkConfiguration);
}
}
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
index 62b70ce53..10326363a 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using MediaBrowser.Controller.Entities;
@@ -9,12 +7,12 @@ namespace MediaBrowser.Controller.Drawing
{
public static class ImageProcessorExtensions
{
- public static string GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType)
+ public static string? GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType)
{
return processor.GetImageCacheTag(item, imageType, 0);
}
- public static string GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType, int imageIndex)
+ public static string? GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType, int imageIndex)
{
var imageInfo = item.GetImageInfo(imageType, imageIndex);
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 1905ffb1c..e619e690d 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -1216,6 +1216,12 @@ namespace MediaBrowser.Controller.MediaEncoding
int bitrate = state.OutputVideoBitrate.Value;
+ // Bit rate under 1000k is not allowed in h264_qsv
+ if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ bitrate = Math.Max(bitrate, 1000);
+ }
+
// Currently use the same buffer size for all encoders
int bufsize = bitrate * 2;
@@ -1910,7 +1916,9 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(profile))
{
- if (!string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
+ // Currently there's no profile option in av1_nvenc encoder
+ if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
{
param += " -profile:v:0 " + profile;
}
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index aeb08cea3..3055e6cde 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -762,9 +762,11 @@ namespace MediaBrowser.MediaEncoding.Probing
&& !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase);
if (isAudio
- || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)
- || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase)
- || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase))
+ && (string.Equals(stream.Codec, "bmp", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(stream.Codec, "webp", StringComparison.OrdinalIgnoreCase)))
{
stream.Type = MediaStreamType.EmbeddedImage;
}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index b7c23669d..07bb002ea 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -314,7 +314,7 @@ namespace MediaBrowser.Model.Dlna
/// <param name="audioSampleRate">The audio sample rate.</param>
/// <param name="audioBitDepth">The audio bit depth.</param>
/// <returns>The <see cref="ResponseProfile"/>.</returns>
- public ResponseProfile? GetAudioMediaProfile(string container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
+ public ResponseProfile? GetAudioMediaProfile(string? container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
{
foreach (var i in ResponseProfiles)
{
@@ -438,14 +438,14 @@ namespace MediaBrowser.Model.Dlna
/// <param name="isAvc">True if Avc.</param>
/// <returns>The <see cref="ResponseProfile"/>.</returns>
public ResponseProfile? GetVideoMediaProfile(
- string container,
+ string? container,
string? audioCodec,
string? videoCodec,
int? width,
int? height,
int? bitDepth,
int? videoBitrate,
- string videoProfile,
+ string? videoProfile,
VideoRangeType videoRangeType,
double? videoLevel,
float? videoFramerate,
@@ -456,7 +456,7 @@ namespace MediaBrowser.Model.Dlna
int? refFrames,
int? numVideoStreams,
int? numAudioStreams,
- string videoCodecTag,
+ string? videoCodecTag,
bool? isAvc)
{
foreach (var i in ResponseProfiles)
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index adfe0da28..889e2494a 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -179,15 +179,9 @@ namespace MediaBrowser.Model.Dlna
{
ValidateMediaOptions(options, true);
- var mediaSources = new List<MediaSourceInfo>();
- foreach (var mediaSourceInfo in options.MediaSources)
- {
- if (string.IsNullOrEmpty(options.MediaSourceId)
- || string.Equals(mediaSourceInfo.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase))
- {
- mediaSources.Add(mediaSourceInfo);
- }
- }
+ var mediaSources = string.IsNullOrEmpty(options.MediaSourceId)
+ ? options.MediaSources
+ : options.MediaSources.Where(x => string.Equals(x.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase));
var streams = new List<StreamInfo>();
foreach (var mediaSourceInfo in mediaSources)
@@ -216,7 +210,7 @@ namespace MediaBrowser.Model.Dlna
return streams.OrderBy(i =>
{
// Nothing beats direct playing a file
- if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
+ if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource?.Protocol == MediaProtocol.File)
{
return 0;
}
@@ -235,7 +229,7 @@ namespace MediaBrowser.Model.Dlna
}
}).ThenBy(i =>
{
- switch (i.MediaSource.Protocol)
+ switch (i.MediaSource?.Protocol)
{
case MediaProtocol.File:
return 0;
@@ -246,7 +240,7 @@ namespace MediaBrowser.Model.Dlna
{
if (maxBitrate > 0)
{
- if (i.MediaSource.Bitrate.HasValue)
+ if (i.MediaSource?.Bitrate is not null)
{
return Math.Abs(i.MediaSource.Bitrate.Value - maxBitrate);
}
@@ -585,10 +579,10 @@ namespace MediaBrowser.Model.Dlna
MediaSource = item,
RunTimeTicks = item.RunTimeTicks,
Context = options.Context,
- DeviceProfile = options.Profile
+ DeviceProfile = options.Profile,
+ SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles)
};
- playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles);
var subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null;
var audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex);
@@ -659,7 +653,8 @@ namespace MediaBrowser.Model.Dlna
if (audioStreamIndex.HasValue)
{
playlistItem.AudioStreamIndex = audioStreamIndex;
- playlistItem.AudioCodecs = new[] { item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec };
+ var audioCodec = item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec;
+ playlistItem.AudioCodecs = audioCodec is null ? Array.Empty<string>() : new[] { audioCodec };
}
}
else if (directPlay == PlayMethod.DirectStream)
@@ -842,7 +837,7 @@ namespace MediaBrowser.Model.Dlna
if (videoStream is not null && videoStream.Level != 0)
{
- playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString());
+ playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString() ?? string.Empty);
}
// Prefer matching audio codecs, could do better here
@@ -871,7 +866,7 @@ namespace MediaBrowser.Model.Dlna
// Copy matching audio codec options
playlistItem.AudioSampleRate = audioStream.SampleRate;
- playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString());
+ playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString() ?? string.Empty);
if (!string.IsNullOrEmpty(audioStream.Profile))
{
@@ -880,7 +875,7 @@ namespace MediaBrowser.Model.Dlna
if (audioStream.Level != 0)
{
- playlistItem.SetOption(audioStream.Codec, "level", audioStream.Level.ToString());
+ playlistItem.SetOption(audioStream.Codec, "level", audioStream.Level.ToString() ?? string.Empty);
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 00543616d..fc146df30 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -1,9 +1,9 @@
-#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
@@ -34,9 +34,9 @@ namespace MediaBrowser.Model.Dlna
public DlnaProfileType MediaType { get; set; }
- public string Container { get; set; }
+ public string? Container { get; set; }
- public string SubProtocol { get; set; }
+ public string? SubProtocol { get; set; }
public long StartPositionTicks { get; set; }
@@ -80,11 +80,11 @@ namespace MediaBrowser.Model.Dlna
public float? MaxFramerate { get; set; }
- public DeviceProfile DeviceProfile { get; set; }
+ public required DeviceProfile DeviceProfile { get; set; }
- public string DeviceProfileId { get; set; }
+ public string? DeviceProfileId { get; set; }
- public string DeviceId { get; set; }
+ public string? DeviceId { get; set; }
public long? RunTimeTicks { get; set; }
@@ -92,21 +92,21 @@ namespace MediaBrowser.Model.Dlna
public bool EstimateContentLength { get; set; }
- public MediaSourceInfo MediaSource { get; set; }
+ public MediaSourceInfo? MediaSource { get; set; }
public string[] SubtitleCodecs { get; set; }
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
- public string SubtitleFormat { get; set; }
+ public string? SubtitleFormat { get; set; }
- public string PlaySessionId { get; set; }
+ public string? PlaySessionId { get; set; }
public TranscodeReason TranscodeReasons { get; set; }
public Dictionary<string, string> StreamOptions { get; private set; }
- public string MediaSourceId => MediaSource?.Id;
+ public string? MediaSourceId => MediaSource?.Id;
public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay)
&& PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay;
@@ -114,12 +114,12 @@ namespace MediaBrowser.Model.Dlna
/// <summary>
/// Gets the audio stream that will be used.
/// </summary>
- public MediaStream TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex);
+ public MediaStream? TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex);
/// <summary>
/// Gets the video stream that will be used.
/// </summary>
- public MediaStream TargetVideoStream => MediaSource?.VideoStream;
+ public MediaStream? TargetVideoStream => MediaSource?.VideoStream;
/// <summary>
/// Gets the audio sample rate that will be in the output stream.
@@ -259,7 +259,7 @@ namespace MediaBrowser.Model.Dlna
/// <summary>
/// Gets the audio sample rate that will be in the output stream.
/// </summary>
- public string TargetVideoProfile
+ public string? TargetVideoProfile
{
get
{
@@ -307,7 +307,7 @@ namespace MediaBrowser.Model.Dlna
/// Gets the target video codec tag.
/// </summary>
/// <value>The target video codec tag.</value>
- public string TargetVideoCodecTag
+ public string? TargetVideoCodecTag
{
get
{
@@ -364,7 +364,7 @@ namespace MediaBrowser.Model.Dlna
{
var stream = TargetAudioStream;
- string inputCodec = stream?.Codec;
+ string? inputCodec = stream?.Codec;
if (IsDirectStream)
{
@@ -389,7 +389,7 @@ namespace MediaBrowser.Model.Dlna
{
var stream = TargetVideoStream;
- string inputCodec = stream?.Codec;
+ string? inputCodec = stream?.Codec;
if (IsDirectStream)
{
@@ -417,7 +417,7 @@ namespace MediaBrowser.Model.Dlna
{
if (IsDirectStream)
{
- return MediaSource.Size;
+ return MediaSource?.Size;
}
if (RunTimeTicks.HasValue)
@@ -580,7 +580,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- public void SetOption(string qualifier, string name, string value)
+ public void SetOption(string? qualifier, string name, string value)
{
if (string.IsNullOrEmpty(qualifier))
{
@@ -597,7 +597,7 @@ namespace MediaBrowser.Model.Dlna
StreamOptions[name] = value;
}
- public string GetOption(string qualifier, string name)
+ public string? GetOption(string? qualifier, string name)
{
var value = GetOption(qualifier + "-" + name);
@@ -609,7 +609,7 @@ namespace MediaBrowser.Model.Dlna
return value;
}
- public string GetOption(string name)
+ public string? GetOption(string name)
{
if (StreamOptions.TryGetValue(name, out var value))
{
@@ -619,7 +619,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- public string ToUrl(string baseUrl, string accessToken)
+ public string ToUrl(string baseUrl, string? accessToken)
{
ArgumentException.ThrowIfNullOrEmpty(baseUrl);
@@ -686,7 +686,7 @@ namespace MediaBrowser.Model.Dlna
return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
}
- private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string accessToken)
+ private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string? accessToken)
{
var list = new List<NameValuePair>();
@@ -730,7 +730,7 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
- string liveStreamId = item.MediaSource?.LiveStreamId;
+ string? liveStreamId = item.MediaSource?.LiveStreamId;
list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
@@ -772,7 +772,7 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
}
- list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty));
+ list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty));
string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
string.Empty :
@@ -816,13 +816,18 @@ namespace MediaBrowser.Model.Dlna
return list;
}
- public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken)
+ public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string? accessToken)
{
return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
}
- public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
+ public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string? accessToken)
{
+ if (MediaSource is null)
+ {
+ return Enumerable.Empty<SubtitleStreamInfo>();
+ }
+
var list = new List<SubtitleStreamInfo>();
// HLS will preserve timestamps so we can just grab the full subtitle stream
@@ -856,27 +861,36 @@ namespace MediaBrowser.Model.Dlna
return list;
}
- private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
+ private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string? accessToken, long startPositionTicks)
{
if (enableAllProfiles)
{
foreach (var profile in DeviceProfile.SubtitleProfiles)
{
var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
-
- list.Add(info);
+ if (info is not null)
+ {
+ list.Add(info);
+ }
}
}
else
{
var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
-
- list.Add(info);
+ if (info is not null)
+ {
+ list.Add(info);
+ }
}
}
- private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
+ private SubtitleStreamInfo? GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string? accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
{
+ if (MediaSource is null)
+ {
+ return null;
+ }
+
var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
var info = new SubtitleStreamInfo
{
@@ -920,7 +934,7 @@ namespace MediaBrowser.Model.Dlna
return info;
}
- public int? GetTargetVideoBitDepth(string codec)
+ public int? GetTargetVideoBitDepth(string? codec)
{
var value = GetOption(codec, "videobitdepth");
@@ -932,7 +946,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- public int? GetTargetAudioBitDepth(string codec)
+ public int? GetTargetAudioBitDepth(string? codec)
{
var value = GetOption(codec, "audiobitdepth");
@@ -944,7 +958,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- public double? GetTargetVideoLevel(string codec)
+ public double? GetTargetVideoLevel(string? codec)
{
var value = GetOption(codec, "level");
@@ -956,7 +970,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- public int? GetTargetRefFrames(string codec)
+ public int? GetTargetRefFrames(string? codec)
{
var value = GetOption(codec, "maxrefframes");
@@ -968,7 +982,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- public int? GetTargetAudioChannels(string codec)
+ public int? GetTargetAudioChannels(string? codec)
{
var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
@@ -988,7 +1002,7 @@ namespace MediaBrowser.Model.Dlna
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
- var count = MediaSource.GetStreamCount(type);
+ var count = MediaSource?.GetStreamCount(type);
if (count.HasValue)
{
diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs
index 0fb996df9..35353e6fa 100644
--- a/MediaBrowser.Model/Querying/NextUpQuery.cs
+++ b/MediaBrowser.Model/Querying/NextUpQuery.cs
@@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Querying
EnableTotalRecordCount = true;
DisableFirstEpisode = false;
NextUpDateCutoff = DateTime.MinValue;
+ EnableResumable = false;
EnableRewatching = false;
}
@@ -84,6 +85,11 @@ namespace MediaBrowser.Model.Querying
public DateTime NextUpDateCutoff { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether to include resumable episodes as next up.
+ /// </summary>
+ public bool EnableResumable { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether getting rewatching next up list.
/// </summary>
public bool EnableRewatching { get; set; }
diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
index f58f5f7a3..c24f4e2fc 100644
--- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
@@ -177,9 +177,11 @@ namespace MediaBrowser.Providers.MediaInfo
var format = imageStream.Codec switch
{
+ "bmp" => ImageFormat.Bmp,
+ "gif" => ImageFormat.Gif,
"mjpeg" => ImageFormat.Jpg,
"png" => ImageFormat.Png,
- "gif" => ImageFormat.Gif,
+ "webp" => ImageFormat.Webp,
_ => ImageFormat.Jpg
};
diff --git a/global.json b/global.json
new file mode 100644
index 000000000..24335d7a0
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "7.0.0",
+ "rollForward": "latestMinor"
+ }
+}
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index 2d980db18..5a1d3dc5f 100644
--- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -489,10 +489,8 @@ public class SkiaEncoder : IImageEncoder
Directory.CreateDirectory(directory);
using (var outputStream = new SKFileWStream(outputPath))
{
- using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels()))
- {
- pixmap.Encode(outputStream, skiaOutputFormat, quality);
- }
+ using var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels());
+ pixmap.Encode(outputStream, skiaOutputFormat, quality);
}
return outputPath;
diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs
index 44e06bb52..4f16e294b 100644
--- a/src/Jellyfin.Drawing/ImageProcessor.cs
+++ b/src/Jellyfin.Drawing/ImageProcessor.cs
@@ -111,10 +111,8 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{
var file = await ProcessImage(options).ConfigureAwait(false);
- using (var fileStream = AsyncFile.OpenRead(file.Path))
- {
- await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
- }
+ using var fileStream = AsyncFile.OpenRead(file.Path);
+ await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
}
/// <inheritdoc />
diff --git a/src/Jellyfin.Extensions/StreamExtensions.cs b/src/Jellyfin.Extensions/StreamExtensions.cs
index d76558ded..182996852 100644
--- a/src/Jellyfin.Extensions/StreamExtensions.cs
+++ b/src/Jellyfin.Extensions/StreamExtensions.cs
@@ -26,10 +26,8 @@ namespace Jellyfin.Extensions
/// <returns>All lines in the stream.</returns>
public static string[] ReadAllLines(this Stream stream, Encoding encoding)
{
- using (StreamReader reader = new StreamReader(stream, encoding))
- {
- return ReadAllLines(reader).ToArray();
- }
+ using StreamReader reader = new StreamReader(stream, encoding);
+ return ReadAllLines(reader).ToArray();
}
/// <summary>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
index fe0d7fc90..1f908d7e0 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
@@ -12,17 +12,16 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
[Fact]
public void Parse_Valid_Success()
{
- using (var stream = File.OpenRead("Test Data/example.ass"))
- {
- var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "ass");
- Assert.Single(parsed.TrackEvents);
- var trackEvent = parsed.TrackEvents[0];
+ using var stream = File.OpenRead("Test Data/example.ass");
- Assert.Equal("1", trackEvent.Id);
- Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
- Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
- Assert.Equal("{\\pos(400,570)}Like an Angel with pity on nobody" + Environment.NewLine + "The second line in subtitle", trackEvent.Text);
- }
+ var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "ass");
+ Assert.Single(parsed.TrackEvents);
+ var trackEvent = parsed.TrackEvents[0];
+
+ Assert.Equal("1", trackEvent.Id);
+ Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
+ Assert.Equal("{\\pos(400,570)}Like an Angel with pity on nobody" + Environment.NewLine + "The second line in subtitle", trackEvent.Text);
}
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
index 2aebee556..b7152961c 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
@@ -12,45 +12,43 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
[Fact]
public void Parse_Valid_Success()
{
- using (var stream = File.OpenRead("Test Data/example.srt"))
- {
- var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "srt");
- Assert.Equal(2, parsed.TrackEvents.Count);
-
- var trackEvent1 = parsed.TrackEvents[0];
- Assert.Equal("1", trackEvent1.Id);
- Assert.Equal(TimeSpan.Parse("00:02:17.440", CultureInfo.InvariantCulture).Ticks, trackEvent1.StartPositionTicks);
- Assert.Equal(TimeSpan.Parse("00:02:20.375", CultureInfo.InvariantCulture).Ticks, trackEvent1.EndPositionTicks);
- Assert.Equal("Senator, we're making" + Environment.NewLine + "our final approach into Coruscant.", trackEvent1.Text);
-
- var trackEvent2 = parsed.TrackEvents[1];
- Assert.Equal("2", trackEvent2.Id);
- Assert.Equal(TimeSpan.Parse("00:02:20.476", CultureInfo.InvariantCulture).Ticks, trackEvent2.StartPositionTicks);
- Assert.Equal(TimeSpan.Parse("00:02:22.501", CultureInfo.InvariantCulture).Ticks, trackEvent2.EndPositionTicks);
- Assert.Equal("Very good, Lieutenant.", trackEvent2.Text);
- }
+ using var stream = File.OpenRead("Test Data/example.srt");
+
+ var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "srt");
+ Assert.Equal(2, parsed.TrackEvents.Count);
+
+ var trackEvent1 = parsed.TrackEvents[0];
+ Assert.Equal("1", trackEvent1.Id);
+ Assert.Equal(TimeSpan.Parse("00:02:17.440", CultureInfo.InvariantCulture).Ticks, trackEvent1.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:02:20.375", CultureInfo.InvariantCulture).Ticks, trackEvent1.EndPositionTicks);
+ Assert.Equal("Senator, we're making" + Environment.NewLine + "our final approach into Coruscant.", trackEvent1.Text);
+
+ var trackEvent2 = parsed.TrackEvents[1];
+ Assert.Equal("2", trackEvent2.Id);
+ Assert.Equal(TimeSpan.Parse("00:02:20.476", CultureInfo.InvariantCulture).Ticks, trackEvent2.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:02:22.501", CultureInfo.InvariantCulture).Ticks, trackEvent2.EndPositionTicks);
+ Assert.Equal("Very good, Lieutenant.", trackEvent2.Text);
}
[Fact]
public void Parse_EmptyNewlineBetweenText_Success()
{
- using (var stream = File.OpenRead("Test Data/example2.srt"))
- {
- var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "srt");
- Assert.Equal(2, parsed.TrackEvents.Count);
-
- var trackEvent1 = parsed.TrackEvents[0];
- Assert.Equal("311", trackEvent1.Id);
- Assert.Equal(TimeSpan.Parse("00:16:46.465", CultureInfo.InvariantCulture).Ticks, trackEvent1.StartPositionTicks);
- Assert.Equal(TimeSpan.Parse("00:16:49.009", CultureInfo.InvariantCulture).Ticks, trackEvent1.EndPositionTicks);
- Assert.Equal("Una vez que la gente se entere" + Environment.NewLine + Environment.NewLine + "de que ustedes están aquí,", trackEvent1.Text);
-
- var trackEvent2 = parsed.TrackEvents[1];
- Assert.Equal("312", trackEvent2.Id);
- Assert.Equal(TimeSpan.Parse("00:16:49.092", CultureInfo.InvariantCulture).Ticks, trackEvent2.StartPositionTicks);
- Assert.Equal(TimeSpan.Parse("00:16:51.470", CultureInfo.InvariantCulture).Ticks, trackEvent2.EndPositionTicks);
- Assert.Equal("este lugar se convertirá" + Environment.NewLine + Environment.NewLine + "en un maldito zoológico.", trackEvent2.Text);
- }
+ using var stream = File.OpenRead("Test Data/example2.srt");
+
+ var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "srt");
+ Assert.Equal(2, parsed.TrackEvents.Count);
+
+ var trackEvent1 = parsed.TrackEvents[0];
+ Assert.Equal("311", trackEvent1.Id);
+ Assert.Equal(TimeSpan.Parse("00:16:46.465", CultureInfo.InvariantCulture).Ticks, trackEvent1.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:16:49.009", CultureInfo.InvariantCulture).Ticks, trackEvent1.EndPositionTicks);
+ Assert.Equal("Una vez que la gente se entere" + Environment.NewLine + Environment.NewLine + "de que ustedes están aquí,", trackEvent1.Text);
+
+ var trackEvent2 = parsed.TrackEvents[1];
+ Assert.Equal("312", trackEvent2.Id);
+ Assert.Equal(TimeSpan.Parse("00:16:49.092", CultureInfo.InvariantCulture).Ticks, trackEvent2.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:16:51.470", CultureInfo.InvariantCulture).Ticks, trackEvent2.EndPositionTicks);
+ Assert.Equal("este lugar se convertirá" + Environment.NewLine + Environment.NewLine + "en un maldito zoológico.", trackEvent2.Text);
}
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
index 6abf2d26c..5b7aa7eaa 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
@@ -18,22 +18,21 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
[MemberData(nameof(Parse_MultipleDialogues_TestData))]
public void Parse_MultipleDialogues_Success(string ssa, IReadOnlyList<SubtitleTrackEvent> expectedSubtitleTrackEvents)
{
- using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
- {
- SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, "ssa");
+ using Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa));
- Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
+ SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, "ssa");
- for (int i = 0; i < expectedSubtitleTrackEvents.Count; ++i)
- {
- SubtitleTrackEvent expected = expectedSubtitleTrackEvents[i];
- SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[i];
+ Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
+
+ for (int i = 0; i < expectedSubtitleTrackEvents.Count; ++i)
+ {
+ SubtitleTrackEvent expected = expectedSubtitleTrackEvents[i];
+ SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[i];
- Assert.Equal(expected.Id, actual.Id);
- Assert.Equal(expected.Text, actual.Text);
- Assert.Equal(expected.StartPositionTicks, actual.StartPositionTicks);
- Assert.Equal(expected.EndPositionTicks, actual.EndPositionTicks);
- }
+ Assert.Equal(expected.Id, actual.Id);
+ Assert.Equal(expected.Text, actual.Text);
+ Assert.Equal(expected.StartPositionTicks, actual.StartPositionTicks);
+ Assert.Equal(expected.EndPositionTicks, actual.EndPositionTicks);
}
}
@@ -73,17 +72,16 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
[Fact]
public void Parse_Valid_Success()
{
- using (var stream = File.OpenRead("Test Data/example.ssa"))
- {
- var parsed = _parser.Parse(stream, "ssa");
- Assert.Single(parsed.TrackEvents);
- var trackEvent = parsed.TrackEvents[0];
+ using var stream = File.OpenRead("Test Data/example.ssa");
- Assert.Equal("1", trackEvent.Id);
- Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
- Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
- Assert.Equal("{\\pos(400,570)}Like an angel with pity on nobody", trackEvent.Text);
- }
+ var parsed = _parser.Parse(stream, "ssa");
+ Assert.Single(parsed.TrackEvents);
+ var trackEvent = parsed.TrackEvents[0];
+
+ Assert.Equal("1", trackEvent.Id);
+ Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
+ Assert.Equal("{\\pos(400,570)}Like an angel with pity on nobody", trackEvent.Text);
}
}
}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
index c30dad6f9..210ce4a47 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
@@ -351,11 +351,11 @@ namespace Jellyfin.Model.Tests
// Assert.Contains(uri.Extension, containers);
// Check expected video codec (1)
- Assert.Contains(targetVideoStream.Codec, streamInfo.TargetVideoCodec);
+ Assert.Contains(targetVideoStream?.Codec, streamInfo.TargetVideoCodec);
Assert.Single(streamInfo.TargetVideoCodec);
// Check expected audio codecs (1)
- Assert.Contains(targetAudioStream.Codec, streamInfo.TargetAudioCodec);
+ Assert.Contains(targetAudioStream?.Codec, streamInfo.TargetAudioCodec);
Assert.Single(streamInfo.TargetAudioCodec);
// Assert.Single(val.AudioCodecs);
@@ -410,13 +410,13 @@ namespace Jellyfin.Model.Tests
else
{
// Check expected video codec (1)
- Assert.Contains(targetVideoStream.Codec, streamInfo.TargetVideoCodec);
+ Assert.Contains(targetVideoStream?.Codec, streamInfo.TargetVideoCodec);
Assert.Single(streamInfo.TargetVideoCodec);
if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal))
{
// Check expected audio codecs (1)
- if (!targetAudioStream.IsExternal)
+ if (targetAudioStream?.IsExternal == false)
{
// Check expected audio codecs (1)
if (streamInfo.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
@@ -432,7 +432,7 @@ namespace Jellyfin.Model.Tests
else if (transcodeMode.Equals("Remux", StringComparison.Ordinal))
{
// Check expected audio codecs (1)
- Assert.Contains(targetAudioStream.Codec, streamInfo.AudioCodecs);
+ Assert.Contains(targetAudioStream?.Codec, streamInfo.AudioCodecs);
Assert.Single(streamInfo.AudioCodecs);
}
@@ -440,10 +440,10 @@ namespace Jellyfin.Model.Tests
var videoStream = targetVideoStream;
Assert.False(streamInfo.EstimateContentLength);
Assert.Equal(TranscodeSeekInfo.Auto, streamInfo.TranscodeSeekInfo);
- Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, streamInfo.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty<string>());
- Assert.Equal(videoStream.Level, streamInfo.TargetVideoLevel);
- Assert.Equal(videoStream.BitDepth, streamInfo.TargetVideoBitDepth);
- Assert.InRange(streamInfo.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
+ Assert.Contains(videoStream?.Profile?.ToLowerInvariant() ?? string.Empty, streamInfo.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty<string>());
+ Assert.Equal(videoStream?.Level, streamInfo.TargetVideoLevel);
+ Assert.Equal(videoStream?.BitDepth, streamInfo.TargetVideoBitDepth);
+ Assert.InRange(streamInfo.VideoBitrate.GetValueOrDefault(), videoStream?.BitRate.GetValueOrDefault() ?? 0, int.MaxValue);
// Audio codec not supported
if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
@@ -452,7 +452,7 @@ namespace Jellyfin.Model.Tests
if (options.AudioStreamIndex >= 0)
{
// TODO:fixme
- if (!targetAudioStream.IsExternal)
+ if (targetAudioStream?.IsExternal == false)
{
Assert.DoesNotContain(targetAudioStream.Codec, streamInfo.AudioCodecs);
}
@@ -488,16 +488,16 @@ namespace Jellyfin.Model.Tests
private static async ValueTask<T> TestData<T>(string name)
{
var path = Path.Join("Test Data", typeof(T).Name + "-" + name + ".json");
- using (var stream = File.OpenRead(path))
- {
- var value = await JsonSerializer.DeserializeAsync<T>(stream, JsonDefaults.Options);
- if (value is not null)
- {
- return value;
- }
- throw new SerializationException("Invalid test data: " + name);
+ using var stream = File.OpenRead(path);
+
+ var value = await JsonSerializer.DeserializeAsync<T>(stream, JsonDefaults.Options);
+ if (value is not null)
+ {
+ return value;
}
+
+ throw new SerializationException("Invalid test data: " + name);
}
private StreamBuilder GetStreamBuilder()
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
index 6b2d9021c..2bc686a33 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
@@ -98,9 +98,11 @@ namespace Jellyfin.Providers.Tests.MediaInfo
[InlineData(null, null, 1, ImageType.Primary, ImageFormat.Jpg)] // no label, finds primary
[InlineData("backdrop", null, 2, ImageType.Backdrop, ImageFormat.Jpg)] // uses label to find index 2, not just pulling first stream
[InlineData("cover", null, 2, ImageType.Primary, ImageFormat.Jpg)] // uses label to find index 2, not just pulling first stream
+ [InlineData(null, "bmp", 1, ImageType.Primary, ImageFormat.Bmp)]
+ [InlineData(null, "gif", 1, ImageType.Primary, ImageFormat.Gif)]
[InlineData(null, "mjpeg", 1, ImageType.Primary, ImageFormat.Jpg)]
[InlineData(null, "png", 1, ImageType.Primary, ImageFormat.Png)]
- [InlineData(null, "gif", 1, ImageType.Primary, ImageFormat.Gif)]
+ [InlineData(null, "webp", 1, ImageType.Primary, ImageFormat.Webp)]
public async void GetImage_Embedded_ReturnsCorrectSelection(string label, string? codec, int targetIndex, ImageType type, ImageFormat? expectedFormat)
{
var streams = new List<MediaStream>();
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
index e1d2bb2d5..d4f28f327 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
@@ -96,7 +96,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect
var days = JsonSerializer.Deserialize<IReadOnlyList<DayDto>>(bytes, _jsonOptions);
Assert.NotNull(days);
- Assert.Equal(1, days!.Count);
+ Assert.Single(days);
var dayDto = days[0];
Assert.Equal("20454", dayDto.StationId);
@@ -110,7 +110,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect
Assert.Equal(2, dayDto.Programs[0].AudioProperties.Count);
Assert.Equal("stereo", dayDto.Programs[0].AudioProperties[0]);
Assert.Equal("cc", dayDto.Programs[0].AudioProperties[1]);
- Assert.Equal(1, dayDto.Programs[0].VideoProperties.Count);
+ Assert.Single(dayDto.Programs[0].VideoProperties);
Assert.Equal("hdtv", dayDto.Programs[0].VideoProperties[0]);
}
@@ -126,13 +126,13 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect
Assert.NotNull(programDtos);
Assert.Equal(2, programDtos!.Count);
Assert.Equal("EP000000060003", programDtos[0].ProgramId);
- Assert.Equal(1, programDtos[0].Titles.Count);
+ Assert.Single(programDtos[0].Titles);
Assert.Equal("'Allo 'Allo!", programDtos[0].Titles[0].Title120);
Assert.Equal("Series", programDtos[0].EventDetails?.SubType);
Assert.Equal("en", programDtos[0].Descriptions?.Description1000[0].DescriptionLanguage);
Assert.Equal("A disguised British Intelligence officer is sent to help the airmen.", programDtos[0].Descriptions?.Description1000[0].Description);
Assert.Equal(new DateTime(1985, 11, 04), programDtos[0].OriginalAirDate);
- Assert.Equal(1, programDtos[0].Genres.Count);
+ Assert.Single(programDtos[0].Genres);
Assert.Equal("Sitcom", programDtos[0].Genres[0]);
Assert.Equal("The Poloceman Cometh", programDtos[0].EpisodeTitle150);
Assert.Equal(2, programDtos[0].Metadata[0].Gracenote?.Season);
@@ -161,7 +161,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect
var showImagesDtos = JsonSerializer.Deserialize<IReadOnlyList<ShowImagesDto>>(bytes, _jsonOptions);
Assert.NotNull(showImagesDtos);
- Assert.Equal(1, showImagesDtos!.Count);
+ Assert.Single(showImagesDtos!);
Assert.Equal("SH00712240", showImagesDtos[0].ProgramId);
Assert.Equal(4, showImagesDtos[0].Data.Count);
Assert.Equal("135", showImagesDtos[0].Data[0].Width);
diff --git a/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs b/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs
index 797fc8f64..93e065685 100644
--- a/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs
+++ b/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs
@@ -22,7 +22,7 @@ namespace Jellyfin.Server.Tests
Assert.Single(test.Query);
var (k, v) = test.Query.First();
Assert.Equal(key, k);
- Assert.Empty(v);
+ Assert.True(StringValues.IsNullOrEmpty(v));
}
}
}
diff --git a/tests/jellyfin-tests.ruleset b/tests/jellyfin-tests.ruleset
index e2abaf5bb..9d133da56 100644
--- a/tests/jellyfin-tests.ruleset
+++ b/tests/jellyfin-tests.ruleset
@@ -19,4 +19,10 @@
<!-- CA2234: Pass system uri objects instead of strings -->
<Rule Id="CA2234" Action="Info" />
</Rules>
+
+ <!-- xUnit -->
+ <Rules AnalyzerId="xUnit" RuleNamespace="xUnit">
+ <!-- Test methods must have a supported return type. -->
+ <Rule Id="xUnit1028" Action="None" />
+ </Rules>
</RuleSet>