aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/codeql-analysis.yml6
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs80
-rw-r--r--Emby.Server.Implementations/Localization/Core/ml.json4
-rw-r--r--Emby.Server.Implementations/SystemManager.cs108
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs29
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs47
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs35
-rw-r--r--Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs8
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs24
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs12
-rw-r--r--MediaBrowser.Controller/ISystemManager.cs34
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs41
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs6
13 files changed, 243 insertions, 191 deletions
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 8e764b019..4c9d68d11 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '7.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0
+ uses: github/codeql-action/init@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0
+ uses: github/codeql-action/autobuild@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0
+ uses: github/codeql-action/analyze@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 8518b1352..3253ea026 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -101,7 +101,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Prometheus.DotNetRuntime;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
@@ -133,7 +132,7 @@ namespace Emby.Server.Implementations
/// <value>All concrete types.</value>
private Type[] _allConcreteTypes;
- private bool _disposed = false;
+ private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationHost"/> class.
@@ -184,26 +183,16 @@ namespace Emby.Server.Implementations
public bool CoreStartupHasCompleted { get; private set; }
- public virtual bool CanLaunchWebBrowser => Environment.UserInteractive
- && !_startupOptions.IsService
- && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS());
-
/// <summary>
/// Gets the <see cref="INetworkManager"/> singleton instance.
/// </summary>
public INetworkManager NetManager { get; private set; }
- /// <summary>
- /// Gets a value indicating whether this instance has changes that require the entire application to restart.
- /// </summary>
- /// <value><c>true</c> if this instance has pending application restart; otherwise, <c>false</c>.</value>
- public bool HasPendingRestart { get; private set; }
-
/// <inheritdoc />
- public bool IsShuttingDown { get; private set; }
+ public bool HasPendingRestart { get; private set; }
/// <inheritdoc />
- public bool ShouldRestart { get; private set; }
+ public bool ShouldRestart { get; set; }
/// <summary>
/// Gets the logger.
@@ -507,6 +496,8 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IFileSystem, ManagedFileSystem>();
serviceCollection.AddSingleton<IShortcutHandler, MbLinkShortcutHandler>();
+ serviceCollection.AddScoped<ISystemManager, SystemManager>();
+
serviceCollection.AddSingleton<TmdbClientManager>();
serviceCollection.AddSingleton(NetManager);
@@ -850,24 +841,6 @@ namespace Emby.Server.Implementations
}
}
- /// <inheritdoc />
- public void Restart()
- {
- ShouldRestart = true;
- Shutdown();
- }
-
- /// <inheritdoc />
- public void Shutdown()
- {
- Task.Run(async () =>
- {
- await Task.Delay(100).ConfigureAwait(false);
- IsShuttingDown = true;
- Resolve<IHostApplicationLifetime>().StopApplication();
- });
- }
-
/// <summary>
/// Gets the composable part assemblies.
/// </summary>
@@ -923,49 +896,6 @@ namespace Emby.Server.Implementations
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
- /// <summary>
- /// Gets the system status.
- /// </summary>
- /// <param name="request">Where this request originated.</param>
- /// <returns>SystemInfo.</returns>
- public SystemInfo GetSystemInfo(HttpRequest request)
- {
- return new SystemInfo
- {
- HasPendingRestart = HasPendingRestart,
- IsShuttingDown = IsShuttingDown,
- Version = ApplicationVersionString,
- WebSocketPortNumber = HttpPort,
- CompletedInstallations = Resolve<IInstallationManager>().CompletedInstallations.ToArray(),
- Id = SystemId,
- ProgramDataPath = ApplicationPaths.ProgramDataPath,
- WebPath = ApplicationPaths.WebPath,
- LogPath = ApplicationPaths.LogDirectoryPath,
- ItemsByNamePath = ApplicationPaths.InternalMetadataPath,
- InternalMetadataPath = ApplicationPaths.InternalMetadataPath,
- CachePath = ApplicationPaths.CachePath,
- CanLaunchWebBrowser = CanLaunchWebBrowser,
- TranscodingTempPath = ConfigurationManager.GetTranscodePath(),
- ServerName = FriendlyName,
- LocalAddress = GetSmartApiUrl(request),
- SupportsLibraryMonitor = true,
- PackageName = _startupOptions.PackageName
- };
- }
-
- public PublicSystemInfo GetPublicSystemInfo(HttpRequest request)
- {
- return new PublicSystemInfo
- {
- Version = ApplicationVersionString,
- ProductName = ApplicationProductName,
- Id = SystemId,
- ServerName = FriendlyName,
- LocalAddress = GetSmartApiUrl(request),
- StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted
- };
- }
-
/// <inheritdoc/>
public string GetSmartApiUrl(IPAddress remoteAddr)
{
diff --git a/Emby.Server.Implementations/Localization/Core/ml.json b/Emby.Server.Implementations/Localization/Core/ml.json
index 0620fbcdb..0b50fa529 100644
--- a/Emby.Server.Implementations/Localization/Core/ml.json
+++ b/Emby.Server.Implementations/Localization/Core/ml.json
@@ -121,5 +121,7 @@
"TaskOptimizeDatabaseDescription": "ഡാറ്റാബേസ് ചുരുക്കുകയും സ്വതന്ത്ര ഇടം വെട്ടിച്ചുരുക്കുകയും ചെയ്യുന്നു. ലൈബ്രറി സ്‌കാൻ ചെയ്‌തതിനുശേഷം അല്ലെങ്കിൽ ഡാറ്റാബേസ് പരിഷ്‌ക്കരണങ്ങളെ സൂചിപ്പിക്കുന്ന മറ്റ് മാറ്റങ്ങൾ ചെയ്‌തതിന് ശേഷം ഈ ടാസ്‌ക് പ്രവർത്തിപ്പിക്കുന്നത് പ്രകടനം മെച്ചപ്പെടുത്തും.",
"TaskOptimizeDatabase": "ഡാറ്റാബേസ് ഒപ്റ്റിമൈസ് ചെയ്യുക",
"HearingImpaired": "കേൾവി തകരാറുകൾ",
- "External": "പുറമേയുള്ള"
+ "External": "പുറമേയുള്ള",
+ "TaskKeyframeExtractorDescription": "കൂടുതൽ കൃത്യമായ HLS പ്ലേലിസ്റ്റുകൾ സൃഷ്‌ടിക്കുന്നതിന് വീഡിയോ ഫയലുകളിൽ നിന്ന് കീഫ്രെയിമുകൾ എക്‌സ്‌ട്രാക്‌റ്റ് ചെയ്യുന്നു. ഈ പ്രവർത്തനം പൂർത്തിയാവാൻ കുറച്ചധികം സമയം എടുത്തേക്കാം.",
+ "TaskKeyframeExtractor": "കീഫ്രെയിം എക്സ്ട്രാക്റ്റർ"
}
diff --git a/Emby.Server.Implementations/SystemManager.cs b/Emby.Server.Implementations/SystemManager.cs
new file mode 100644
index 000000000..5e9c424e9
--- /dev/null
+++ b/Emby.Server.Implementations/SystemManager.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Updates;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Hosting;
+
+namespace Emby.Server.Implementations;
+
+/// <inheritdoc />
+public class SystemManager : ISystemManager
+{
+ private readonly IHostApplicationLifetime _applicationLifetime;
+ private readonly IServerApplicationHost _applicationHost;
+ private readonly IServerApplicationPaths _applicationPaths;
+ private readonly IServerConfigurationManager _configurationManager;
+ private readonly IStartupOptions _startupOptions;
+ private readonly IInstallationManager _installationManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SystemManager"/> class.
+ /// </summary>
+ /// <param name="applicationLifetime">Instance of <see cref="IHostApplicationLifetime"/>.</param>
+ /// <param name="applicationHost">Instance of <see cref="IServerApplicationHost"/>.</param>
+ /// <param name="applicationPaths">Instance of <see cref="IServerApplicationPaths"/>.</param>
+ /// <param name="configurationManager">Instance of <see cref="IServerConfigurationManager"/>.</param>
+ /// <param name="startupOptions">Instance of <see cref="IStartupOptions"/>.</param>
+ /// <param name="installationManager">Instance of <see cref="IInstallationManager"/>.</param>
+ public SystemManager(
+ IHostApplicationLifetime applicationLifetime,
+ IServerApplicationHost applicationHost,
+ IServerApplicationPaths applicationPaths,
+ IServerConfigurationManager configurationManager,
+ IStartupOptions startupOptions,
+ IInstallationManager installationManager)
+ {
+ _applicationLifetime = applicationLifetime;
+ _applicationHost = applicationHost;
+ _applicationPaths = applicationPaths;
+ _configurationManager = configurationManager;
+ _startupOptions = startupOptions;
+ _installationManager = installationManager;
+ }
+
+ private bool CanLaunchWebBrowser => Environment.UserInteractive
+ && !_startupOptions.IsService
+ && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS());
+
+ /// <inheritdoc />
+ public SystemInfo GetSystemInfo(HttpRequest request)
+ {
+ return new SystemInfo
+ {
+ HasPendingRestart = _applicationHost.HasPendingRestart,
+ IsShuttingDown = _applicationLifetime.ApplicationStopping.IsCancellationRequested,
+ Version = _applicationHost.ApplicationVersionString,
+ WebSocketPortNumber = _applicationHost.HttpPort,
+ CompletedInstallations = _installationManager.CompletedInstallations.ToArray(),
+ Id = _applicationHost.SystemId,
+ ProgramDataPath = _applicationPaths.ProgramDataPath,
+ WebPath = _applicationPaths.WebPath,
+ LogPath = _applicationPaths.LogDirectoryPath,
+ ItemsByNamePath = _applicationPaths.InternalMetadataPath,
+ InternalMetadataPath = _applicationPaths.InternalMetadataPath,
+ CachePath = _applicationPaths.CachePath,
+ CanLaunchWebBrowser = CanLaunchWebBrowser,
+ TranscodingTempPath = _configurationManager.GetTranscodePath(),
+ ServerName = _applicationHost.FriendlyName,
+ LocalAddress = _applicationHost.GetSmartApiUrl(request),
+ SupportsLibraryMonitor = true,
+ PackageName = _startupOptions.PackageName
+ };
+ }
+
+ /// <inheritdoc />
+ public PublicSystemInfo GetPublicSystemInfo(HttpRequest request)
+ {
+ return new PublicSystemInfo
+ {
+ Version = _applicationHost.ApplicationVersionString,
+ ProductName = _applicationHost.Name,
+ Id = _applicationHost.SystemId,
+ ServerName = _applicationHost.FriendlyName,
+ LocalAddress = _applicationHost.GetSmartApiUrl(request),
+ StartupWizardCompleted = _configurationManager.CommonConfiguration.IsStartupWizardCompleted
+ };
+ }
+
+ /// <inheritdoc />
+ public void Restart() => ShutdownInternal(true);
+
+ /// <inheritdoc />
+ public void Shutdown() => ShutdownInternal(false);
+
+ private void ShutdownInternal(bool restart)
+ {
+ Task.Run(async () =>
+ {
+ await Task.Delay(100).ConfigureAwait(false);
+ _applicationHost.ShouldRestart = restart;
+ _applicationLifetime.StopApplication();
+ });
+ }
+}
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 4026a38f8..7bf366e5d 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -45,6 +45,8 @@ public class DynamicHlsController : BaseJellyfinApiController
private const string DefaultEventEncoderPreset = "superfast";
private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls;
+ private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0);
+
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
@@ -1705,16 +1707,28 @@ public class DynamicHlsController : BaseJellyfinApiController
var audioCodec = _encodingHelper.GetAudioEncoder(state);
var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
+ // opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer
+ var strictArgs = string.Empty;
+ var actualOutputAudioCodec = state.ActualOutputAudioCodec;
+ if (string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.EncoderVersion < _minFFmpegFlacInMp4))
+ {
+ strictArgs = " -strict -2";
+ }
+
if (!state.IsOutputVideo)
{
if (EncodingHelper.IsCopyCodec(audioCodec))
{
- return "-acodec copy -strict -2" + bitStreamArgs;
+ return "-acodec copy" + bitStreamArgs + strictArgs;
}
var audioTranscodeParams = string.Empty;
- audioTranscodeParams += "-acodec " + audioCodec + bitStreamArgs;
+ audioTranscodeParams += "-acodec " + audioCodec + bitStreamArgs + strictArgs;
var audioBitrate = state.OutputAudioBitrate;
var audioChannels = state.OutputAudioChannels;
@@ -1746,17 +1760,6 @@ public class DynamicHlsController : BaseJellyfinApiController
return audioTranscodeParams;
}
- // dts, flac, opus and truehd are experimental in mp4 muxer
- var strictArgs = string.Empty;
- var actualOutputAudioCodec = state.ActualOutputAudioCodec;
- if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
- || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
- || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
- || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase))
- {
- strictArgs = " -strict -2";
- }
-
if (EncodingHelper.IsCopyCodec(audioCodec))
{
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index 42ac4a9b4..11095a97f 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -10,7 +10,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
@@ -26,32 +25,36 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
public class SystemController : BaseJellyfinApiController
{
+ private readonly ILogger<SystemController> _logger;
private readonly IServerApplicationHost _appHost;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
- private readonly INetworkManager _network;
- private readonly ILogger<SystemController> _logger;
+ private readonly INetworkManager _networkManager;
+ private readonly ISystemManager _systemManager;
/// <summary>
/// Initializes a new instance of the <see cref="SystemController"/> class.
/// </summary>
- /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
+ /// <param name="logger">Instance of <see cref="ILogger{SystemController}"/> interface.</param>
+ /// <param name="appPaths">Instance of <see cref="IServerApplicationPaths"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="fileSystem">Instance of <see cref="IFileSystem"/> interface.</param>
- /// <param name="network">Instance of <see cref="INetworkManager"/> interface.</param>
- /// <param name="logger">Instance of <see cref="ILogger{SystemController}"/> interface.</param>
+ /// <param name="networkManager">Instance of <see cref="INetworkManager"/> interface.</param>
+ /// <param name="systemManager">Instance of <see cref="ISystemManager"/> interface.</param>
public SystemController(
- IServerConfigurationManager serverConfigurationManager,
+ ILogger<SystemController> logger,
IServerApplicationHost appHost,
+ IServerApplicationPaths appPaths,
IFileSystem fileSystem,
- INetworkManager network,
- ILogger<SystemController> logger)
+ INetworkManager networkManager,
+ ISystemManager systemManager)
{
- _appPaths = serverConfigurationManager.ApplicationPaths;
+ _logger = logger;
_appHost = appHost;
+ _appPaths = appPaths;
_fileSystem = fileSystem;
- _network = network;
- _logger = logger;
+ _networkManager = networkManager;
+ _systemManager = systemManager;
}
/// <summary>
@@ -65,9 +68,7 @@ public class SystemController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult<SystemInfo> GetSystemInfo()
- {
- return _appHost.GetSystemInfo(Request);
- }
+ => _systemManager.GetSystemInfo(Request);
/// <summary>
/// Gets public information about the server.
@@ -77,9 +78,7 @@ public class SystemController : BaseJellyfinApiController
[HttpGet("Info/Public")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<PublicSystemInfo> GetPublicSystemInfo()
- {
- return _appHost.GetPublicSystemInfo(Request);
- }
+ => _systemManager.GetPublicSystemInfo(Request);
/// <summary>
/// Pings the system.
@@ -90,9 +89,7 @@ public class SystemController : BaseJellyfinApiController
[HttpPost("Ping", Name = "PostPingSystem")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<string> PingSystem()
- {
- return _appHost.Name;
- }
+ => _appHost.Name;
/// <summary>
/// Restarts the application.
@@ -106,7 +103,7 @@ public class SystemController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult RestartApplication()
{
- _appHost.Restart();
+ _systemManager.Restart();
return NoContent();
}
@@ -122,7 +119,7 @@ public class SystemController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult ShutdownApplication()
{
- _appHost.Shutdown();
+ _systemManager.Shutdown();
return NoContent();
}
@@ -180,7 +177,7 @@ public class SystemController : BaseJellyfinApiController
return new EndPointInfo
{
IsLocal = HttpContext.IsLocal(),
- IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP())
+ IsInNetwork = _networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP())
};
}
@@ -218,7 +215,7 @@ public class SystemController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
{
- var result = _network.GetMacAddresses()
+ var result = _networkManager.GetMacAddresses()
.Select(i => new WakeOnLanInfo(i));
return Ok(result);
}
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index fe602fba3..276a09f41 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -200,13 +200,6 @@ public class DynamicHlsHelper
if (state.VideoStream is not null && state.VideoRequest is not null)
{
- // Provide a workaround for the case issue between flac and fLaC.
- var flacWaPlaylist = ApplyFlacCaseWorkaround(state, basicPlaylist.ToString());
- if (!string.IsNullOrEmpty(flacWaPlaylist))
- {
- builder.Append(flacWaPlaylist);
- }
-
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
// Provide SDR HEVC entrance for backward compatibility.
@@ -236,14 +229,7 @@ public class DynamicHlsHelper
}
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
- var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);
-
- // Provide a workaround for the case issue between flac and fLaC.
- flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString());
- if (!string.IsNullOrEmpty(flacWaPlaylist))
- {
- builder.Append(flacWaPlaylist);
- }
+ AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);
// Restore the video codec
state.OutputVideoCodec = "copy";
@@ -274,13 +260,6 @@ public class DynamicHlsHelper
state.VideoStream.Level = originalLevel;
var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField);
builder.Append(newPlaylist);
-
- // Provide a workaround for the case issue between flac and fLaC.
- flacWaPlaylist = ApplyFlacCaseWorkaround(state, newPlaylist);
- if (!string.IsNullOrEmpty(flacWaPlaylist))
- {
- builder.Append(flacWaPlaylist);
- }
}
}
@@ -767,16 +746,4 @@ public class DynamicHlsHelper
newValue.ToString(),
StringComparison.Ordinal);
}
-
- private string ApplyFlacCaseWorkaround(StreamState state, string srcPlaylist)
- {
- if (!string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase))
- {
- return string.Empty;
- }
-
- var newPlaylist = srcPlaylist.Replace(",flac\"", ",fLaC\"", StringComparison.Ordinal);
-
- return newPlaylist.Contains(",fLaC\"", StringComparison.Ordinal) ? newPlaylist : string.Empty;
- }
}
diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
index 9a141a16d..5eec1b0ca 100644
--- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
+++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
@@ -5,7 +5,9 @@ using System.Text;
namespace Jellyfin.Api.Helpers;
/// <summary>
-/// Hls Codec string helpers.
+/// Helpers to generate HLS codec strings according to
+/// <a href="https://datatracker.ietf.org/doc/html/rfc6381#section-3.3">RFC 6381 section 3.3</a>
+/// and the <a href="https://mp4ra.org">MP4 Registration Authority</a>.
/// </summary>
public static class HlsCodecStringHelpers
{
@@ -27,7 +29,7 @@ public static class HlsCodecStringHelpers
/// <summary>
/// Codec name for FLAC.
/// </summary>
- public const string FLAC = "flac";
+ public const string FLAC = "fLaC";
/// <summary>
/// Codec name for ALAC.
@@ -37,7 +39,7 @@ public static class HlsCodecStringHelpers
/// <summary>
/// Codec name for OPUS.
/// </summary>
- public const string OPUS = "opus";
+ public const string OPUS = "Opus";
/// <summary>
/// Gets a MP3 codec string.
diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs
index 5985d3dd8..23795c6be 100644
--- a/MediaBrowser.Common/IApplicationHost.cs
+++ b/MediaBrowser.Common/IApplicationHost.cs
@@ -35,21 +35,15 @@ namespace MediaBrowser.Common
string SystemId { get; }
/// <summary>
- /// Gets a value indicating whether this instance has pending kernel reload.
+ /// Gets a value indicating whether this instance has pending changes requiring a restart.
/// </summary>
- /// <value><c>true</c> if this instance has pending kernel reload; otherwise, <c>false</c>.</value>
+ /// <value><c>true</c> if this instance has a pending restart; otherwise, <c>false</c>.</value>
bool HasPendingRestart { get; }
/// <summary>
- /// Gets a value indicating whether this instance is currently shutting down.
+ /// Gets or sets a value indicating whether the application should restart.
/// </summary>
- /// <value><c>true</c> if this instance is shutting down; otherwise, <c>false</c>.</value>
- bool IsShuttingDown { get; }
-
- /// <summary>
- /// Gets a value indicating whether the application should restart.
- /// </summary>
- bool ShouldRestart { get; }
+ bool ShouldRestart { get; set; }
/// <summary>
/// Gets the application version.
@@ -92,11 +86,6 @@ namespace MediaBrowser.Common
void NotifyPendingRestart();
/// <summary>
- /// Restarts this instance.
- /// </summary>
- void Restart();
-
- /// <summary>
/// Gets the exports.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
@@ -128,11 +117,6 @@ namespace MediaBrowser.Common
T Resolve<T>();
/// <summary>
- /// Shuts down.
- /// </summary>
- void Shutdown();
-
- /// <summary>
/// Initializes this instance.
/// </summary>
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 45ac5c3a8..e9c4d9e19 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -4,7 +4,6 @@
using System.Net;
using MediaBrowser.Common;
-using MediaBrowser.Model.System;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller
@@ -16,8 +15,6 @@ namespace MediaBrowser.Controller
{
bool CoreStartupHasCompleted { get; }
- bool CanLaunchWebBrowser { get; }
-
/// <summary>
/// Gets the HTTP server port.
/// </summary>
@@ -42,15 +39,6 @@ namespace MediaBrowser.Controller
string FriendlyName { get; }
/// <summary>
- /// Gets the system info.
- /// </summary>
- /// <param name="request">The HTTP request.</param>
- /// <returns>SystemInfo.</returns>
- SystemInfo GetSystemInfo(HttpRequest request);
-
- PublicSystemInfo GetPublicSystemInfo(HttpRequest request);
-
- /// <summary>
/// Gets a URL specific for the request.
/// </summary>
/// <param name="request">The <see cref="HttpRequest"/> instance.</param>
diff --git a/MediaBrowser.Controller/ISystemManager.cs b/MediaBrowser.Controller/ISystemManager.cs
new file mode 100644
index 000000000..ef3034d2f
--- /dev/null
+++ b/MediaBrowser.Controller/ISystemManager.cs
@@ -0,0 +1,34 @@
+using MediaBrowser.Model.System;
+using Microsoft.AspNetCore.Http;
+
+namespace MediaBrowser.Controller;
+
+/// <summary>
+/// A service for managing the application instance.
+/// </summary>
+public interface ISystemManager
+{
+ /// <summary>
+ /// Gets the system info.
+ /// </summary>
+ /// <param name="request">The HTTP request.</param>
+ /// <returns>The <see cref="SystemInfo"/>.</returns>
+ SystemInfo GetSystemInfo(HttpRequest request);
+
+ /// <summary>
+ /// Gets the public system info.
+ /// </summary>
+ /// <param name="request">The HTTP request.</param>
+ /// <returns>The <see cref="PublicSystemInfo"/>.</returns>
+ PublicSystemInfo GetPublicSystemInfo(HttpRequest request);
+
+ /// <summary>
+ /// Starts the application restart process.
+ /// </summary>
+ void Restart();
+
+ /// <summary>
+ /// Starts the application shutdown process.
+ /// </summary>
+ void Shutdown();
+}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index d2f349ad7..432b89c31 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.IO;
+using System.Text;
using System.Threading;
using System.Xml;
using MediaBrowser.Common.Configuration;
@@ -81,7 +82,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
// Extract the last episode number from nfo
+ // Retrieves all title and plot tags from the rest of the nfo and concatenates them with the first episode
// This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
+ var name = new StringBuilder(item.Item.Name);
+ var overview = new StringBuilder(item.Item.Overview);
while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
{
xml = xmlFile.Substring(0, index + srch.Length);
@@ -92,12 +96,44 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
reader.MoveToContent();
- if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num))
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
- item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "name":
+ case "title":
+ case "localtitle":
+ name.Append(" / ").Append(reader.ReadElementContentAsString());
+ break;
+ case "episode":
+ {
+ if (int.TryParse(reader.ReadElementContentAsString(), out var num))
+ {
+ item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
+ }
+
+ break;
+ }
+
+ case "biography":
+ case "plot":
+ case "review":
+ overview.Append(" / ").Append(reader.ReadElementContentAsString());
+ break;
+ }
+ }
+
+ reader.Read();
}
}
}
+
+ item.Item.Name = name.ToString();
+ item.Item.Overview = overview.ToString();
}
catch (XmlException)
{
@@ -172,6 +208,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
break;
}
+ case "displayafterseason":
case "airsafter_season":
{
var val = reader.ReadElementContentAsString();
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
index f63bc0e1b..c0d06116b 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using System.Threading;
using Jellyfin.Data.Enums;
@@ -114,11 +114,11 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
_parser.Fetch(result, "Test Data/Rising.nfo", CancellationToken.None);
var item = result.Item;
- Assert.Equal("Rising (1)", item.Name);
+ Assert.Equal("Rising (1) / Rising (2)", item.Name);
Assert.Equal(1, item.IndexNumber);
Assert.Equal(2, item.IndexNumberEnd);
Assert.Equal(1, item.ParentIndexNumber);
- Assert.Equal("A new Stargate team embarks on a dangerous mission to a distant galaxy, where they discover a mythical lost city -- and a deadly new enemy.", item.Overview);
+ Assert.Equal("A new Stargate team embarks on a dangerous mission to a distant galaxy, where they discover a mythical lost city -- and a deadly new enemy. / Sheppard tries to convince Weir to mount a rescue mission to free Colonel Sumner, Teyla, and the others captured by the Wraith.", item.Overview);
Assert.Equal(new DateTime(2004, 7, 16), item.PremiereDate);
Assert.Equal(2004, item.ProductionYear);
}