diff --git a/Emby.Dlna/ContentDirectory/StubType.cs b/Emby.Dlna/ContentDirectory/StubType.cs
index 982ae5d68e..187dc1d75a 100644
--- a/Emby.Dlna/ContentDirectory/StubType.cs
+++ b/Emby.Dlna/ContentDirectory/StubType.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1602
namespace Emby.Dlna.ContentDirectory
{
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index 21ba1c7552..9ab3240388 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -553,7 +553,7 @@ namespace Emby.Dlna
private void DumpProfiles()
{
- DeviceProfile[] list = new []
+ DeviceProfile[] list = new[]
{
new SamsungSmartTvProfile(),
new XboxOneProfile(),
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 82490ec310..ec87160293 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -228,7 +228,10 @@ namespace Emby.Dlna.Main
{
try
{
- ((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer);
+ if (communicationsServer != null)
+ {
+ ((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer);
+ }
}
catch (Exception ex)
{
@@ -313,9 +316,12 @@ namespace Emby.Dlna.Main
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
- // DLNA will only work over http, so we must reset to http:// : {port}
- uri.Scheme = "http://";
- uri.Port = _netConfig.HttpServerPortNumber;
+ if (_appHost.PublishedServerUrl == null)
+ {
+ // DLNA will only work over http, so we must reset to http:// : {port}.
+ uri.Scheme = "http";
+ uri.Port = _netConfig.HttpServerPortNumber;
+ }
var device = new SsdpRootDevice
{
diff --git a/Emby.Dlna/PlayTo/TransportState.cs b/Emby.Dlna/PlayTo/TransportState.cs
index 7068a5d24d..2058e9dc79 100644
--- a/Emby.Dlna/PlayTo/TransportState.cs
+++ b/Emby.Dlna/PlayTo/TransportState.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1602
namespace Emby.Dlna.PlayTo
{
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 7740b4fc5b..6880b18423 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -137,6 +137,9 @@ namespace Emby.Server.Implementations
public bool CoreStartupHasCompleted { get; private set; }
+ ///
+ public Uri PublishedServerUrl => _startupOptions.PublishedServerUrl;
+
public virtual bool CanLaunchWebBrowser
{
get
@@ -384,7 +387,7 @@ namespace Emby.Server.Implementations
///
/// Creates an instance of type and resolves all constructor dependencies.
///
- /// /// The type.
+ /// The type.
/// T.
public T CreateInstance()
=> ActivatorUtilities.CreateInstance(ServiceProvider);
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 3011a37e31..1ab2bdfbe8 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Collections
var name = _localizationManager.GetLocalizedString("Collections");
- await _libraryManager.AddVirtualFolder(name, CollectionType.BoxSets, libraryOptions, true).ConfigureAwait(false);
+ await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.BoxSets, libraryOptions, true).ConfigureAwait(false);
return FindFolders(path).First();
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index dad8bec7b0..d78b93bd78 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -6207,9 +6207,9 @@ AND Type = @InternalPersonType)");
if (item.Type == MediaStreamType.Subtitle)
{
- item.localizedUndefined = _localization.GetLocalizedString("Undefined");
- item.localizedDefault = _localization.GetLocalizedString("Default");
- item.localizedForced = _localization.GetLocalizedString("Forced");
+ item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
+ item.LocalizedDefault = _localization.GetLocalizedString("Default");
+ item.LocalizedForced = _localization.GetLocalizedString("Forced");
}
return item;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 522667153b..f03f04e021 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -29,7 +29,7 @@
-
+
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index 9486874d58..a12a6b26c5 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.EntryPoints
///
/// The UDP server.
///
- private UdpServer _udpServer;
+ private UdpServer? _udpServer;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false;
@@ -71,9 +73,8 @@ namespace Emby.Server.Implementations.EntryPoints
}
_cancellationTokenSource.Cancel();
- _udpServer.Dispose();
_cancellationTokenSource.Dispose();
- _cancellationTokenSource = null;
+ _udpServer?.Dispose();
_udpServer = null;
_disposed = true;
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index db27862ce7..d9ffe64b3b 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1240,11 +1240,20 @@ namespace Emby.Server.Implementations.Library
return info;
}
- private string GetCollectionType(string path)
+ private CollectionTypeOptions? GetCollectionType(string path)
{
- return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false)
- .Select(Path.GetFileNameWithoutExtension)
- .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+ var files = _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false);
+ foreach (var file in files)
+ {
+ // TODO: @bond use a ReadOnlySpan here when Enum.TryParse supports it
+ // https://github.com/dotnet/runtime/issues/20008
+ if (Enum.TryParse(Path.GetExtension(file), true, out var res))
+ {
+ return res;
+ }
+ }
+
+ return null;
}
///
@@ -2956,7 +2965,7 @@ namespace Emby.Server.Implementations.Library
throw new InvalidOperationException();
}
- public async Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary)
+ public async Task AddVirtualFolder(string name, CollectionTypeOptions? collectionType, LibraryOptions options, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -2990,9 +2999,9 @@ namespace Emby.Server.Implementations.Library
{
Directory.CreateDirectory(virtualFolderPath);
- if (!string.IsNullOrEmpty(collectionType))
+ if (collectionType != null)
{
- var path = Path.Combine(virtualFolderPath, collectionType + ".collection");
+ var path = Path.Combine(virtualFolderPath, collectionType.ToString() + ".collection");
File.WriteAllBytes(path, Array.Empty());
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 2c0de661df..13b5a1c552 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -2604,7 +2604,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Locations = new string[] { customPath },
Name = "Recorded Movies",
- CollectionType = CollectionType.Movies
+ CollectionType = CollectionTypeOptions.Movies
};
}
@@ -2615,7 +2615,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Locations = new string[] { customPath },
Name = "Recorded Shows",
- CollectionType = CollectionType.TvShows
+ CollectionType = CollectionTypeOptions.TvShows
};
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index c641b760b6..6d7c5ac6ee 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -10,7 +10,6 @@ using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 5ef83f2746..0760e81274 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -335,11 +335,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new Uri(url).AbsoluteUri.TrimEnd('/');
}
- protected EncodingOptions GetEncodingOptions()
- {
- return Config.GetConfiguration("encoding");
- }
-
private static string GetHdHrIdFromChannelId(string channelId)
{
return channelId.Split('_')[1];
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 977a1c2d70..b029b7042b 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -112,5 +112,8 @@
"TaskRefreshLibraryDescription": "Skandeer u media versameling vir nuwe lêers en verfris metadata.",
"TaskRefreshLibrary": "Skandeer Media Versameling",
"TaskRefreshChapterImagesDescription": "Maak kleinkiekeis (fotos) vir films wat hoofstukke het.",
- "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde"
+ "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde",
+ "Undefined": "Ongedefineerd",
+ "Forced": "Geforseer",
+ "Default": "Oorspronklik"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 05181116da..5d7ed243f4 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -117,5 +117,6 @@
"TaskCleanActivityLogDescription": "Elimina entradas del registro de actividad que sean más antiguas al periodo establecido.",
"TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir",
- "Forced": "Forzado"
+ "Forced": "Forzado",
+ "Default": "Predeterminado"
}
diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json
index faee2519a1..12bcd793e3 100644
--- a/Emby.Server.Implementations/Localization/Core/gl.json
+++ b/Emby.Server.Implementations/Localization/Core/gl.json
@@ -7,5 +7,55 @@
"Books": "Libros",
"AuthenticationSucceededWithUserName": "{0} autenticouse correctamente",
"Artists": "Artistas",
- "Application": "Aplicativo"
+ "Application": "Aplicativo",
+ "NotificationOptionServerRestartRequired": "Necesario un reinicio do servidor",
+ "NotificationOptionPluginUpdateInstalled": "Actualización do Plugin instalada",
+ "NotificationOptionPluginUninstalled": "Plugin desinstalado",
+ "NotificationOptionPluginInstalled": "Plugin instalado",
+ "NotificationOptionPluginError": "Fallo do Plugin",
+ "NotificationOptionNewLibraryContent": "Novo contido engadido",
+ "NotificationOptionInstallationFailed": "Fallo na instalación",
+ "NotificationOptionCameraImageUploaded": "Imaxe da cámara subida",
+ "NotificationOptionAudioPlaybackStopped": "Reproducción de audio parada",
+ "NotificationOptionAudioPlayback": "Reproducción de audio comezada",
+ "NotificationOptionApplicationUpdateInstalled": "Actualización da aplicación instalada",
+ "NotificationOptionApplicationUpdateAvailable": "Actualización da aplicación dispoñible",
+ "NewVersionIsAvailable": "Unha nova versión do Servidor Jellyfin está dispoñible para descarga.",
+ "NameSeasonUnknown": "Tempada descoñecida",
+ "NameSeasonNumber": "Tempada {0}",
+ "NameInstallFailed": "{0} instalación fallida",
+ "MusicVideos": "Vídeos Musicais",
+ "Music": "Música",
+ "Movies": "Películas",
+ "MixedContent": "Contido Mixto",
+ "MessageServerConfigurationUpdated": "A configuración do servidor foi actualizada",
+ "MessageNamedServerConfigurationUpdatedWithValue": "A sección de configuración {0} do servidor foi actualizada",
+ "MessageApplicationUpdatedTo": "O servidor Jellyfin foi actualizado a {0}",
+ "MessageApplicationUpdated": "O servidor Jellyfin foi actualizado",
+ "Latest": "Último",
+ "LabelRunningTimeValue": "Tempo de execución: {0}",
+ "LabelIpAddressValue": "Enderezo IP: {0}",
+ "ItemRemovedWithName": "{0} foi eliminado da biblioteca",
+ "ItemAddedWithName": "{0} foi engadido a biblioteca",
+ "Inherit": "Herdar",
+ "HomeVideos": "Videos caseiros",
+ "HeaderRecordingGroups": "Grupos de Grabación",
+ "HeaderNextUp": "De seguido",
+ "HeaderLiveTV": "TV en directo",
+ "HeaderFavoriteSongs": "Cancións Favoritas",
+ "HeaderFavoriteShows": "Series de TV Favoritas",
+ "HeaderFavoriteEpisodes": "Episodios Favoritos",
+ "HeaderFavoriteArtists": "Artistas Favoritos",
+ "HeaderFavoriteAlbums": "Álbunes Favoritos",
+ "HeaderContinueWatching": "Seguir mirando",
+ "HeaderAlbumArtists": "Artistas de Album",
+ "Genres": "Xéneros",
+ "Forced": "Forzado",
+ "Folders": "Cartafoles",
+ "Favorites": "Favoritos",
+ "FailedLoginAttemptWithUserName": "Intento de incio de sesión fallido {0}",
+ "DeviceOnlineWithName": "{0} conectouse",
+ "DeviceOfflineWithName": "{0} desconectouse",
+ "Default": "Por defecto",
+ "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index 105ef7be99..ba35138700 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -1,7 +1,7 @@
{
"Albums": "Album",
"AuthenticationSucceededWithUserName": "{0} berhasil diautentikasi",
- "AppDeviceValues": "Aplikasi : {0}, Alat : {1}",
+ "AppDeviceValues": "Aplikasi : {0}, Perangkat : {1}",
"LabelRunningTimeValue": "Waktu berjalan: {0}",
"MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index c26ccfd886..7bc9f0a7e2 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -1,8 +1,10 @@
#nullable enable
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
@@ -11,9 +13,11 @@ using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Json.Converters;
+using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -33,6 +37,21 @@ namespace Emby.Server.Implementations.Plugins
private readonly IList _plugins;
private readonly Version _minimumVersion;
+ private IHttpClientFactory? _httpClientFactory;
+
+ private IHttpClientFactory HttpClientFactory
+ {
+ get
+ {
+ if (_httpClientFactory == null)
+ {
+ _httpClientFactory = _appHost.Resolve();
+ }
+
+ return _httpClientFactory;
+ }
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -332,32 +351,74 @@ namespace Emby.Server.Implementations.Plugins
ChangePluginState(plugin, PluginStatus.Malfunctioned);
}
- ///
- /// Saves the manifest back to disk.
- ///
- /// The to save.
- /// The path where to save the manifest.
- /// True if successful.
+ ///
public bool SaveManifest(PluginManifest manifest, string path)
{
- if (manifest == null)
- {
- return false;
- }
-
try
{
var data = JsonSerializer.Serialize(manifest, _jsonOptions);
File.WriteAllText(Path.Combine(path, "meta.json"), data);
return true;
}
-#pragma warning disable CA1031 // Do not catch general exception types
- catch (Exception e)
-#pragma warning restore CA1031 // Do not catch general exception types
+ catch (ArgumentException e)
+ {
+ _logger.LogWarning(e, "Unable to save plugin manifest due to invalid value. {Path}", path);
+ return false;
+ }
+ }
+
+ ///
+ public async Task GenerateManifest(PackageInfo packageInfo, Version version, string path)
+ {
+ if (packageInfo == null)
{
- _logger.LogWarning(e, "Unable to save plugin manifest. {Path}", path);
return false;
}
+
+ var versionInfo = packageInfo.Versions.First(v => v.Version == version.ToString());
+ var imagePath = string.Empty;
+
+ if (!string.IsNullOrEmpty(packageInfo.ImageUrl))
+ {
+ var url = new Uri(packageInfo.ImageUrl);
+ imagePath = Path.Join(path, url.Segments[^1]);
+
+ await using var fileStream = File.OpenWrite(imagePath);
+
+ try
+ {
+ await using var downloadStream = await HttpClientFactory
+ .CreateClient(NamedClient.Default)
+ .GetStreamAsync(url)
+ .ConfigureAwait(false);
+
+ await downloadStream.CopyToAsync(fileStream).ConfigureAwait(false);
+ }
+ catch (HttpRequestException ex)
+ {
+ _logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath);
+ imagePath = string.Empty;
+ }
+ }
+
+ var manifest = new PluginManifest
+ {
+ Category = packageInfo.Category,
+ Changelog = versionInfo.Changelog ?? string.Empty,
+ Description = packageInfo.Description,
+ Id = new Guid(packageInfo.Id),
+ Name = packageInfo.Name,
+ Overview = packageInfo.Overview,
+ Owner = packageInfo.Owner,
+ TargetAbi = versionInfo.TargetAbi ?? string.Empty,
+ Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp),
+ Version = versionInfo.Version,
+ Status = PluginStatus.Active,
+ AutoUpdate = true,
+ ImagePath = imagePath
+ };
+
+ return SaveManifest(manifest, path);
}
///
@@ -410,7 +471,7 @@ namespace Emby.Server.Implementations.Plugins
if (plugin == null)
{
// Create a dummy record for the providers.
- // TODO: remove this code, if all provided have been released as separate plugins.
+ // TODO: remove this code once all provided have been released as separate plugins.
plugin = new LocalPlugin(
instance.AssemblyFilePath,
true,
diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs
index f9c6a13c69..a653b58c2b 100644
--- a/Emby.Server.Implementations/Session/WebSocketController.cs
+++ b/Emby.Server.Implementations/Session/WebSocketController.cs
@@ -8,7 +8,6 @@ using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Net;
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index abcb4313f3..7af52ea652 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -192,17 +192,12 @@ namespace Emby.Server.Implementations.Updates
var version = package.Versions[i];
var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber);
- // Update the manifests, if anything changes.
if (plugin != null)
{
- if (!string.Equals(plugin.Manifest.TargetAbi, version.TargetAbi, StringComparison.Ordinal))
- {
- plugin.Manifest.TargetAbi = version.TargetAbi ?? string.Empty;
- _pluginManager.SaveManifest(plugin.Manifest, plugin.Path);
- }
+ await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path);
}
- // Remove versions with a target abi that is greater then the current application version.
+ // Remove versions with a target ABI greater then the current application version.
if (Version.TryParse(version.TargetAbi, out var targetAbi) && _applicationHost.ApplicationVersion < targetAbi)
{
package.Versions.RemoveAt(i);
@@ -294,7 +289,8 @@ namespace Emby.Server.Implementations.Updates
Name = package.Name,
Version = v.VersionNumber,
SourceUrl = v.SourceUrl,
- Checksum = v.Checksum
+ Checksum = v.Checksum,
+ PackageInfo = package
};
}
}
@@ -571,24 +567,16 @@ namespace Emby.Server.Implementations.Updates
stream.Position = 0;
_zipClient.ExtractAllFromZip(stream, targetDir, true);
+ await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir);
_pluginManager.ImportPluginFrom(targetDir);
}
private async Task InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken)
{
- // Set last update time if we were installed before
LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version))
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
- if (plugin != null)
- {
- plugin.Manifest.Timestamp = DateTime.UtcNow;
- _pluginManager.SaveManifest(plugin.Manifest, plugin.Path);
- }
- // Do the install
await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false);
-
- // Do plugin-specific processing
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
return plugin != null;
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index b2baa9cea0..a2c2ecd666 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -6,7 +6,6 @@ using System.Net.Mime;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Models;
using MediaBrowser.Common.Plugins;
-using MediaBrowser.Controller;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Http;
@@ -22,22 +21,18 @@ namespace Jellyfin.Api.Controllers
public class DashboardController : BaseJellyfinApiController
{
private readonly ILogger _logger;
- private readonly IServerApplicationHost _appHost;
private readonly IPluginManager _pluginManager;
///
/// Initializes a new instance of the class.
///
/// Instance of interface.
- /// Instance of interface.
/// Instance of interface.
public DashboardController(
ILogger logger,
- IServerApplicationHost appHost,
IPluginManager pluginManager)
{
_logger = logger;
- _appHost = appHost;
_pluginManager = pluginManager;
}
@@ -51,7 +46,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("web/ConfigurationPages")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult> GetConfigurationPages(
+ public ActionResult> GetConfigurationPages(
[FromQuery] bool? enableInMainMenu)
{
var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList();
@@ -77,38 +72,22 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Html, "application/x-javascript")]
public ActionResult GetDashboardConfigurationPage([FromQuery] string? name)
{
- IPlugin? plugin = null;
- Stream? stream = null;
-
- var isJs = false;
- var isTemplate = false;
-
var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase));
- if (altPage != null)
+ if (altPage == null)
{
- plugin = altPage.Item2;
- stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath);
-
- isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase);
- isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal);
+ return NotFound();
}
- if (plugin != null && stream != null)
+ IPlugin plugin = altPage.Item2;
+ string resourcePath = altPage.Item1.EmbeddedResourcePath;
+ Stream? stream = plugin.GetType().Assembly.GetManifestResourceStream(resourcePath);
+ if (stream == null)
{
- if (isJs)
- {
- return File(stream, MimeTypes.GetMimeType("page.js"));
- }
-
- if (isTemplate)
- {
- return File(stream, MimeTypes.GetMimeType("page.html"));
- }
-
- return File(stream, MimeTypes.GetMimeType("page.html"));
+ _logger.LogError("Failed to get resource {Resource} from plugin {Plugin}", resourcePath, plugin.Name);
+ return NotFound();
}
- return NotFound();
+ return File(stream, MimeTypes.GetMimeType(resourcePath));
}
private IEnumerable GetConfigPages(LocalPlugin plugin)
@@ -120,7 +99,7 @@ namespace Jellyfin.Api.Controllers
{
if (plugin?.Instance is not IHasWebPages hasWebPages)
{
- return new List>();
+ return Enumerable.Empty>();
}
return hasWebPages.GetPages().Select(i => new Tuple(i, plugin.Instance));
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index dc36349704..a50d6e46bf 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
}
- user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
+ user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType ?? string.Empty)));
await _providerManager
.SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
@@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
}
- user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
+ user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType ?? string.Empty)));
await _providerManager
.SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs
index 94995650cb..328efea26e 100644
--- a/Jellyfin.Api/Controllers/LibraryStructureController.cs
+++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs
@@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task AddVirtualFolder(
[FromQuery] string? name,
- [FromQuery] string? collectionType,
+ [FromQuery] CollectionTypeOptions? collectionType,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] paths,
[FromBody] AddVirtualFolderDto? libraryOptionsDto,
[FromQuery] bool refreshLibrary = false)
diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs
index f2abd515d3..e0c744325f 100644
--- a/Jellyfin.Api/Extensions/DtoExtensions.cs
+++ b/Jellyfin.Api/Extensions/DtoExtensions.cs
@@ -113,14 +113,5 @@ namespace Jellyfin.Api.Extensions
return dtoOptions;
}
-
- ///
- /// Check if DtoOptions contains field.
- ///
- /// DtoOptions object.
- /// Field to check.
- /// Field existence.
- internal static bool ContainsField(this DtoOptions dtoOptions, ItemFields field)
- => dtoOptions.Fields != null && dtoOptions.Fields.Contains(field);
}
}
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index 8a47f7461d..16380f0bba 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
+using System.Net.Mime;
using System.Security.Claims;
using System.Text;
using System.Threading;
@@ -171,13 +172,15 @@ namespace Jellyfin.Api.Helpers
var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString();
// from universal audio service
- if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer))
+ if (!string.IsNullOrWhiteSpace(state.Request.SegmentContainer)
+ && !queryString.Contains("SegmentContainer", StringComparison.OrdinalIgnoreCase))
{
queryString += "&SegmentContainer=" + state.Request.SegmentContainer;
}
// from universal audio service
- if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1)
+ if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons)
+ && !queryString.Contains("TranscodeReasons=", StringComparison.OrdinalIgnoreCase))
{
queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
}
@@ -560,13 +563,13 @@ namespace Jellyfin.Api.Helpers
profileString = state.GetRequestedProfiles(codec).FirstOrDefault() ?? string.Empty;
if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
- profileString = profileString ?? "high";
+ profileString ??= "high";
}
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
{
- profileString = profileString ?? "main";
+ profileString ??= "main";
}
}
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
index 0d8315dee1..ce6740fc99 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -523,7 +523,7 @@ namespace Jellyfin.Api.Helpers
/// Dlna profile type.
public void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type)
{
- mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type);
+ mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, profile, type);
}
private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index 4957ee8b8d..d20a02cf5d 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -183,7 +183,7 @@ namespace Jellyfin.Api.Helpers
if (string.IsNullOrEmpty(containerInternal))
{
containerInternal = streamingRequest.Static ?
- StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio)
+ StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio)
: GetOutputFileExtension(state);
}
@@ -245,7 +245,7 @@ namespace Jellyfin.Api.Helpers
var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
? GetOutputFileExtension(state)
- : ('.' + state.OutputContainer);
+ : ("." + state.OutputContainer);
state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 5f7c64a7ef..67d0a3b5ab 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -17,8 +17,8 @@
-
-
+
+
diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs
index a7bbe42fe5..ec4a0d1a1c 100644
--- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs
+++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs
@@ -1,6 +1,5 @@
using System;
using MediaBrowser.Common.Plugins;
-using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Plugins;
namespace Jellyfin.Api.Models
@@ -25,6 +24,14 @@ namespace Jellyfin.Api.Models
PluginId = plugin?.Id;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ConfigurationPageInfo()
+ {
+ Name = string.Empty;
+ }
+
///
/// Gets or sets the name.
///
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
index 872a468245..e33e552edb 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
@@ -98,7 +98,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
private EncodingOptions GetOptions()
{
- return _config.GetConfiguration("encoding");
+ return _config.GetEncodingOptions();
}
private async void TimerCallback(object? state)
diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs
index 8bb97937c3..51fcb6d9a0 100644
--- a/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Net;
@@ -691,11 +692,11 @@ namespace Jellyfin.Networking.Manager
/// Checks the string to see if it matches any interface names.
///
/// String to check.
- /// Interface index number.
+ /// Interface index numbers that match.
/// true if an interface name matches the token, False otherwise.
- private bool IsInterface(string token, out int index)
+ private bool TryGetInterfaces(string token, [NotNullWhen(true)] out List? index)
{
- index = -1;
+ index = null;
// Is it the name of an interface (windows) eg, Wireless LAN adapter Wireless Network Connection 1.
// Null check required here for automated testing.
@@ -712,13 +713,13 @@ namespace Jellyfin.Networking.Manager
if ((!partial && string.Equals(interfc, token, StringComparison.OrdinalIgnoreCase))
|| (partial && interfc.StartsWith(token, true, CultureInfo.InvariantCulture)))
{
- index = interfcIndex;
- return true;
+ index ??= new List();
+ index.Add(interfcIndex);
}
}
}
- return false;
+ return index != null;
}
///
@@ -730,14 +731,14 @@ namespace Jellyfin.Networking.Manager
{
// Is it the name of an interface (windows) eg, Wireless LAN adapter Wireless Network Connection 1.
// Null check required here for automated testing.
- if (IsInterface(token, out int index))
+ if (TryGetInterfaces(token, out var indices))
{
_logger.LogInformation("Interface {Token} used in settings. Using its interface addresses.", token);
- // Replace interface tags with the interface IP's.
+ // Replace all the interface tags with the interface IP's.
foreach (IPNetAddress iface in _interfaceAddresses)
{
- if (Math.Abs(iface.Tag) == index
+ if (indices.Contains(Math.Abs(iface.Tag))
&& ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|| (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6)))
{
@@ -916,11 +917,19 @@ namespace Jellyfin.Networking.Manager
// Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded.
if (config.IgnoreVirtualInterfaces)
{
- var virtualInterfaceNames = config.VirtualInterfaceNames.Split(',');
- var newList = new string[lanAddresses.Length + virtualInterfaceNames.Length];
- Array.Copy(lanAddresses, newList, lanAddresses.Length);
- Array.Copy(virtualInterfaceNames, 0, newList, lanAddresses.Length, virtualInterfaceNames.Length);
- lanAddresses = newList;
+ // each virtual interface name must be pre-pended with the exclusion symbol !
+ var virtualInterfaceNames = config.VirtualInterfaceNames.Split(',').Select(p => "!" + p).ToArray();
+ if (lanAddresses.Length > 0)
+ {
+ var newList = new string[lanAddresses.Length + virtualInterfaceNames.Length];
+ Array.Copy(lanAddresses, newList, lanAddresses.Length);
+ Array.Copy(virtualInterfaceNames, 0, newList, lanAddresses.Length, virtualInterfaceNames.Length);
+ lanAddresses = newList;
+ }
+ else
+ {
+ lanAddresses = virtualInterfaceNames;
+ }
}
// Read and parse bind addresses and exclusions, removing ones that don't exist.
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index 1daa32deea..94c3ca4a95 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -11,7 +11,6 @@ using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity;
using Jellyfin.Server.Implementations.Events;
using Jellyfin.Server.Implementations.Users;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.BaseItemManager;
using MediaBrowser.Controller.Drawing;
diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
index 0925a87b55..bf0225e988 100644
--- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
+++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
@@ -32,7 +32,7 @@ namespace Jellyfin.Server.Migrations.Routines
public void Perform()
{
// Set EnableThrottling to false since it wasn't used before and may introduce issues
- var encoding = _configManager.GetConfiguration("encoding");
+ var encoding = _configManager.GetEncodingOptions();
if (encoding.EnableThrottling)
{
_logger.LogInformation("Disabling transcoding throttling during migration");
diff --git a/MediaBrowser.Common/Extensions/StreamExtensions.cs b/MediaBrowser.Common/Extensions/StreamExtensions.cs
new file mode 100644
index 0000000000..cd77be7b2b
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/StreamExtensions.cs
@@ -0,0 +1,51 @@
+#nullable enable
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace MediaBrowser.Common.Extensions
+{
+ ///
+ /// Class BaseExtensions.
+ ///
+ public static class StreamExtensions
+ {
+ ///
+ /// Reads all lines in the .
+ ///
+ /// The to read from.
+ /// All lines in the stream.
+ public static string[] ReadAllLines(this Stream stream)
+ => ReadAllLines(stream, Encoding.UTF8);
+
+ ///
+ /// Reads all lines in the .
+ ///
+ /// The to read from.
+ /// The character encoding to use.
+ /// All lines in the stream.
+ public static string[] ReadAllLines(this Stream stream, Encoding encoding)
+ {
+ using (StreamReader reader = new StreamReader(stream, encoding))
+ {
+ return ReadAllLines(reader).ToArray();
+ }
+ }
+
+ ///
+ /// Reads all lines in the .
+ ///
+ /// The to read from.
+ /// All lines in the stream.
+ public static IEnumerable ReadAllLines(this StreamReader reader)
+ {
+ string? line;
+ while ((line = reader.ReadLine()) != null)
+ {
+ yield return line;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Extensions/StringExtensions.cs b/MediaBrowser.Common/Extensions/StringExtensions.cs
deleted file mode 100644
index 7643017412..0000000000
--- a/MediaBrowser.Common/Extensions/StringExtensions.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-#nullable enable
-
-using System;
-
-namespace MediaBrowser.Common.Extensions
-{
- ///
- /// Extensions methods to simplify string operations.
- ///
- public static class StringExtensions
- {
- ///
- /// Returns the part on the left of the needle.
- ///
- /// The string to seek.
- /// The needle to find.
- /// The part left of the .
- public static ReadOnlySpan LeftPart(this ReadOnlySpan haystack, char needle)
- {
- var pos = haystack.IndexOf(needle);
- return pos == -1 ? haystack : haystack[..pos];
- }
-
- ///
- /// Returns the part on the left of the needle.
- ///
- /// The string to seek.
- /// The needle to find.
- /// One of the enumeration values that specifies the rules for the search.
- /// The part left of the needle.
- public static ReadOnlySpan LeftPart(this ReadOnlySpan haystack, ReadOnlySpan needle, StringComparison stringComparison = default)
- {
- var pos = haystack.IndexOf(needle, stringComparison);
- return pos == -1 ? haystack : haystack[..pos];
- }
- }
-}
diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs
index 4cede9ab16..4a7c701907 100644
--- a/MediaBrowser.Common/Net/IPHost.cs
+++ b/MediaBrowser.Common/Net/IPHost.cs
@@ -128,62 +128,63 @@ namespace MediaBrowser.Common.Net
/// true if the parsing is successful, false if not.
public static bool TryParse(string host, out IPHost hostObj)
{
- if (!string.IsNullOrEmpty(host))
+ if (string.IsNullOrWhiteSpace(host))
{
- // See if it's an IPv6 with port address e.g. [::1]:120.
- int i = host.IndexOf("]:", StringComparison.OrdinalIgnoreCase);
- if (i != -1)
- {
- return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
- }
- else
- {
- // See if it's an IPv6 in [] with no port.
- i = host.IndexOf(']', StringComparison.OrdinalIgnoreCase);
- if (i != -1)
- {
- return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
- }
+ hostObj = IPHost.None;
+ return false;
+ }
- // Is it a host or IPv4 with port?
- string[] hosts = host.Split(':');
+ // See if it's an IPv6 with port address e.g. [::1] or [::1]:120.
+ int i = host.IndexOf("]", StringComparison.OrdinalIgnoreCase);
+ if (i != -1)
+ {
+ return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
+ }
- if (hosts.Length > 2)
- {
- hostObj = new IPHost(string.Empty, IPAddress.None);
- return false;
- }
+ if (IPNetAddress.TryParse(host, out var netAddress))
+ {
+ // Host name is an ip address, so fake resolve.
+ hostObj = new IPHost(host, netAddress.Address);
+ return true;
+ }
- // Remove port from IPv4 if it exists.
- host = hosts[0];
+ // Is it a host, IPv4/6 with/out port?
+ string[] hosts = host.Split(':');
- if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
- {
- hostObj = new IPHost(host, new IPAddress(Ipv4Loopback));
- return true;
- }
+ if (hosts.Length <= 2)
+ {
+ // This is either a hostname: port, or an IP4:port.
+ host = hosts[0];
- if (IPNetAddress.TryParse(host, out IPNetAddress netIP))
- {
- // Host name is an ip address, so fake resolve.
- hostObj = new IPHost(host, netIP.Address);
- return true;
- }
+ if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
+ {
+ hostObj = new IPHost(host);
+ return true;
}
- // Only thing left is to see if it's a host string.
- if (!string.IsNullOrEmpty(host))
+ if (IPAddress.TryParse(host, out var netIP))
{
- // Use regular expression as CheckHostName isn't RFC5892 compliant.
- // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
- Regex re = new Regex(@"^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)$", RegexOptions.IgnoreCase | RegexOptions.Multiline);
- if (re.Match(host).Success)
- {
- hostObj = new IPHost(host);
- return true;
- }
+ // Host name is an ip address, so fake resolve.
+ hostObj = new IPHost(host, netIP);
+ return true;
}
}
+ else
+ {
+ // Invalid host name, as it cannot contain :
+ hostObj = new IPHost(string.Empty, IPAddress.None);
+ return false;
+ }
+
+ // Use regular expression as CheckHostName isn't RFC5892 compliant.
+ // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
+ string pattern = @"(?im)^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)$";
+
+ if (Regex.IsMatch(host, pattern))
+ {
+ hostObj = new IPHost(host);
+ return true;
+ }
hostObj = IPHost.None;
return false;
@@ -344,10 +345,14 @@ namespace MediaBrowser.Common.Net
{
output += "Any Address,";
}
- else
+ else if (i.AddressFamily == AddressFamily.InterNetwork)
{
output += $"{i}/32,";
}
+ else
+ {
+ output += $"{i}/128,";
+ }
}
output = output[0..^1];
diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs
index d07bba249b..9c1a0cf495 100644
--- a/MediaBrowser.Common/Net/NetworkExtensions.cs
+++ b/MediaBrowser.Common/Net/NetworkExtensions.cs
@@ -1,11 +1,6 @@
-#pragma warning disable CA1062 // Validate arguments of public methods
using System;
-using System.Collections;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
-using System.Runtime.CompilerServices;
-using System.Text;
namespace MediaBrowser.Common.Net
{
diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs
index e228ae7ec5..7b162c0e1c 100644
--- a/MediaBrowser.Common/Plugins/BasePlugin.cs
+++ b/MediaBrowser.Common/Plugins/BasePlugin.cs
@@ -1,10 +1,7 @@
using System;
using System.IO;
using System.Reflection;
-using System.Runtime.InteropServices;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Plugins;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Common.Plugins
{
diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs
index 3da34d8bb3..fc2fcb5179 100644
--- a/MediaBrowser.Common/Plugins/IPluginManager.cs
+++ b/MediaBrowser.Common/Plugins/IPluginManager.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
+using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
namespace MediaBrowser.Common.Plugins
@@ -44,6 +45,15 @@ namespace MediaBrowser.Common.Plugins
/// True if successful.
bool SaveManifest(PluginManifest manifest, string path);
+ ///
+ /// Generates a manifest from repository data.
+ ///
+ /// The used to generate a manifest.
+ /// Version to be installed.
+ /// The path where to save the manifest.
+ /// True if successful.
+ Task GenerateManifest(PackageInfo packageInfo, Version version, string path);
+
///
/// Imports plugin details from a folder.
///
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 92b2d43ce2..6378625e87 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -52,6 +52,11 @@ namespace MediaBrowser.Controller
/// The name of the friendly.
string FriendlyName { get; }
+ ///
+ /// Gets the configured published server url.
+ ///
+ Uri PublishedServerUrl { get; }
+
///
/// Gets the system info.
///
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 6700761fcc..80fcf71f3e 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -542,7 +542,7 @@ namespace MediaBrowser.Controller.Library
Guid GetMusicGenreId(string name);
- Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary);
+ Task AddVirtualFolder(string name, CollectionTypeOptions? collectionType, LibraryOptions options, bool refreshLibrary);
Task RemoveVirtualFolder(string name, bool refreshLibrary);
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index c0b6cf28bb..a520193844 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -103,7 +103,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath()
{
// 1) Custom path stored in config/encoding xml file under tag takes precedence
- if (!ValidatePath(_configurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom))
+ if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom))
{
// 2) Check if the --ffmpeg CLI switch has been given
if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
@@ -118,7 +118,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
// Write the FFmpeg path to the config/encoding.xml file as so it appears in UI
- var config = _configurationManager.GetConfiguration("encoding");
+ var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
_configurationManager.SaveConfiguration("encoding", config);
@@ -177,7 +177,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Write the new ffmpeg path to the xml as
// This ensures its not lost on next startup
- var config = _configurationManager.GetConfiguration("encoding");
+ var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPath = newPath;
_configurationManager.SaveConfiguration("encoding", config);
@@ -209,6 +209,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_ffmpegPath = path;
EncoderLocation = location;
+ return true;
}
else
{
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index f8af499e4c..61daf50b32 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -24,6 +24,7 @@
+
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index b5291b5609..b9cb49cf2f 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -681,9 +681,9 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.Type = MediaStreamType.Subtitle;
stream.Codec = NormalizeSubtitleCodec(stream.Codec);
- stream.localizedUndefined = _localization.GetLocalizedString("Undefined");
- stream.localizedDefault = _localization.GetLocalizedString("Default");
- stream.localizedForced = _localization.GetLocalizedString("Forced");
+ stream.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
+ stream.LocalizedDefault = _localization.GetLocalizedString("Default");
+ stream.LocalizedForced = _localization.GetLocalizedString("Forced");
}
else if (string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
index bb48bed270..8219aa7b47 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
@@ -1,130 +1,21 @@
-#pragma warning disable CS1591
+#nullable enable
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Threading;
-using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Logging;
+using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles
{
- public class AssParser : ISubtitleParser
+ ///
+ /// Advanced SubStation Alpha subtitle parser.
+ ///
+ public class AssParser : SubtitleEditParser
{
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
- ///
- public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public AssParser(ILogger logger) : base(logger)
{
- var trackInfo = new SubtitleTrackInfo();
- var trackEvents = new List();
- var eventIndex = 1;
- using (var reader = new StreamReader(stream))
- {
- string line;
- while (!string.Equals(reader.ReadLine(), "[Events]", StringComparison.Ordinal))
- {
- }
-
- var headers = ParseFieldHeaders(reader.ReadLine());
-
- while ((line = reader.ReadLine()) != null)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (string.IsNullOrWhiteSpace(line))
- {
- continue;
- }
-
- if (line[0] == '[')
- {
- break;
- }
-
- var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) };
- eventIndex++;
- const string Dialogue = "Dialogue: ";
- var sections = line.Substring(Dialogue.Length).Split(',');
-
- subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
- subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
-
- subEvent.Text = string.Join(',', sections[headers["Text"]..]);
- RemoteNativeFormatting(subEvent);
-
- subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
-
- subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w0-9]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
-
- trackEvents.Add(subEvent);
- }
- }
-
- trackInfo.TrackEvents = trackEvents;
- return trackInfo;
- }
-
- private long GetTicks(ReadOnlySpan time)
- {
- return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out var span)
- ? span.Ticks : 0;
- }
-
- internal static Dictionary ParseFieldHeaders(string line)
- {
- const string Format = "Format: ";
- var fields = line.Substring(Format.Length).Split(',').Select(x => x.Trim()).ToList();
-
- return new Dictionary
- {
- { "Start", fields.IndexOf("Start") },
- { "End", fields.IndexOf("End") },
- { "Text", fields.IndexOf("Text") }
- };
- }
-
- private void RemoteNativeFormatting(SubtitleTrackEvent p)
- {
- int indexOfBegin = p.Text.IndexOf('{', StringComparison.Ordinal);
- string pre = string.Empty;
- while (indexOfBegin >= 0 && p.Text.IndexOf('}', StringComparison.Ordinal) > indexOfBegin)
- {
- string s = p.Text.Substring(indexOfBegin);
- if (s.StartsWith("{\\an1}", StringComparison.Ordinal) ||
- s.StartsWith("{\\an2}", StringComparison.Ordinal) ||
- s.StartsWith("{\\an3}", StringComparison.Ordinal) ||
- s.StartsWith("{\\an4}", StringComparison.Ordinal) ||
- s.StartsWith("{\\an5}", StringComparison.Ordinal) ||
- s.StartsWith("{\\an6}", StringComparison.Ordinal) ||
- s.StartsWith("{\\an7}", StringComparison.Ordinal) ||
- s.StartsWith("{\\an8}", StringComparison.Ordinal) ||
- s.StartsWith("{\\an9}", StringComparison.Ordinal))
- {
- pre = s.Substring(0, 6);
- }
- else if (s.StartsWith("{\\an1\\", StringComparison.Ordinal) ||
- s.StartsWith("{\\an2\\", StringComparison.Ordinal) ||
- s.StartsWith("{\\an3\\", StringComparison.Ordinal) ||
- s.StartsWith("{\\an4\\", StringComparison.Ordinal) ||
- s.StartsWith("{\\an5\\", StringComparison.Ordinal) ||
- s.StartsWith("{\\an6\\", StringComparison.Ordinal) ||
- s.StartsWith("{\\an7\\", StringComparison.Ordinal) ||
- s.StartsWith("{\\an8\\", StringComparison.Ordinal) ||
- s.StartsWith("{\\an9\\", StringComparison.Ordinal))
- {
- pre = s.Substring(0, 5) + "}";
- }
-
- int indexOfEnd = p.Text.IndexOf('}', StringComparison.Ordinal);
- p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1);
-
- indexOfBegin = p.Text.IndexOf('{', StringComparison.Ordinal);
- }
-
- p.Text = pre + p.Text;
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
index ccef7eeeae..19fb951dc5 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
@@ -1,102 +1,21 @@
-#pragma warning disable CS1591
+#nullable enable
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Text.RegularExpressions;
-using System.Threading;
-using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
+using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles
{
- public class SrtParser : ISubtitleParser
+ ///
+ /// SubRip subtitle parser.
+ ///
+ public class SrtParser : SubtitleEditParser
{
- private readonly ILogger _logger;
-
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
- public SrtParser(ILogger logger)
- {
- _logger = logger;
- }
-
- ///
- public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
- {
- var trackInfo = new SubtitleTrackInfo();
- var trackEvents = new List();
- using (var reader = new StreamReader(stream))
- {
- string line;
- while ((line = reader.ReadLine()) != null)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (string.IsNullOrWhiteSpace(line))
- {
- continue;
- }
-
- var subEvent = new SubtitleTrackEvent { Id = line };
- line = reader.ReadLine();
-
- if (string.IsNullOrWhiteSpace(line))
- {
- continue;
- }
-
- var time = Regex.Split(line, @"[\t ]*-->[\t ]*");
-
- if (time.Length < 2)
- {
- // This occurs when subtitle text has an empty line as part of the text.
- // Need to adjust the break statement below to resolve this.
- _logger.LogWarning("Unrecognized line in srt: {0}", line);
- continue;
- }
-
- subEvent.StartPositionTicks = GetTicks(time[0]);
- var endTime = time[1].AsSpan();
- var idx = endTime.IndexOf(' ');
- if (idx > 0)
- {
- endTime = endTime.Slice(0, idx);
- }
-
- subEvent.EndPositionTicks = GetTicks(endTime);
- var multiline = new List();
- while ((line = reader.ReadLine()) != null)
- {
- if (line.Length == 0)
- {
- break;
- }
-
- multiline.Add(line);
- }
-
- subEvent.Text = string.Join(ParserValues.NewLine, multiline);
- subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
- subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\[0-9]?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase);
- subEvent.Text = Regex.Replace(subEvent.Text, "<", "<", RegexOptions.IgnoreCase);
- subEvent.Text = Regex.Replace(subEvent.Text, ">", ">", RegexOptions.IgnoreCase);
- subEvent.Text = Regex.Replace(subEvent.Text, "<(\\/?(font|b|u|i|s))((\\s+(\\w|\\w[\\w\\-]*\\w)(\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)(\\/?)>", "<$1$3$7>", RegexOptions.IgnoreCase);
- trackEvents.Add(subEvent);
- }
- }
-
- trackInfo.TrackEvents = trackEvents;
- return trackInfo;
- }
-
- private long GetTicks(ReadOnlySpan time)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public SrtParser(ILogger logger) : base(logger)
{
- return TimeSpan.TryParseExact(time, @"hh\:mm\:ss\.fff", _usCulture, out var span)
- ? span.Ticks
- : (TimeSpan.TryParseExact(time, @"hh\:mm\:ss\,fff", _usCulture, out span)
- ? span.Ticks : 0);
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
index bc84c5074d..36dc2e01fc 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
@@ -1,477 +1,21 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Text;
-using System.Threading;
-using MediaBrowser.Model.MediaInfo;
+#nullable enable
+
+using Microsoft.Extensions.Logging;
+using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles
{
///
- /// Credit.
+ /// SubStation Alpha subtitle parser.
///
- public class SsaParser : ISubtitleParser
+ public class SsaParser : SubtitleEditParser
{
- ///
- public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public SsaParser(ILogger logger) : base(logger)
{
- var trackInfo = new SubtitleTrackInfo();
- var trackEvents = new List();
-
- using (var reader = new StreamReader(stream))
- {
- bool eventsStarted = false;
-
- string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(',');
- int indexLayer = 0;
- int indexStart = 1;
- int indexEnd = 2;
- int indexStyle = 3;
- int indexName = 4;
- int indexEffect = 8;
- int indexText = 9;
- int lineNumber = 0;
-
- var header = new StringBuilder();
-
- string line;
-
- while ((line = reader.ReadLine()) != null)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- lineNumber++;
- if (!eventsStarted)
- {
- header.AppendLine(line);
- }
-
- if (string.Equals(line.Trim(), "[events]", StringComparison.OrdinalIgnoreCase))
- {
- eventsStarted = true;
- }
- else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(';'))
- {
- // skip comment lines
- }
- else if (eventsStarted && line.Trim().Length > 0)
- {
- string s = line.Trim().ToLowerInvariant();
- if (s.StartsWith("format:", StringComparison.Ordinal))
- {
- if (line.Length > 10)
- {
- format = line.ToLowerInvariant().Substring(8).Split(',');
- for (int i = 0; i < format.Length; i++)
- {
- if (string.Equals(format[i].Trim(), "layer", StringComparison.OrdinalIgnoreCase))
- {
- indexLayer = i;
- }
- else if (string.Equals(format[i].Trim(), "start", StringComparison.OrdinalIgnoreCase))
- {
- indexStart = i;
- }
- else if (string.Equals(format[i].Trim(), "end", StringComparison.OrdinalIgnoreCase))
- {
- indexEnd = i;
- }
- else if (string.Equals(format[i].Trim(), "text", StringComparison.OrdinalIgnoreCase))
- {
- indexText = i;
- }
- else if (string.Equals(format[i].Trim(), "effect", StringComparison.OrdinalIgnoreCase))
- {
- indexEffect = i;
- }
- else if (string.Equals(format[i].Trim(), "style", StringComparison.OrdinalIgnoreCase))
- {
- indexStyle = i;
- }
- }
- }
- }
- else if (!string.IsNullOrEmpty(s))
- {
- string text = string.Empty;
- string start = string.Empty;
- string end = string.Empty;
- string style = string.Empty;
- string layer = string.Empty;
- string effect = string.Empty;
- string name = string.Empty;
-
- string[] splittedLine;
-
- if (s.StartsWith("dialogue:", StringComparison.Ordinal))
- {
- splittedLine = line.Substring(10).Split(',');
- }
- else
- {
- splittedLine = line.Split(',');
- }
-
- for (int i = 0; i < splittedLine.Length; i++)
- {
- if (i == indexStart)
- {
- start = splittedLine[i].Trim();
- }
- else if (i == indexEnd)
- {
- end = splittedLine[i].Trim();
- }
- else if (i == indexLayer)
- {
- layer = splittedLine[i];
- }
- else if (i == indexEffect)
- {
- effect = splittedLine[i];
- }
- else if (i == indexText)
- {
- text = splittedLine[i];
- }
- else if (i == indexStyle)
- {
- style = splittedLine[i];
- }
- else if (i == indexName)
- {
- name = splittedLine[i];
- }
- else if (i > indexText)
- {
- text += "," + splittedLine[i];
- }
- }
-
- try
- {
- trackEvents.Add(
- new SubtitleTrackEvent
- {
- StartPositionTicks = GetTimeCodeFromString(start),
- EndPositionTicks = GetTimeCodeFromString(end),
- Text = GetFormattedText(text)
- });
- }
- catch
- {
- }
- }
- }
- }
-
- // if (header.Length > 0)
- // subtitle.Header = header.ToString();
-
- // subtitle.Renumber(1);
- }
-
- trackInfo.TrackEvents = trackEvents.ToArray();
- return trackInfo;
- }
-
- private static long GetTimeCodeFromString(string time)
- {
- // h:mm:ss.cc
- string[] timeCode = time.Split(':', '.');
- return new TimeSpan(
- 0,
- int.Parse(timeCode[0], CultureInfo.InvariantCulture),
- int.Parse(timeCode[1], CultureInfo.InvariantCulture),
- int.Parse(timeCode[2], CultureInfo.InvariantCulture),
- int.Parse(timeCode[3], CultureInfo.InvariantCulture) * 10).Ticks;
- }
-
- private static string GetFormattedText(string text)
- {
- text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
-
- for (int i = 0; i < 10; i++) // just look ten times...
- {
- if (text.Contains(@"{\fn", StringComparison.Ordinal))
- {
- int start = text.IndexOf(@"{\fn", StringComparison.Ordinal);
- int end = text.IndexOf('}', start);
- if (end > 0 && !text.Substring(start).StartsWith("{\\fn}", StringComparison.Ordinal))
- {
- string fontName = text.Substring(start + 4, end - (start + 4));
- string extraTags = string.Empty;
- CheckAndAddSubTags(ref fontName, ref extraTags, out bool italic);
- text = text.Remove(start, end - start + 1);
- if (italic)
- {
- text = text.Insert(start, "");
- }
- else
- {
- text = text.Insert(start, "");
- }
-
- int indexOfEndTag = text.IndexOf("{\\fn}", start, StringComparison.Ordinal);
- if (indexOfEndTag > 0)
- {
- text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "");
- }
- else
- {
- text += "";
- }
- }
- }
-
- if (text.Contains(@"{\fs", StringComparison.Ordinal))
- {
- int start = text.IndexOf(@"{\fs", StringComparison.Ordinal);
- int end = text.IndexOf('}', start);
- if (end > 0 && !text.Substring(start).StartsWith("{\\fs}", StringComparison.Ordinal))
- {
- string fontSize = text.Substring(start + 4, end - (start + 4));
- string extraTags = string.Empty;
- CheckAndAddSubTags(ref fontSize, ref extraTags, out bool italic);
- if (IsInteger(fontSize))
- {
- text = text.Remove(start, end - start + 1);
- if (italic)
- {
- text = text.Insert(start, "");
- }
- else
- {
- text = text.Insert(start, "");
- }
-
- int indexOfEndTag = text.IndexOf("{\\fs}", start, StringComparison.Ordinal);
- if (indexOfEndTag > 0)
- {
- text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "");
- }
- else
- {
- text += "";
- }
- }
- }
- }
-
- if (text.Contains(@"{\c", StringComparison.Ordinal))
- {
- int start = text.IndexOf(@"{\c", StringComparison.Ordinal);
- int end = text.IndexOf('}', start);
- if (end > 0 && !text.Substring(start).StartsWith("{\\c}", StringComparison.Ordinal))
- {
- string color = text.Substring(start + 4, end - (start + 4));
- string extraTags = string.Empty;
- CheckAndAddSubTags(ref color, ref extraTags, out bool italic);
-
- color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
- color = color.PadLeft(6, '0');
-
- // switch to rrggbb from bbggrr
- color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
- color = color.ToLowerInvariant();
-
- text = text.Remove(start, end - start + 1);
- if (italic)
- {
- text = text.Insert(start, "");
- }
- else
- {
- text = text.Insert(start, "");
- }
-
- int indexOfEndTag = text.IndexOf("{\\c}", start, StringComparison.Ordinal);
- if (indexOfEndTag > 0)
- {
- text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "");
- }
- else
- {
- text += "";
- }
- }
- }
-
- if (text.Contains(@"{\1c", StringComparison.Ordinal)) // "1" specifices primary color
- {
- int start = text.IndexOf(@"{\1c", StringComparison.Ordinal);
- int end = text.IndexOf('}', start);
- if (end > 0 && !text.Substring(start).StartsWith("{\\1c}", StringComparison.Ordinal))
- {
- string color = text.Substring(start + 5, end - (start + 5));
- string extraTags = string.Empty;
- CheckAndAddSubTags(ref color, ref extraTags, out bool italic);
-
- color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
- color = color.PadLeft(6, '0');
-
- // switch to rrggbb from bbggrr
- color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
- color = color.ToLowerInvariant();
-
- text = text.Remove(start, end - start + 1);
- if (italic)
- {
- text = text.Insert(start, "");
- }
- else
- {
- text = text.Insert(start, "");
- }
-
- int indexOfEndTag = text.IndexOf("{\\1c}", start, StringComparison.Ordinal);
- if (indexOfEndTag > 0)
- {
- text = text.Remove(indexOfEndTag, "{\\1c}".Length).Insert(indexOfEndTag, "");
- }
- else
- {
- text += "";
- }
- }
- }
- }
-
- text = text.Replace(@"{\i1}", "", StringComparison.Ordinal);
- text = text.Replace(@"{\i0}", "", StringComparison.Ordinal);
- text = text.Replace(@"{\i}", "", StringComparison.Ordinal);
- if (CountTagInText(text, "") > CountTagInText(text, ""))
- {
- text += "";
- }
-
- text = text.Replace(@"{\u1}", "", StringComparison.Ordinal);
- text = text.Replace(@"{\u0}", "", StringComparison.Ordinal);
- text = text.Replace(@"{\u}", "", StringComparison.Ordinal);
- if (CountTagInText(text, "") > CountTagInText(text, ""))
- {
- text += "";
- }
-
- text = text.Replace(@"{\b1}", "", StringComparison.Ordinal);
- text = text.Replace(@"{\b0}", "", StringComparison.Ordinal);
- text = text.Replace(@"{\b}", "", StringComparison.Ordinal);
- if (CountTagInText(text, "") > CountTagInText(text, ""))
- {
- text += "";
- }
-
- return text;
- }
-
- private static bool IsInteger(string s)
- => int.TryParse(s, out _);
-
- private static int CountTagInText(string text, string tag)
- {
- int count = 0;
- int index = text.IndexOf(tag, StringComparison.Ordinal);
- while (index >= 0)
- {
- count++;
- if (index == text.Length)
- {
- return count;
- }
-
- index = text.IndexOf(tag, index + 1, StringComparison.Ordinal);
- }
-
- return count;
- }
-
- private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic)
- {
- italic = false;
- int indexOfSPlit = tagName.IndexOf('\\', StringComparison.Ordinal);
- if (indexOfSPlit > 0)
- {
- string rest = tagName.Substring(indexOfSPlit).TrimStart('\\');
- tagName = tagName.Remove(indexOfSPlit);
-
- for (int i = 0; i < 10; i++)
- {
- if (rest.StartsWith("fs", StringComparison.Ordinal) && rest.Length > 2)
- {
- indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
- string fontSize = rest;
- if (indexOfSPlit > 0)
- {
- fontSize = rest.Substring(0, indexOfSPlit);
- rest = rest.Substring(indexOfSPlit).TrimStart('\\');
- }
- else
- {
- rest = string.Empty;
- }
-
- extraTags += " size=\"" + fontSize.Substring(2) + "\"";
- }
- else if (rest.StartsWith("fn", StringComparison.Ordinal) && rest.Length > 2)
- {
- indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
- string fontName = rest;
- if (indexOfSPlit > 0)
- {
- fontName = rest.Substring(0, indexOfSPlit);
- rest = rest.Substring(indexOfSPlit).TrimStart('\\');
- }
- else
- {
- rest = string.Empty;
- }
-
- extraTags += " face=\"" + fontName.Substring(2) + "\"";
- }
- else if (rest.StartsWith("c", StringComparison.Ordinal) && rest.Length > 2)
- {
- indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
- string fontColor = rest;
- if (indexOfSPlit > 0)
- {
- fontColor = rest.Substring(0, indexOfSPlit);
- rest = rest.Substring(indexOfSPlit).TrimStart('\\');
- }
- else
- {
- rest = string.Empty;
- }
-
- string color = fontColor.Substring(2);
- color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
- color = color.PadLeft(6, '0');
- // switch to rrggbb from bbggrr
- color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
- color = color.ToLowerInvariant();
-
- extraTags += " color=\"" + color + "\"";
- }
- else if (rest.StartsWith("i1", StringComparison.Ordinal) && rest.Length > 1)
- {
- indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
- italic = true;
- if (indexOfSPlit > 0)
- {
- rest = rest.Substring(indexOfSPlit).TrimStart('\\');
- }
- else
- {
- rest = string.Empty;
- }
- }
- else if (rest.Length > 0 && rest.Contains('\\', StringComparison.Ordinal))
- {
- indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
- rest = rest.Substring(indexOfSPlit).TrimStart('\\');
- }
- }
- }
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
new file mode 100644
index 0000000000..82ec6ca217
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
@@ -0,0 +1,63 @@
+#nullable enable
+
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Logging;
+using Nikse.SubtitleEdit.Core;
+using ILogger = Microsoft.Extensions.Logging.ILogger;
+using SubtitleFormat = Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat;
+
+namespace MediaBrowser.MediaEncoding.Subtitles
+{
+ ///
+ /// SubStation Alpha subtitle parser.
+ ///
+ /// The .
+ public abstract class SubtitleEditParser : ISubtitleParser
+ where T : SubtitleFormat, new()
+ {
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ protected SubtitleEditParser(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
+ {
+ var subtitle = new Subtitle();
+ var subRip = new T();
+ var lines = stream.ReadAllLines().ToList();
+ subRip.LoadSubtitle(subtitle, lines, "untitled");
+ if (subRip.ErrorCount > 0)
+ {
+ _logger.LogError("{ErrorCount} errors encountered while parsing subtitle.");
+ }
+
+ var trackInfo = new SubtitleTrackInfo();
+ int len = subtitle.Paragraphs.Count;
+ var trackEvents = new SubtitleTrackEvent[len];
+ for (int i = 0; i < len; i++)
+ {
+ var p = subtitle.Paragraphs[i];
+ trackEvents[i] = new SubtitleTrackEvent(p.Number.ToString(CultureInfo.InvariantCulture), p.Text)
+ {
+ StartPositionTicks = p.StartTime.TimeSpan.Ticks,
+ EndPositionTicks = p.EndTime.TimeSpan.Ticks
+ };
+ }
+
+ trackInfo.TrackEvents = trackEvents;
+ return trackInfo;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index b92c4ee067..d195387301 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -27,7 +27,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
public class SubtitleEncoder : ISubtitleEncoder
{
- private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
@@ -42,7 +41,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
new ConcurrentDictionary();
public SubtitleEncoder(
- ILibraryManager libraryManager,
ILogger logger,
IApplicationPaths appPaths,
IFileSystem fileSystem,
@@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IHttpClientFactory httpClientFactory,
IMediaSourceManager mediaSourceManager)
{
- _libraryManager = libraryManager;
_logger = logger;
_appPaths = appPaths;
_fileSystem = fileSystem;
@@ -168,33 +165,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles
MediaStream subtitleStream,
CancellationToken cancellationToken)
{
- var inputFile = mediaSource.Path;
+ var fileInfo = await GetReadableFile(mediaSource, subtitleStream, cancellationToken).ConfigureAwait(false);
- var protocol = mediaSource.Protocol;
- if (subtitleStream.IsExternal)
- {
- protocol = _mediaSourceManager.GetPathProtocol(subtitleStream.Path);
- }
-
- var fileInfo = await GetReadableFile(mediaSource.Path, inputFile, mediaSource, subtitleStream, cancellationToken).ConfigureAwait(false);
-
- var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
+ var stream = await GetSubtitleStream(fileInfo, cancellationToken).ConfigureAwait(false);
return (stream, fileInfo.Format);
}
- private async Task GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
+ private async Task GetSubtitleStream(SubtitleInfo fileInfo, CancellationToken cancellationToken)
{
- if (requiresCharset)
+ if (fileInfo.IsExternal)
{
- using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
+ using (var stream = await GetStream(fileInfo.Path, fileInfo.Protocol, cancellationToken).ConfigureAwait(false))
{
var result = CharsetDetector.DetectFromStream(stream).Detected;
stream.Position = 0;
if (result != null)
{
- _logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, path);
+ _logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, fileInfo.Path);
using var reader = new StreamReader(stream, result.Encoding);
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
@@ -204,12 +193,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- return File.OpenRead(path);
+ return File.OpenRead(fileInfo.Path);
}
private async Task GetReadableFile(
- string mediaPath,
- string inputFile,
MediaSourceInfo mediaSource,
MediaStream subtitleStream,
CancellationToken cancellationToken)
@@ -241,9 +228,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
// Extract
- var outputPath = GetSubtitleCachePath(mediaPath, mediaSource, subtitleStream.Index, "." + outputFormat);
+ var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
- await ExtractTextSubtitle(inputFile, mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
+ await ExtractTextSubtitle(mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
@@ -255,13 +242,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (GetReader(currentFormat, false) == null)
{
// Convert
- var outputPath = GetSubtitleCachePath(mediaPath, mediaSource, subtitleStream.Index, ".srt");
+ var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt");
await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true);
}
+ if (subtitleStream.IsExternal)
+ {
+ return new SubtitleInfo(subtitleStream.Path, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), currentFormat, true);
+ }
+
return new SubtitleInfo(subtitleStream.Path, mediaSource.Protocol, currentFormat, true);
}
@@ -279,12 +271,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{
- return new SsaParser();
+ return new SsaParser(_logger);
}
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{
- return new AssParser();
+ return new AssParser(_logger);
}
if (throwIfMissing)
@@ -504,7 +496,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
///
/// Extracts the text subtitle.
///
- /// The input file.
/// The mediaSource.
/// Index of the subtitle stream.
/// The output codec.
@@ -513,7 +504,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Task.
/// Must use inputPath list overload.
private async Task ExtractTextSubtitle(
- string inputFile,
MediaSourceInfo mediaSource,
int subtitleStreamIndex,
string outputCodec,
@@ -529,7 +519,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (!File.Exists(outputPath))
{
await ExtractTextSubtitleInternal(
- _mediaEncoder.GetInputArgument(inputFile, mediaSource),
+ _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource),
subtitleStreamIndex,
outputCodec,
outputPath,
@@ -695,15 +685,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- private string GetSubtitleCachePath(string mediaPath, MediaSourceInfo mediaSource, int subtitleStreamIndex, string outputSubtitleExtension)
+ private string GetSubtitleCachePath(MediaSourceInfo mediaSource, int subtitleStreamIndex, string outputSubtitleExtension)
{
if (mediaSource.Protocol == MediaProtocol.File)
{
var ticksParam = string.Empty;
- var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
+ var date = _fileSystem.GetLastWriteTimeUtc(mediaSource.Path);
- ReadOnlySpan filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
+ ReadOnlySpan filename = (mediaSource.Path + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
var prefix = filename.Slice(0, 1);
@@ -711,7 +701,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
else
{
- ReadOnlySpan filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
+ ReadOnlySpan filename = (mediaSource.Path + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
var prefix = filename.Slice(0, 1);
diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs
index a55754eddc..d925b78b6d 100644
--- a/MediaBrowser.Model/Channels/ChannelFeatures.cs
+++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs
@@ -7,6 +7,13 @@ namespace MediaBrowser.Model.Channels
{
public class ChannelFeatures
{
+ public ChannelFeatures()
+ {
+ MediaTypes = Array.Empty();
+ ContentTypes = Array.Empty();
+ DefaultSortFields = Array.Empty();
+ }
+
///
/// Gets or sets the name.
///
@@ -38,7 +45,7 @@ namespace MediaBrowser.Model.Channels
public ChannelMediaContentType[] ContentTypes { get; set; }
///
- /// Represents the maximum number of records the channel allows retrieving at a time.
+ /// Gets or sets the maximum number of records the channel allows retrieving at a time.
///
public int? MaxPageSize { get; set; }
@@ -55,7 +62,7 @@ namespace MediaBrowser.Model.Channels
public ChannelItemSortField[] DefaultSortFields { get; set; }
///
- /// Indicates if a sort ascending/descending toggle is supported or not.
+ /// Gets or sets a value indicating whether a sort ascending/descending toggle is supported.
///
public bool SupportsSortOrderToggle { get; set; }
@@ -76,12 +83,5 @@ namespace MediaBrowser.Model.Channels
///
/// true if [supports content downloading]; otherwise, false.
public bool SupportsContentDownloading { get; set; }
-
- public ChannelFeatures()
- {
- MediaTypes = Array.Empty();
- ContentTypes = Array.Empty();
- DefaultSortFields = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs
index fd90e7f062..59966127fb 100644
--- a/MediaBrowser.Model/Channels/ChannelQuery.cs
+++ b/MediaBrowser.Model/Channels/ChannelQuery.cs
@@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Channels
public class ChannelQuery
{
///
- /// Fields to return within the items, in addition to basic information.
+ /// Gets or sets the fields to return within the items, in addition to basic information.
///
/// The fields.
public ItemFields[] Fields { get; set; }
@@ -28,13 +28,13 @@ namespace MediaBrowser.Model.Channels
public Guid UserId { get; set; }
///
- /// Skips over a given number of items within the results. Use for paging.
+ /// Gets or sets the start index. Use for paging.
///
/// The start index.
public int? StartIndex { get; set; }
///
- /// The maximum number of items to return.
+ /// Gets or sets the maximum number of items to return.
///
/// The limit.
public int? Limit { get; set; }
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index da467e133f..a9b2803017 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -5,6 +5,41 @@ namespace MediaBrowser.Model.Configuration
{
public class EncodingOptions
{
+ public EncodingOptions()
+ {
+ EnableFallbackFont = false;
+ DownMixAudioBoost = 2;
+ MaxMuxingQueueSize = 2048;
+ EnableThrottling = false;
+ ThrottleDelaySeconds = 180;
+ EncodingThreadCount = -1;
+ // This is a DRM device that is almost guaranteed to be there on every intel platform,
+ // plus it's the default one in ffmpeg if you don't specify anything
+ VaapiDevice = "/dev/dri/renderD128";
+ // This is the OpenCL device that is used for tonemapping.
+ // The left side of the dot is the platform number, and the right side is the device number on the platform.
+ OpenclDevice = "0.0";
+ EnableTonemapping = false;
+ EnableVppTonemapping = false;
+ TonemappingAlgorithm = "hable";
+ TonemappingRange = "auto";
+ TonemappingDesat = 0;
+ TonemappingThreshold = 0.8;
+ TonemappingPeak = 100;
+ TonemappingParam = 0;
+ H264Crf = 23;
+ H265Crf = 28;
+ DeinterlaceDoubleRate = false;
+ DeinterlaceMethod = "yadif";
+ EnableDecodingColorDepth10Hevc = true;
+ EnableDecodingColorDepth10Vp9 = true;
+ EnableEnhancedNvdecDecoder = true;
+ EnableHardwareEncoding = true;
+ AllowHevcEncoding = true;
+ EnableSubtitleExtraction = true;
+ HardwareDecodingCodecs = new string[] { "h264", "vc1" };
+ }
+
public int EncodingThreadCount { get; set; }
public string TranscodingTempPath { get; set; }
@@ -24,12 +59,12 @@ namespace MediaBrowser.Model.Configuration
public string HardwareAccelerationType { get; set; }
///
- /// FFmpeg path as set by the user via the UI.
+ /// Gets or sets the FFmpeg path as set by the user via the UI.
///
public string EncoderAppPath { get; set; }
///
- /// The current FFmpeg path being used by the system and displayed on the transcode page.
+ /// Gets or sets the current FFmpeg path being used by the system and displayed on the transcode page.
///
public string EncoderAppPathDisplay { get; set; }
@@ -76,40 +111,5 @@ namespace MediaBrowser.Model.Configuration
public bool EnableSubtitleExtraction { get; set; }
public string[] HardwareDecodingCodecs { get; set; }
-
- public EncodingOptions()
- {
- EnableFallbackFont = false;
- DownMixAudioBoost = 2;
- MaxMuxingQueueSize = 2048;
- EnableThrottling = false;
- ThrottleDelaySeconds = 180;
- EncodingThreadCount = -1;
- // This is a DRM device that is almost guaranteed to be there on every intel platform,
- // plus it's the default one in ffmpeg if you don't specify anything
- VaapiDevice = "/dev/dri/renderD128";
- // This is the OpenCL device that is used for tonemapping.
- // The left side of the dot is the platform number, and the right side is the device number on the platform.
- OpenclDevice = "0.0";
- EnableTonemapping = false;
- EnableVppTonemapping = false;
- TonemappingAlgorithm = "hable";
- TonemappingRange = "auto";
- TonemappingDesat = 0;
- TonemappingThreshold = 0.8;
- TonemappingPeak = 100;
- TonemappingParam = 0;
- H264Crf = 23;
- H265Crf = 28;
- DeinterlaceDoubleRate = false;
- DeinterlaceMethod = "yadif";
- EnableDecodingColorDepth10Hevc = true;
- EnableDecodingColorDepth10Vp9 = true;
- EnableEnhancedNvdecDecoder = true;
- EnableHardwareEncoding = true;
- AllowHevcEncoding = true;
- EnableSubtitleExtraction = true;
- HardwareDecodingCodecs = new string[] { "h264", "vc1" };
- }
}
}
diff --git a/MediaBrowser.Model/Configuration/ImageOption.cs b/MediaBrowser.Model/Configuration/ImageOption.cs
index 2b1268c743..0af7b7e146 100644
--- a/MediaBrowser.Model/Configuration/ImageOption.cs
+++ b/MediaBrowser.Model/Configuration/ImageOption.cs
@@ -6,6 +6,11 @@ namespace MediaBrowser.Model.Configuration
{
public class ImageOption
{
+ public ImageOption()
+ {
+ Limit = 1;
+ }
+
///
/// Gets or sets the type.
///
@@ -23,10 +28,5 @@ namespace MediaBrowser.Model.Configuration
///
/// The minimum width.
public int MinWidth { get; set; }
-
- public ImageOption()
- {
- Limit = 1;
- }
}
}
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index 77ac11d69f..24698360ec 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -2,13 +2,30 @@
#pragma warning disable CS1591
using System;
-using System.Collections.Generic;
-using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Configuration
{
public class LibraryOptions
{
+ public LibraryOptions()
+ {
+ TypeOptions = Array.Empty();
+ DisabledSubtitleFetchers = Array.Empty();
+ SubtitleFetcherOrder = Array.Empty();
+ DisabledLocalMetadataReaders = Array.Empty();
+
+ SkipSubtitlesIfAudioTrackMatches = true;
+ RequirePerfectSubtitleMatch = true;
+
+ EnablePhotos = true;
+ SaveSubtitlesWithMedia = true;
+ EnableRealtimeMonitor = true;
+ PathInfos = Array.Empty();
+ EnableInternetProviders = true;
+ EnableAutomaticSeriesGrouping = true;
+ SeasonZeroDisplayName = "Specials";
+ }
+
public bool EnablePhotos { get; set; }
public bool EnableRealtimeMonitor { get; set; }
@@ -79,387 +96,5 @@ namespace MediaBrowser.Model.Configuration
return null;
}
-
- public LibraryOptions()
- {
- TypeOptions = Array.Empty();
- DisabledSubtitleFetchers = Array.Empty();
- SubtitleFetcherOrder = Array.Empty();
- DisabledLocalMetadataReaders = Array.Empty();
-
- SkipSubtitlesIfAudioTrackMatches = true;
- RequirePerfectSubtitleMatch = true;
-
- EnablePhotos = true;
- SaveSubtitlesWithMedia = true;
- EnableRealtimeMonitor = true;
- PathInfos = Array.Empty();
- EnableInternetProviders = true;
- EnableAutomaticSeriesGrouping = true;
- SeasonZeroDisplayName = "Specials";
- }
- }
-
- public class MediaPathInfo
- {
- public string Path { get; set; }
-
- public string NetworkPath { get; set; }
- }
-
- public class TypeOptions
- {
- public string Type { get; set; }
-
- public string[] MetadataFetchers { get; set; }
-
- public string[] MetadataFetcherOrder { get; set; }
-
- public string[] ImageFetchers { get; set; }
-
- public string[] ImageFetcherOrder { get; set; }
-
- public ImageOption[] ImageOptions { get; set; }
-
- public ImageOption GetImageOptions(ImageType type)
- {
- foreach (var i in ImageOptions)
- {
- if (i.Type == type)
- {
- return i;
- }
- }
-
- if (DefaultImageOptions.TryGetValue(Type, out ImageOption[] options))
- {
- foreach (var i in options)
- {
- if (i.Type == type)
- {
- return i;
- }
- }
- }
-
- return DefaultInstance;
- }
-
- public int GetLimit(ImageType type)
- {
- return GetImageOptions(type).Limit;
- }
-
- public int GetMinWidth(ImageType type)
- {
- return GetImageOptions(type).MinWidth;
- }
-
- public bool IsEnabled(ImageType type)
- {
- return GetLimit(type) > 0;
- }
-
- public TypeOptions()
- {
- MetadataFetchers = Array.Empty();
- MetadataFetcherOrder = Array.Empty();
- ImageFetchers = Array.Empty();
- ImageFetcherOrder = Array.Empty();
- ImageOptions = Array.Empty();
- }
-
- public static Dictionary DefaultImageOptions = new Dictionary
- {
- {
- "Movie", new []
- {
- new ImageOption
- {
- Limit = 1,
- MinWidth = 1280,
- Type = ImageType.Backdrop
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Art
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Disc
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Primary
- },
-
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Banner
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Thumb
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Logo
- }
- }
- },
- {
- "MusicVideo", new []
- {
- new ImageOption
- {
- Limit = 1,
- MinWidth = 1280,
- Type = ImageType.Backdrop
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Art
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Disc
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Primary
- },
-
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Banner
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Thumb
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Logo
- }
- }
- },
- {
- "Series", new []
- {
- new ImageOption
- {
- Limit = 1,
- MinWidth = 1280,
- Type = ImageType.Backdrop
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Art
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Primary
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Banner
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Thumb
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Logo
- }
- }
- },
- {
- "MusicAlbum", new []
- {
- new ImageOption
- {
- Limit = 0,
- MinWidth = 1280,
- Type = ImageType.Backdrop
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Disc
- }
- }
- },
- {
- "MusicArtist", new []
- {
- new ImageOption
- {
- Limit = 1,
- MinWidth = 1280,
- Type = ImageType.Backdrop
- },
-
- // Don't download this by default
- // They do look great, but most artists won't have them, which means a banner view isn't really possible
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Banner
- },
-
- // Don't download this by default
- // Generally not used
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Art
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Logo
- }
- }
- },
- {
- "BoxSet", new []
- {
- new ImageOption
- {
- Limit = 1,
- MinWidth = 1280,
- Type = ImageType.Backdrop
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Primary
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Thumb
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Logo
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Art
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Disc
- },
-
- // Don't download this by default as it's rarely used.
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Banner
- }
- }
- },
- {
- "Season", new []
- {
- new ImageOption
- {
- Limit = 0,
- MinWidth = 1280,
- Type = ImageType.Backdrop
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Primary
- },
-
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Banner
- },
-
- new ImageOption
- {
- Limit = 0,
- Type = ImageType.Thumb
- }
- }
- },
- {
- "Episode", new []
- {
- new ImageOption
- {
- Limit = 0,
- MinWidth = 1280,
- Type = ImageType.Backdrop
- },
-
- new ImageOption
- {
- Limit = 1,
- Type = ImageType.Primary
- }
- }
- }
- };
-
- public static ImageOption DefaultInstance = new ImageOption();
}
}
diff --git a/MediaBrowser.Model/Configuration/MediaPathInfo.cs b/MediaBrowser.Model/Configuration/MediaPathInfo.cs
new file mode 100644
index 0000000000..4f311c58f0
--- /dev/null
+++ b/MediaBrowser.Model/Configuration/MediaPathInfo.cs
@@ -0,0 +1,12 @@
+#nullable disable
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Model.Configuration
+{
+ public class MediaPathInfo
+ {
+ public string Path { get; set; }
+
+ public string NetworkPath { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Configuration/MetadataConfiguration.cs b/MediaBrowser.Model/Configuration/MetadataConfiguration.cs
index 706831bddc..be044243dd 100644
--- a/MediaBrowser.Model/Configuration/MetadataConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/MetadataConfiguration.cs
@@ -4,11 +4,11 @@ namespace MediaBrowser.Model.Configuration
{
public class MetadataConfiguration
{
- public bool UseFileCreationTimeForDateAdded { get; set; }
-
public MetadataConfiguration()
{
UseFileCreationTimeForDateAdded = true;
}
+
+ public bool UseFileCreationTimeForDateAdded { get; set; }
}
}
diff --git a/MediaBrowser.Model/Configuration/MetadataOptions.cs b/MediaBrowser.Model/Configuration/MetadataOptions.cs
index e7dc3da3cb..76b72bd08e 100644
--- a/MediaBrowser.Model/Configuration/MetadataOptions.cs
+++ b/MediaBrowser.Model/Configuration/MetadataOptions.cs
@@ -10,6 +10,16 @@ namespace MediaBrowser.Model.Configuration
///
public class MetadataOptions
{
+ public MetadataOptions()
+ {
+ DisabledMetadataSavers = Array.Empty();
+ LocalMetadataReaderOrder = Array.Empty();
+ DisabledMetadataFetchers = Array.Empty();
+ MetadataFetcherOrder = Array.Empty();
+ DisabledImageFetchers = Array.Empty();
+ ImageFetcherOrder = Array.Empty();
+ }
+
public string ItemType { get; set; }
public string[] DisabledMetadataSavers { get; set; }
@@ -23,15 +33,5 @@ namespace MediaBrowser.Model.Configuration
public string[] DisabledImageFetchers { get; set; }
public string[] ImageFetcherOrder { get; set; }
-
- public MetadataOptions()
- {
- DisabledMetadataSavers = Array.Empty();
- LocalMetadataReaderOrder = Array.Empty();
- DisabledMetadataFetchers = Array.Empty();
- MetadataFetcherOrder = Array.Empty();
- DisabledImageFetchers = Array.Empty();
- ImageFetcherOrder = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs b/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs
index 0c197ee021..aa07d66237 100644
--- a/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs
+++ b/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs
@@ -8,6 +8,12 @@ namespace MediaBrowser.Model.Configuration
{
public class MetadataPluginSummary
{
+ public MetadataPluginSummary()
+ {
+ SupportedImageTypes = Array.Empty();
+ Plugins = Array.Empty();
+ }
+
///
/// Gets or sets the type of the item.
///
@@ -25,11 +31,5 @@ namespace MediaBrowser.Model.Configuration
///
/// The supported image types.
public ImageType[] SupportedImageTypes { get; set; }
-
- public MetadataPluginSummary()
- {
- SupportedImageTypes = Array.Empty();
- Plugins = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Configuration/TypeOptions.cs b/MediaBrowser.Model/Configuration/TypeOptions.cs
new file mode 100644
index 0000000000..d0179e5aab
--- /dev/null
+++ b/MediaBrowser.Model/Configuration/TypeOptions.cs
@@ -0,0 +1,365 @@
+#nullable disable
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.Configuration
+{
+ public class TypeOptions
+ {
+ public static readonly ImageOption DefaultInstance = new ImageOption();
+
+ public static readonly Dictionary DefaultImageOptions = new Dictionary
+ {
+ {
+ "Movie", new[]
+ {
+ new ImageOption
+ {
+ Limit = 1,
+ MinWidth = 1280,
+ Type = ImageType.Backdrop
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Art
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Disc
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Primary
+ },
+
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Banner
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Thumb
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Logo
+ }
+ }
+ },
+ {
+ "MusicVideo", new[]
+ {
+ new ImageOption
+ {
+ Limit = 1,
+ MinWidth = 1280,
+ Type = ImageType.Backdrop
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Art
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Disc
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Primary
+ },
+
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Banner
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Thumb
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Logo
+ }
+ }
+ },
+ {
+ "Series", new[]
+ {
+ new ImageOption
+ {
+ Limit = 1,
+ MinWidth = 1280,
+ Type = ImageType.Backdrop
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Art
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Primary
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Banner
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Thumb
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Logo
+ }
+ }
+ },
+ {
+ "MusicAlbum", new[]
+ {
+ new ImageOption
+ {
+ Limit = 0,
+ MinWidth = 1280,
+ Type = ImageType.Backdrop
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Disc
+ }
+ }
+ },
+ {
+ "MusicArtist", new[]
+ {
+ new ImageOption
+ {
+ Limit = 1,
+ MinWidth = 1280,
+ Type = ImageType.Backdrop
+ },
+
+ // Don't download this by default
+ // They do look great, but most artists won't have them, which means a banner view isn't really possible
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Banner
+ },
+
+ // Don't download this by default
+ // Generally not used
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Art
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Logo
+ }
+ }
+ },
+ {
+ "BoxSet", new[]
+ {
+ new ImageOption
+ {
+ Limit = 1,
+ MinWidth = 1280,
+ Type = ImageType.Backdrop
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Primary
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Thumb
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Logo
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Art
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Disc
+ },
+
+ // Don't download this by default as it's rarely used.
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Banner
+ }
+ }
+ },
+ {
+ "Season", new[]
+ {
+ new ImageOption
+ {
+ Limit = 0,
+ MinWidth = 1280,
+ Type = ImageType.Backdrop
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Primary
+ },
+
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Banner
+ },
+
+ new ImageOption
+ {
+ Limit = 0,
+ Type = ImageType.Thumb
+ }
+ }
+ },
+ {
+ "Episode", new[]
+ {
+ new ImageOption
+ {
+ Limit = 0,
+ MinWidth = 1280,
+ Type = ImageType.Backdrop
+ },
+
+ new ImageOption
+ {
+ Limit = 1,
+ Type = ImageType.Primary
+ }
+ }
+ }
+ };
+
+ public TypeOptions()
+ {
+ MetadataFetchers = Array.Empty();
+ MetadataFetcherOrder = Array.Empty();
+ ImageFetchers = Array.Empty();
+ ImageFetcherOrder = Array.Empty();
+ ImageOptions = Array.Empty();
+ }
+
+ public string Type { get; set; }
+
+ public string[] MetadataFetchers { get; set; }
+
+ public string[] MetadataFetcherOrder { get; set; }
+
+ public string[] ImageFetchers { get; set; }
+
+ public string[] ImageFetcherOrder { get; set; }
+
+ public ImageOption[] ImageOptions { get; set; }
+
+ public ImageOption GetImageOptions(ImageType type)
+ {
+ foreach (var i in ImageOptions)
+ {
+ if (i.Type == type)
+ {
+ return i;
+ }
+ }
+
+ if (DefaultImageOptions.TryGetValue(Type, out ImageOption[] options))
+ {
+ foreach (var i in options)
+ {
+ if (i.Type == type)
+ {
+ return i;
+ }
+ }
+ }
+
+ return DefaultInstance;
+ }
+
+ public int GetLimit(ImageType type)
+ {
+ return GetImageOptions(type).Limit;
+ }
+
+ public int GetMinWidth(ImageType type)
+ {
+ return GetImageOptions(type).MinWidth;
+ }
+
+ public bool IsEnabled(ImageType type)
+ {
+ return GetLimit(type) > 0;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index cc0e0c4681..935e6cbe10 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -11,6 +11,24 @@ namespace MediaBrowser.Model.Configuration
///
public class UserConfiguration
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UserConfiguration()
+ {
+ EnableNextEpisodeAutoPlay = true;
+ RememberAudioSelections = true;
+ RememberSubtitleSelections = true;
+
+ HidePlayedInLatest = true;
+ PlayDefaultAudioTrack = true;
+
+ LatestItemsExcludes = Array.Empty();
+ OrderedViews = Array.Empty();
+ MyMediaExcludes = Array.Empty();
+ GroupedFolders = Array.Empty();
+ }
+
///
/// Gets or sets the audio language preference.
///
@@ -52,23 +70,5 @@ namespace MediaBrowser.Model.Configuration
public bool RememberSubtitleSelections { get; set; }
public bool EnableNextEpisodeAutoPlay { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public UserConfiguration()
- {
- EnableNextEpisodeAutoPlay = true;
- RememberAudioSelections = true;
- RememberSubtitleSelections = true;
-
- HidePlayedInLatest = true;
- PlayDefaultAudioTrack = true;
-
- LatestItemsExcludes = Array.Empty();
- OrderedViews = Array.Empty();
- MyMediaExcludes = Array.Empty();
- GroupedFolders = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
index 4d5f996f84..8ad070dcbf 100644
--- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
+++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
@@ -5,6 +5,14 @@ namespace MediaBrowser.Model.Configuration
{
public class XbmcMetadataOptions
{
+ public XbmcMetadataOptions()
+ {
+ ReleaseDateFormat = "yyyy-MM-dd";
+
+ SaveImagePathsInNfo = true;
+ EnablePathSubstitution = true;
+ }
+
public string UserId { get; set; }
public string ReleaseDateFormat { get; set; }
@@ -14,13 +22,5 @@ namespace MediaBrowser.Model.Configuration
public bool EnablePathSubstitution { get; set; }
public bool EnableExtraThumbsDuplication { get; set; }
-
- public XbmcMetadataOptions()
- {
- ReleaseDateFormat = "yyyy-MM-dd";
-
- SaveImagePathsInNfo = true;
- EnablePathSubstitution = true;
- }
}
}
diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs
index bbb8bf4263..4d4d8d78cb 100644
--- a/MediaBrowser.Model/Dlna/AudioOptions.cs
+++ b/MediaBrowser.Model/Dlna/AudioOptions.cs
@@ -34,20 +34,20 @@ namespace MediaBrowser.Model.Dlna
public DeviceProfile Profile { get; set; }
///
- /// Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested.
+ /// Gets or sets a media source id. Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested.
///
public string MediaSourceId { get; set; }
public string DeviceId { get; set; }
///
- /// Allows an override of supported number of audio channels
- /// Example: DeviceProfile supports five channel, but user only has stereo speakers
+ /// Gets or sets an override of supported number of audio channels
+ /// Example: DeviceProfile supports five channel, but user only has stereo speakers.
///
public int? MaxAudioChannels { get; set; }
///
- /// The application's configured quality setting.
+ /// Gets or sets the application's configured quality setting.
///
public int? MaxBitrate { get; set; }
@@ -66,6 +66,7 @@ namespace MediaBrowser.Model.Dlna
///
/// Gets the maximum bitrate.
///
+ /// Whether or not this is audio.
/// System.Nullable<System.Int32>.
public int? GetMaxBitrate(bool isAudio)
{
diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs
index d4fd3e6730..8343cf028b 100644
--- a/MediaBrowser.Model/Dlna/CodecProfile.cs
+++ b/MediaBrowser.Model/Dlna/CodecProfile.cs
@@ -9,6 +9,12 @@ namespace MediaBrowser.Model.Dlna
{
public class CodecProfile
{
+ public CodecProfile()
+ {
+ Conditions = Array.Empty();
+ ApplyConditions = Array.Empty();
+ }
+
[XmlAttribute("type")]
public CodecType Type { get; set; }
@@ -22,12 +28,6 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("container")]
public string Container { get; set; }
- public CodecProfile()
- {
- Conditions = Array.Empty();
- ApplyConditions = Array.Empty();
- }
-
public string[] GetCodecs()
{
return ContainerProfile.SplitValue(Codec);
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index faf1ee41be..55c4dd0742 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -1,8 +1,8 @@
#pragma warning disable CS1591
using System;
-using System.Linq;
using System.Globalization;
+using System.Linq;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs
index 56c89d854f..d83c8f2f3c 100644
--- a/MediaBrowser.Model/Dlna/ContainerProfile.cs
+++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs
@@ -9,6 +9,11 @@ namespace MediaBrowser.Model.Dlna
{
public class ContainerProfile
{
+ public ContainerProfile()
+ {
+ Conditions = Array.Empty();
+ }
+
[XmlAttribute("type")]
public DlnaProfileType Type { get; set; }
@@ -17,11 +22,6 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("container")]
public string Container { get; set; }
- public ContainerProfile()
- {
- Conditions = Array.Empty();
- }
-
public string[] GetContainers()
{
return SplitValue(Container);
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 50e3374f77..ec106f1054 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -81,13 +81,13 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.DlnaV15;
// if (isDirectStream)
- //{
- // flagValue = flagValue | DlnaFlags.ByteBasedSeek;
- //}
- // else if (runtimeTicks.HasValue)
- //{
- // flagValue = flagValue | DlnaFlags.TimeBasedSeek;
- //}
+ // {
+ // flagValue = flagValue | DlnaFlags.ByteBasedSeek;
+ // }
+ // else if (runtimeTicks.HasValue)
+ // {
+ // flagValue = flagValue | DlnaFlags.TimeBasedSeek;
+ // }
string dlnaflags = string.Format(
CultureInfo.InvariantCulture,
@@ -150,16 +150,18 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.DlnaV15;
// if (isDirectStream)
- //{
- // flagValue = flagValue | DlnaFlags.ByteBasedSeek;
- //}
- // else if (runtimeTicks.HasValue)
- //{
- // flagValue = flagValue | DlnaFlags.TimeBasedSeek;
- //}
-
- string dlnaflags = string.Format(CultureInfo.InvariantCulture, ";DLNA.ORG_FLAGS={0}",
- DlnaMaps.FlagsToString(flagValue));
+ // {
+ // flagValue = flagValue | DlnaFlags.ByteBasedSeek;
+ // }
+ // else if (runtimeTicks.HasValue)
+ // {
+ // flagValue = flagValue | DlnaFlags.TimeBasedSeek;
+ // }
+
+ string dlnaflags = string.Format(
+ CultureInfo.InvariantCulture,
+ ";DLNA.ORG_FLAGS={0}",
+ DlnaMaps.FlagsToString(flagValue));
ResponseProfile mediaProfile = _profile.GetVideoMediaProfile(
container,
diff --git a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs
index 05209e53d0..086088deae 100644
--- a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs
+++ b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs
@@ -8,6 +8,7 @@ namespace MediaBrowser.Model.Dlna
public interface IDeviceDiscovery
{
event EventHandler> DeviceDiscovered;
+
event EventHandler> DeviceLeft;
}
}
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
index 3c955989a1..f61b8d59e8 100644
--- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
@@ -57,7 +57,6 @@ namespace MediaBrowser.Model.Dlna
string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
{
-
return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType);
}
@@ -323,7 +322,6 @@ namespace MediaBrowser.Model.Dlna
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase) &&
(string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "wmapro", StringComparison.OrdinalIgnoreCase)))
{
-
if (width.HasValue && height.HasValue)
{
if ((width.Value <= 720) && (height.Value <= 576))
@@ -479,7 +477,9 @@ namespace MediaBrowser.Model.Dlna
{
if (string.Equals(container, "jpeg", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "jpg", StringComparison.OrdinalIgnoreCase))
+ {
return ResolveImageJPGFormat(width, height);
+ }
if (string.Equals(container, "png", StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs
index 30c44fbe0e..f8f76c69d8 100644
--- a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs
+++ b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs
@@ -4,14 +4,14 @@ namespace MediaBrowser.Model.Dlna
{
public class ResolutionConfiguration
{
- public int MaxWidth { get; set; }
-
- public int MaxBitrate { get; set; }
-
public ResolutionConfiguration(int maxWidth, int maxBitrate)
{
MaxWidth = maxWidth;
MaxBitrate = maxBitrate;
}
+
+ public int MaxWidth { get; set; }
+
+ public int MaxBitrate { get; set; }
}
}
diff --git a/MediaBrowser.Model/Dlna/ResponseProfile.cs b/MediaBrowser.Model/Dlna/ResponseProfile.cs
index 48f53f06c2..bf9661f7f3 100644
--- a/MediaBrowser.Model/Dlna/ResponseProfile.cs
+++ b/MediaBrowser.Model/Dlna/ResponseProfile.cs
@@ -8,6 +8,11 @@ namespace MediaBrowser.Model.Dlna
{
public class ResponseProfile
{
+ public ResponseProfile()
+ {
+ Conditions = Array.Empty();
+ }
+
[XmlAttribute("container")]
public string Container { get; set; }
@@ -28,11 +33,6 @@ namespace MediaBrowser.Model.Dlna
public ProfileCondition[] Conditions { get; set; }
- public ResponseProfile()
- {
- Conditions = Array.Empty();
- }
-
public string[] GetContainers()
{
return ContainerProfile.SplitValue(Container);
diff --git a/MediaBrowser.Model/Dlna/SearchCriteria.cs b/MediaBrowser.Model/Dlna/SearchCriteria.cs
index 94f5bd3dbe..b1fc48c087 100644
--- a/MediaBrowser.Model/Dlna/SearchCriteria.cs
+++ b/MediaBrowser.Model/Dlna/SearchCriteria.cs
@@ -7,31 +7,6 @@ namespace MediaBrowser.Model.Dlna
{
public class SearchCriteria
{
- public SearchType SearchType { get; set; }
-
- ///
- /// Splits the specified string.
- ///
- /// The string.
- /// The term.
- /// The limit.
- /// System.String[].
- private static string[] RegexSplit(string str, string term, int limit)
- {
- return new Regex(term).Split(str, limit);
- }
-
- ///
- /// Splits the specified string.
- ///
- /// The string.
- /// The term.
- /// System.String[].
- private static string[] RegexSplit(string str, string term)
- {
- return Regex.Split(str, term, RegexOptions.IgnoreCase);
- }
-
public SearchCriteria(string search)
{
if (search.Length == 0)
@@ -48,8 +23,8 @@ namespace MediaBrowser.Model.Dlna
if (subFactors.Length == 3)
{
- if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase) &&
- (string.Equals("=", subFactors[1], StringComparison.Ordinal) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase)))
+ if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase)
+ && (string.Equals("=", subFactors[1], StringComparison.Ordinal) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase)))
{
if (string.Equals("\"object.item.imageItem\"", subFactors[2], StringComparison.Ordinal) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
@@ -71,5 +46,30 @@ namespace MediaBrowser.Model.Dlna
}
}
}
+
+ public SearchType SearchType { get; set; }
+
+ ///
+ /// Splits the specified string.
+ ///
+ /// The string.
+ /// The term.
+ /// The limit.
+ /// System.String[].
+ private static string[] RegexSplit(string str, string term, int limit)
+ {
+ return new Regex(term).Split(str, limit);
+ }
+
+ ///
+ /// Splits the specified string.
+ ///
+ /// The string.
+ /// The term.
+ /// System.String[].
+ private static string[] RegexSplit(string str, string term)
+ {
+ return Regex.Split(str, term, RegexOptions.IgnoreCase);
+ }
}
}
diff --git a/MediaBrowser.Model/Dlna/SortCriteria.cs b/MediaBrowser.Model/Dlna/SortCriteria.cs
index 53e4540cbb..7769d0bd3e 100644
--- a/MediaBrowser.Model/Dlna/SortCriteria.cs
+++ b/MediaBrowser.Model/Dlna/SortCriteria.cs
@@ -6,10 +6,10 @@ namespace MediaBrowser.Model.Dlna
{
public class SortCriteria
{
- public SortOrder SortOrder => SortOrder.Ascending;
-
public SortCriteria(string value)
{
}
+
+ public SortOrder SortOrder => SortOrder.Ascending;
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index a3983afe5f..bf33691c77 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -227,7 +227,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, string _, DeviceProfile profile, DlnaProfileType type)
+ public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type)
{
if (string.IsNullOrEmpty(inputContainer))
{
@@ -274,14 +274,14 @@ namespace MediaBrowser.Model.Dlna
if (options.ForceDirectPlay)
{
playlistItem.PlayMethod = PlayMethod.DirectPlay;
- playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio);
+ playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
if (options.ForceDirectStream)
{
playlistItem.PlayMethod = PlayMethod.DirectStream;
- playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio);
+ playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
@@ -349,7 +349,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.PlayMethod = PlayMethod.DirectStream;
}
- playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio);
+ playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
@@ -698,7 +698,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlay != null)
{
playlistItem.PlayMethod = directPlay.Value;
- playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Video);
+ playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video);
if (subtitleStream != null)
{
@@ -1404,7 +1404,9 @@ namespace MediaBrowser.Model.Dlna
{
_logger.LogInformation(
"Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
- playMethod, itemBitrate, requestedMaxBitrate);
+ playMethod,
+ itemBitrate,
+ requestedMaxBitrate);
return false;
}
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 4765052d59..29da5d9e7f 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -27,45 +27,6 @@ namespace MediaBrowser.Model.Dlna
StreamOptions = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
- public void SetOption(string qualifier, string name, string value)
- {
- if (string.IsNullOrEmpty(qualifier))
- {
- SetOption(name, value);
- }
- else
- {
- SetOption(qualifier + "-" + name, value);
- }
- }
-
- public void SetOption(string name, string value)
- {
- StreamOptions[name] = value;
- }
-
- public string GetOption(string qualifier, string name)
- {
- var value = GetOption(qualifier + "-" + name);
-
- if (string.IsNullOrEmpty(value))
- {
- value = GetOption(name);
- }
-
- return value;
- }
-
- public string GetOption(string name)
- {
- if (StreamOptions.TryGetValue(name, out var value))
- {
- return value;
- }
-
- return null;
- }
-
public Guid ItemId { get; set; }
public PlayMethod PlayMethod { get; set; }
@@ -152,887 +113,906 @@ namespace MediaBrowser.Model.Dlna
PlayMethod == PlayMethod.DirectStream ||
PlayMethod == PlayMethod.DirectPlay;
- public string ToUrl(string baseUrl, string accessToken)
- {
- if (PlayMethod == PlayMethod.DirectPlay)
- {
- return MediaSource.Path;
- }
+ ///
+ /// Gets the audio stream that will be used.
+ ///
+ public MediaStream TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex);
- if (string.IsNullOrEmpty(baseUrl))
+ ///
+ /// Gets the video stream that will be used.
+ ///
+ public MediaStream TargetVideoStream => MediaSource?.VideoStream;
+
+ ///
+ /// Gets the audio sample rate that will be in the output stream.
+ ///
+ public int? TargetAudioSampleRate
+ {
+ get
{
- throw new ArgumentNullException(nameof(baseUrl));
+ var stream = TargetAudioStream;
+ return AudioSampleRate.HasValue && !IsDirectStream
+ ? AudioSampleRate
+ : stream == null ? null : stream.SampleRate;
}
+ }
- var list = new List();
- foreach (NameValuePair pair in BuildParams(this, accessToken))
+ ///
+ /// Gets the audio sample rate that will be in the output stream.
+ ///
+ public int? TargetAudioBitDepth
+ {
+ get
{
- if (string.IsNullOrEmpty(pair.Value))
+ if (IsDirectStream)
{
- continue;
+ return TargetAudioStream == null ? (int?)null : TargetAudioStream.BitDepth;
}
- // Try to keep the url clean by omitting defaults
- if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) &&
- string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
+ var targetAudioCodecs = TargetAudioCodec;
+ var audioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
+ if (!string.IsNullOrEmpty(audioCodec))
{
- continue;
+ return GetTargetAudioBitDepth(audioCodec);
}
- if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) &&
- string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
+ return TargetAudioStream == null ? (int?)null : TargetAudioStream.BitDepth;
+ }
+ }
+
+ ///
+ /// Gets the audio sample rate that will be in the output stream.
+ ///
+ public int? TargetVideoBitDepth
+ {
+ get
+ {
+ if (IsDirectStream)
{
- continue;
+ return TargetVideoStream == null ? (int?)null : TargetVideoStream.BitDepth;
}
- // Be careful, IsDirectStream==true by default (Static != false or not in query).
- // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true.
- if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) &&
- string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase))
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
{
- continue;
+ return GetTargetVideoBitDepth(videoCodec);
}
- var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
-
- list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
+ return TargetVideoStream == null ? (int?)null : TargetVideoStream.BitDepth;
}
-
- string queryString = string.Join('&', list);
-
- return GetUrl(baseUrl, queryString);
}
- private string GetUrl(string baseUrl, string queryString)
+ ///
+ /// Gets the target reference frames.
+ ///
+ /// The target reference frames.
+ public int? TargetRefFrames
{
- if (string.IsNullOrEmpty(baseUrl))
- {
- throw new ArgumentNullException(nameof(baseUrl));
- }
-
- string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
-
- baseUrl = baseUrl.TrimEnd('/');
-
- if (MediaType == DlnaProfileType.Audio)
+ get
{
- if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
+ if (IsDirectStream)
{
- return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
+ return TargetVideoStream == null ? (int?)null : TargetVideoStream.RefFrames;
}
- return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
- }
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
+ {
+ return GetTargetRefFrames(videoCodec);
+ }
- if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
- {
- return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
+ return TargetVideoStream == null ? (int?)null : TargetVideoStream.RefFrames;
}
-
- return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
}
- private static List BuildParams(StreamInfo item, string accessToken)
+ ///
+ /// Gets the audio sample rate that will be in the output stream.
+ ///
+ public float? TargetFramerate
{
- var list = new List();
-
- string audioCodecs = item.AudioCodecs.Length == 0 ?
- string.Empty :
- string.Join(',', item.AudioCodecs);
-
- string videoCodecs = item.VideoCodecs.Length == 0 ?
- string.Empty :
- string.Join(',', item.VideoCodecs);
-
- list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
- list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
- list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
- list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- list.Add(new NameValuePair("VideoCodec", videoCodecs));
- list.Add(new NameValuePair("AudioCodec", audioCodecs));
- list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
-
- list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
-
- long startPositionTicks = item.StartPositionTicks;
-
- var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase);
-
- if (isHls)
- {
- list.Add(new NameValuePair("StartTimeTicks", string.Empty));
- }
- else
+ get
{
- list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
+ var stream = TargetVideoStream;
+ return MaxFramerate.HasValue && !IsDirectStream
+ ? MaxFramerate
+ : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate;
}
+ }
- list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
- list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
-
- 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));
-
- if (!item.IsDirectStream)
+ ///
+ /// Gets the audio sample rate that will be in the output stream.
+ ///
+ public double? TargetVideoLevel
+ {
+ get
{
- if (item.RequireNonAnamorphic)
+ if (IsDirectStream)
{
- list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ return TargetVideoStream == null ? (double?)null : TargetVideoStream.Level;
}
- list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
-
- if (item.EnableSubtitlesInManifest)
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
{
- list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ return GetTargetVideoLevel(videoCodec);
}
- if (item.EnableMpegtsM2TsMode)
- {
- list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- }
+ return TargetVideoStream == null ? (double?)null : TargetVideoStream.Level;
+ }
+ }
- if (item.EstimateContentLength)
- {
- list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- }
+ ///
+ /// Gets the audio sample rate that will be in the output stream.
+ ///
+ public int? TargetPacketLength
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+ return !IsDirectStream
+ ? null
+ : stream == null ? null : stream.PacketLength;
+ }
+ }
- if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
+ ///
+ /// Gets the audio sample rate that will be in the output stream.
+ ///
+ public string TargetVideoProfile
+ {
+ get
+ {
+ if (IsDirectStream)
{
- list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
+ return TargetVideoStream == null ? null : TargetVideoStream.Profile;
}
- if (item.CopyTimestamps)
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
{
- list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ return GetOption(videoCodec, "profile");
}
- list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- }
-
- list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty));
-
- string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
- string.Empty :
- string.Join(',', item.SubtitleCodecs);
-
- list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
-
- if (isHls)
- {
- list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
-
- if (item.SegmentLength.HasValue)
- {
- list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
- }
-
- if (item.MinSegments.HasValue)
- {
- list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
- }
-
- list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
- }
-
- foreach (var pair in item.StreamOptions)
- {
- if (string.IsNullOrEmpty(pair.Value))
- {
- continue;
- }
-
- // strip spaces to avoid having to encode h264 profile names
- list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
+ return TargetVideoStream == null ? null : TargetVideoStream.Profile;
}
+ }
- if (!item.IsDirectStream)
+ ///
+ /// Gets the target video codec tag.
+ ///
+ /// The target video codec tag.
+ public string TargetVideoCodecTag
+ {
+ get
{
- list.Add(new NameValuePair("TranscodeReasons", string.Join(',', item.TranscodeReasons.Distinct().Select(i => i.ToString()))));
+ var stream = TargetVideoStream;
+ return !IsDirectStream
+ ? null
+ : stream == null ? null : stream.CodecTag;
}
-
- return list;
}
- public List GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken)
+ ///
+ /// Gets the audio bitrate that will be in the output stream.
+ ///
+ public int? TargetAudioBitrate
{
- return GetExternalSubtitles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
+ get
+ {
+ var stream = TargetAudioStream;
+ return AudioBitrate.HasValue && !IsDirectStream
+ ? AudioBitrate
+ : stream == null ? null : stream.BitRate;
+ }
}
- public List GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
+ ///
+ /// Gets the audio channels that will be in the output stream.
+ ///
+ public int? TargetAudioChannels
{
- var list = GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken);
- var newList = new List();
-
- // First add the selected track
- foreach (SubtitleStreamInfo stream in list)
+ get
{
- if (stream.DeliveryMethod == SubtitleDeliveryMethod.External)
+ if (IsDirectStream)
{
- newList.Add(stream);
+ return TargetAudioStream == null ? (int?)null : TargetAudioStream.Channels;
}
- }
- return newList;
- }
+ var targetAudioCodecs = TargetAudioCodec;
+ var codec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
+ if (!string.IsNullOrEmpty(codec))
+ {
+ return GetTargetRefFrames(codec);
+ }
- public List GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken)
- {
- return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
+ return TargetAudioStream == null ? (int?)null : TargetAudioStream.Channels;
+ }
}
- public List GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
+ ///
+ /// Gets the audio codec that will be in the output stream.
+ ///
+ public string[] TargetAudioCodec
{
- var list = new List();
+ get
+ {
+ var stream = TargetAudioStream;
- // HLS will preserve timestamps so we can just grab the full subtitle stream
- long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)
- ? 0
- : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
+ string inputCodec = stream?.Codec;
- // First add the selected track
- if (SubtitleStreamIndex.HasValue)
- {
- foreach (var stream in MediaSource.MediaStreams)
+ if (IsDirectStream)
{
- if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
- {
- AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
- }
+ return string.IsNullOrEmpty(inputCodec) ? Array.Empty() : new[] { inputCodec };
}
- }
- if (!includeSelectedTrackOnly)
- {
- foreach (var stream in MediaSource.MediaStreams)
+ foreach (string codec in AudioCodecs)
{
- if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
+ if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{
- AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
+ return string.IsNullOrEmpty(codec) ? Array.Empty() : new[] { codec };
}
}
- }
-
- return list;
- }
-
- private void AddSubtitleProfiles(List 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);
- }
- }
- else
- {
- var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
- list.Add(info);
+ return AudioCodecs;
}
}
- private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
+ public string[] TargetVideoCodec
{
- var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
- var info = new SubtitleStreamInfo
+ get
{
- IsForced = stream.IsForced,
- Language = stream.Language,
- Name = stream.Language ?? "Unknown",
- Format = subtitleProfile.Format,
- Index = stream.Index,
- DeliveryMethod = subtitleProfile.Method,
- DisplayTitle = stream.DisplayTitle
- };
+ var stream = TargetVideoStream;
- if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
- {
- if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
+ string inputCodec = stream?.Codec;
+
+ if (IsDirectStream)
{
- info.Url = string.Format(CultureInfo.InvariantCulture, "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
- baseUrl,
- ItemId,
- MediaSourceId,
- stream.Index.ToString(CultureInfo.InvariantCulture),
- startPositionTicks.ToString(CultureInfo.InvariantCulture),
- subtitleProfile.Format);
+ return string.IsNullOrEmpty(inputCodec) ? Array.Empty() : new[] { inputCodec };
+ }
- if (!string.IsNullOrEmpty(accessToken))
+ foreach (string codec in VideoCodecs)
+ {
+ if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{
- info.Url += "?api_key=" + accessToken;
+ return string.IsNullOrEmpty(codec) ? Array.Empty() : new[] { codec };
}
-
- info.IsExternalUrl = false;
- }
- else
- {
- info.Url = stream.Path;
- info.IsExternalUrl = true;
}
- }
- return info;
+ return VideoCodecs;
+ }
}
///
- /// Returns the audio stream that will be used.
+ /// Gets the audio channels that will be in the output stream.
///
- public MediaStream TargetAudioStream
+ public long? TargetSize
{
get
{
- if (MediaSource != null)
+ if (IsDirectStream)
+ {
+ return MediaSource.Size;
+ }
+
+ if (RunTimeTicks.HasValue)
{
- return MediaSource.GetDefaultAudioStream(AudioStreamIndex);
+ int? totalBitrate = TargetTotalBitrate;
+
+ double totalSeconds = RunTimeTicks.Value;
+ // Convert to ms
+ totalSeconds /= 10000;
+ // Convert to seconds
+ totalSeconds /= 1000;
+
+ return totalBitrate.HasValue ?
+ Convert.ToInt64(totalBitrate.Value * totalSeconds) :
+ (long?)null;
}
return null;
}
}
- ///
- /// Returns the video stream that will be used.
- ///
- public MediaStream TargetVideoStream
+ public int? TargetVideoBitrate
{
get
{
- if (MediaSource != null)
- {
- return MediaSource.VideoStream;
- }
+ var stream = TargetVideoStream;
- return null;
+ return VideoBitrate.HasValue && !IsDirectStream
+ ? VideoBitrate
+ : stream == null ? null : stream.BitRate;
}
}
- ///
- /// Predicts the audio sample rate that will be in the output stream.
- ///
- public int? TargetAudioSampleRate
+ public TransportStreamTimestamp TargetTimestamp
{
get
{
- var stream = TargetAudioStream;
- return AudioSampleRate.HasValue && !IsDirectStream
- ? AudioSampleRate
- : stream == null ? null : stream.SampleRate;
+ var defaultValue = string.Equals(Container, "m2ts", StringComparison.OrdinalIgnoreCase)
+ ? TransportStreamTimestamp.Valid
+ : TransportStreamTimestamp.None;
+
+ return !IsDirectStream
+ ? defaultValue
+ : MediaSource == null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None;
}
}
- ///
- /// Predicts the audio sample rate that will be in the output stream.
- ///
- public int? TargetAudioBitDepth
+ public int? TargetTotalBitrate => (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0);
+
+ public bool? IsTargetAnamorphic
{
get
{
if (IsDirectStream)
{
- return TargetAudioStream == null ? (int?)null : TargetAudioStream.BitDepth;
- }
-
- var targetAudioCodecs = TargetAudioCodec;
- var audioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
- if (!string.IsNullOrEmpty(audioCodec))
- {
- return GetTargetAudioBitDepth(audioCodec);
+ return TargetVideoStream == null ? null : TargetVideoStream.IsAnamorphic;
}
- return TargetAudioStream == null ? (int?)null : TargetAudioStream.BitDepth;
+ return false;
}
}
- ///
- /// Predicts the audio sample rate that will be in the output stream.
- ///
- public int? TargetVideoBitDepth
+ public bool? IsTargetInterlaced
{
get
{
if (IsDirectStream)
{
- return TargetVideoStream == null ? (int?)null : TargetVideoStream.BitDepth;
+ return TargetVideoStream == null ? (bool?)null : TargetVideoStream.IsInterlaced;
}
var targetVideoCodecs = TargetVideoCodec;
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
if (!string.IsNullOrEmpty(videoCodec))
{
- return GetTargetVideoBitDepth(videoCodec);
- }
+ if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
- return TargetVideoStream == null ? (int?)null : TargetVideoStream.BitDepth;
+ return TargetVideoStream == null ? (bool?)null : TargetVideoStream.IsInterlaced;
}
}
- ///
- /// Gets the target reference frames.
- ///
- /// The target reference frames.
- public int? TargetRefFrames
+ public bool? IsTargetAVC
{
get
{
if (IsDirectStream)
{
- return TargetVideoStream == null ? (int?)null : TargetVideoStream.RefFrames;
+ return TargetVideoStream == null ? null : TargetVideoStream.IsAVC;
}
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
+ return true;
+ }
+ }
+
+ public int? TargetWidth
+ {
+ get
+ {
+ var videoStream = TargetVideoStream;
+
+ if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue)
{
- return GetTargetRefFrames(videoCodec);
+ ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
+
+ size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
+
+ return size.Width;
}
- return TargetVideoStream == null ? (int?)null : TargetVideoStream.RefFrames;
+ return MaxWidth;
}
}
- ///
- /// Predicts the audio sample rate that will be in the output stream.
- ///
- public float? TargetFramerate
+ public int? TargetHeight
{
get
{
- var stream = TargetVideoStream;
- return MaxFramerate.HasValue && !IsDirectStream
- ? MaxFramerate
- : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate;
+ var videoStream = TargetVideoStream;
+
+ if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue)
+ {
+ ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
+
+ size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
+
+ return size.Height;
+ }
+
+ return MaxHeight;
}
}
- ///
- /// Predicts the audio sample rate that will be in the output stream.
- ///
- public double? TargetVideoLevel
+ public int? TargetVideoStreamCount
{
get
{
if (IsDirectStream)
{
- return TargetVideoStream == null ? (double?)null : TargetVideoStream.Level;
- }
-
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
- {
- return GetTargetVideoLevel(videoCodec);
+ return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
}
- return TargetVideoStream == null ? (double?)null : TargetVideoStream.Level;
+ return GetMediaStreamCount(MediaStreamType.Video, 1);
}
}
- public int? GetTargetVideoBitDepth(string codec)
+ public int? TargetAudioStreamCount
{
- var value = GetOption(codec, "videobitdepth");
- if (string.IsNullOrEmpty(value))
+ get
{
- return null;
- }
+ if (IsDirectStream)
+ {
+ return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
+ }
- if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
- {
- return result;
+ return GetMediaStreamCount(MediaStreamType.Audio, 1);
}
-
- return null;
}
- public int? GetTargetAudioBitDepth(string codec)
+ public void SetOption(string qualifier, string name, string value)
{
- var value = GetOption(codec, "audiobitdepth");
- if (string.IsNullOrEmpty(value))
+ if (string.IsNullOrEmpty(qualifier))
{
- return null;
+ SetOption(name, value);
}
-
- if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
+ else
{
- return result;
+ SetOption(qualifier + "-" + name, value);
}
+ }
- return null;
+ public void SetOption(string name, string value)
+ {
+ StreamOptions[name] = value;
}
- public double? GetTargetVideoLevel(string codec)
+ public string GetOption(string qualifier, string name)
{
- var value = GetOption(codec, "level");
+ var value = GetOption(qualifier + "-" + name);
+
if (string.IsNullOrEmpty(value))
{
- return null;
+ value = GetOption(name);
}
- if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ return value;
+ }
+
+ public string GetOption(string name)
+ {
+ if (StreamOptions.TryGetValue(name, out var value))
{
- return result;
+ return value;
}
return null;
}
- public int? GetTargetRefFrames(string codec)
+ public string ToUrl(string baseUrl, string accessToken)
{
- var value = GetOption(codec, "maxrefframes");
- if (string.IsNullOrEmpty(value))
+ if (PlayMethod == PlayMethod.DirectPlay)
{
- return null;
+ return MediaSource.Path;
}
- if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ if (string.IsNullOrEmpty(baseUrl))
{
- return result;
+ throw new ArgumentNullException(nameof(baseUrl));
}
- return null;
- }
-
- ///
- /// Predicts the audio sample rate that will be in the output stream.
- ///
- public int? TargetPacketLength
- {
- get
+ var list = new List();
+ foreach (NameValuePair pair in BuildParams(this, accessToken))
{
- var stream = TargetVideoStream;
- return !IsDirectStream
- ? null
- : stream == null ? null : stream.PacketLength;
- }
- }
+ if (string.IsNullOrEmpty(pair.Value))
+ {
+ continue;
+ }
- ///
- /// Predicts the audio sample rate that will be in the output stream.
- ///
- public string TargetVideoProfile
- {
- get
- {
- if (IsDirectStream)
+ // Try to keep the url clean by omitting defaults
+ if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
{
- return TargetVideoStream == null ? null : TargetVideoStream.Profile;
+ continue;
}
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
+ if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
{
- return GetOption(videoCodec, "profile");
+ continue;
}
- return TargetVideoStream == null ? null : TargetVideoStream.Profile;
- }
- }
+ // Be careful, IsDirectStream==true by default (Static != false or not in query).
+ // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true.
+ if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
- ///
- /// Gets the target video codec tag.
- ///
- /// The target video codec tag.
- public string TargetVideoCodecTag
- {
- get
- {
- var stream = TargetVideoStream;
- return !IsDirectStream
- ? null
- : stream == null ? null : stream.CodecTag;
+ var encodedValue = pair.Value.Replace(" ", "%20");
+
+ list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
}
+
+ string queryString = string.Join("&", list.ToArray());
+
+ return GetUrl(baseUrl, queryString);
}
- ///
- /// Predicts the audio bitrate that will be in the output stream.
- ///
- public int? TargetAudioBitrate
+ private string GetUrl(string baseUrl, string queryString)
{
- get
+ if (string.IsNullOrEmpty(baseUrl))
{
- var stream = TargetAudioStream;
- return AudioBitrate.HasValue && !IsDirectStream
- ? AudioBitrate
- : stream == null ? null : stream.BitRate;
+ throw new ArgumentNullException(nameof(baseUrl));
}
- }
- ///
- /// Predicts the audio channels that will be in the output stream.
- ///
- public int? TargetAudioChannels
- {
- get
- {
- if (IsDirectStream)
- {
- return TargetAudioStream == null ? (int?)null : TargetAudioStream.Channels;
- }
+ string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
- var targetAudioCodecs = TargetAudioCodec;
- var codec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
- if (!string.IsNullOrEmpty(codec))
+ baseUrl = baseUrl.TrimEnd('/');
+
+ if (MediaType == DlnaProfileType.Audio)
+ {
+ if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
- return GetTargetRefFrames(codec);
+ return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
}
- return TargetAudioStream == null ? (int?)null : TargetAudioStream.Channels;
- }
- }
-
- public int? GetTargetAudioChannels(string codec)
- {
- var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
-
- var value = GetOption(codec, "audiochannels");
- if (string.IsNullOrEmpty(value))
- {
- return defaultValue;
+ return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
}
- if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
+ if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
- return Math.Min(result, defaultValue ?? result);
+ return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
}
- return defaultValue;
+ return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
}
- ///
- /// Predicts the audio codec that will be in the output stream.
- ///
- public string[] TargetAudioCodec
+ private static List BuildParams(StreamInfo item, string accessToken)
{
- get
+ var list = new List();
+
+ string audioCodecs = item.AudioCodecs.Length == 0 ?
+ string.Empty :
+ string.Join(",", item.AudioCodecs);
+
+ string videoCodecs = item.VideoCodecs.Length == 0 ?
+ string.Empty :
+ string.Join(",", item.VideoCodecs);
+
+ list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
+ list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
+ list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
+ list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ list.Add(new NameValuePair("VideoCodec", videoCodecs));
+ list.Add(new NameValuePair("AudioCodec", audioCodecs));
+ list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+
+ list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+
+ long startPositionTicks = item.StartPositionTicks;
+
+ var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase);
+
+ if (isHls)
{
- var stream = TargetAudioStream;
+ list.Add(new NameValuePair("StartTimeTicks", string.Empty));
+ }
+ else
+ {
+ list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
+ }
- string inputCodec = stream?.Codec;
+ list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
+ list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
- if (IsDirectStream)
+ 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));
+
+ if (!item.IsDirectStream)
+ {
+ if (item.RequireNonAnamorphic)
{
- return string.IsNullOrEmpty(inputCodec) ? Array.Empty() : new[] { inputCodec };
+ list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
}
- foreach (string codec in AudioCodecs)
+ list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+
+ if (item.EnableSubtitlesInManifest)
{
- if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
- {
- return string.IsNullOrEmpty(codec) ? Array.Empty() : new[] { codec };
- }
+ list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
}
- return AudioCodecs;
- }
- }
-
- public string[] TargetVideoCodec
- {
- get
- {
- var stream = TargetVideoStream;
+ if (item.EnableMpegtsM2TsMode)
+ {
+ list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ }
- string inputCodec = stream?.Codec;
+ if (item.EstimateContentLength)
+ {
+ list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ }
- if (IsDirectStream)
+ if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
{
- return string.IsNullOrEmpty(inputCodec) ? Array.Empty() : new[] { inputCodec };
+ list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
}
- foreach (string codec in VideoCodecs)
+ if (item.CopyTimestamps)
{
- if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
- {
- return string.IsNullOrEmpty(codec) ? Array.Empty() : new[] { codec };
- }
+ list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
}
- return VideoCodecs;
+ list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
}
- }
- ///
- /// Predicts the audio channels that will be in the output stream.
- ///
- public long? TargetSize
- {
- get
+ list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty));
+
+ string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
+ string.Empty :
+ string.Join(",", item.SubtitleCodecs);
+
+ list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
+
+ if (isHls)
{
- if (IsDirectStream)
+ list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
+
+ if (item.SegmentLength.HasValue)
{
- return MediaSource.Size;
+ list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
}
- if (RunTimeTicks.HasValue)
+ if (item.MinSegments.HasValue)
{
- int? totalBitrate = TargetTotalBitrate;
-
- double totalSeconds = RunTimeTicks.Value;
- // Convert to ms
- totalSeconds /= 10000;
- // Convert to seconds
- totalSeconds /= 1000;
-
- return totalBitrate.HasValue ?
- Convert.ToInt64(totalBitrate.Value * totalSeconds) :
- (long?)null;
+ list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
}
- return null;
+ list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
}
- }
- public int? TargetVideoBitrate
- {
- get
+ foreach (var pair in item.StreamOptions)
{
- var stream = TargetVideoStream;
+ if (string.IsNullOrEmpty(pair.Value))
+ {
+ continue;
+ }
- return VideoBitrate.HasValue && !IsDirectStream
- ? VideoBitrate
- : stream == null ? null : stream.BitRate;
+ // strip spaces to avoid having to encode h264 profile names
+ list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty)));
}
- }
- public TransportStreamTimestamp TargetTimestamp
- {
- get
+ if (!item.IsDirectStream)
{
- var defaultValue = string.Equals(Container, "m2ts", StringComparison.OrdinalIgnoreCase)
- ? TransportStreamTimestamp.Valid
- : TransportStreamTimestamp.None;
-
- return !IsDirectStream
- ? defaultValue
- : MediaSource == null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None;
+ list.Add(new NameValuePair("TranscodeReasons", string.Join(',', item.TranscodeReasons.Distinct())));
}
+
+ return list;
}
- public int? TargetTotalBitrate => (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0);
+ public List GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken)
+ {
+ return GetExternalSubtitles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
+ }
- public bool? IsTargetAnamorphic
+ public List GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
{
- get
+ var list = GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken);
+ var newList = new List();
+
+ // First add the selected track
+ foreach (SubtitleStreamInfo stream in list)
{
- if (IsDirectStream)
+ if (stream.DeliveryMethod == SubtitleDeliveryMethod.External)
{
- return TargetVideoStream == null ? null : TargetVideoStream.IsAnamorphic;
+ newList.Add(stream);
}
-
- return false;
}
+
+ return newList;
}
- public bool? IsTargetInterlaced
+ public List GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken)
{
- get
+ return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
+ }
+
+ public List GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
+ {
+ var list = new List();
+
+ // HLS will preserve timestamps so we can just grab the full subtitle stream
+ long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)
+ ? 0
+ : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
+
+ // First add the selected track
+ if (SubtitleStreamIndex.HasValue)
{
- if (IsDirectStream)
+ foreach (var stream in MediaSource.MediaStreams)
{
- return TargetVideoStream == null ? (bool?)null : TargetVideoStream.IsInterlaced;
+ if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
+ {
+ AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
+ }
}
+ }
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
+ if (!includeSelectedTrackOnly)
+ {
+ foreach (var stream in MediaSource.MediaStreams)
{
- if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
+ if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
{
- return false;
+ AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
}
}
-
- return TargetVideoStream == null ? (bool?)null : TargetVideoStream.IsInterlaced;
}
+
+ return list;
}
- public bool? IsTargetAVC
+ private void AddSubtitleProfiles(List list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
{
- get
+ if (enableAllProfiles)
{
- if (IsDirectStream)
+ foreach (var profile in DeviceProfile.SubtitleProfiles)
{
- return TargetVideoStream == null ? null : TargetVideoStream.IsAVC;
+ var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
+
+ list.Add(info);
}
+ }
+ else
+ {
+ var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
- return true;
+ list.Add(info);
}
}
- public int? TargetWidth
+ private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
{
- get
+ var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
+ var info = new SubtitleStreamInfo
{
- var videoStream = TargetVideoStream;
+ IsForced = stream.IsForced,
+ Language = stream.Language,
+ Name = stream.Language ?? "Unknown",
+ Format = subtitleProfile.Format,
+ Index = stream.Index,
+ DeliveryMethod = subtitleProfile.Method,
+ DisplayTitle = stream.DisplayTitle
+ };
- if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue)
+ if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
+ {
+ if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
{
- ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
+ info.Url = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
+ baseUrl,
+ ItemId,
+ MediaSourceId,
+ stream.Index.ToString(CultureInfo.InvariantCulture),
+ startPositionTicks.ToString(CultureInfo.InvariantCulture),
+ subtitleProfile.Format);
- size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
+ if (!string.IsNullOrEmpty(accessToken))
+ {
+ info.Url += "?api_key=" + accessToken;
+ }
- return size.Width;
+ info.IsExternalUrl = false;
}
+ else
+ {
+ info.Url = stream.Path;
+ info.IsExternalUrl = true;
+ }
+ }
- return MaxWidth;
+ return info;
+ }
+
+ public int? GetTargetVideoBitDepth(string codec)
+ {
+ var value = GetOption(codec, "videobitdepth");
+ if (string.IsNullOrEmpty(value))
+ {
+ return null;
+ }
+
+ if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
}
+
+ return null;
}
- public int? TargetHeight
+ public int? GetTargetAudioBitDepth(string codec)
{
- get
+ var value = GetOption(codec, "audiobitdepth");
+ if (string.IsNullOrEmpty(value))
{
- var videoStream = TargetVideoStream;
+ return null;
+ }
- if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue)
- {
- ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
+ if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
- size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
+ return null;
+ }
- return size.Height;
- }
+ public double? GetTargetVideoLevel(string codec)
+ {
+ var value = GetOption(codec, "level");
+ if (string.IsNullOrEmpty(value))
+ {
+ return null;
+ }
- return MaxHeight;
+ if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
}
+
+ return null;
}
- public int? TargetVideoStreamCount
+ public int? GetTargetRefFrames(string codec)
{
- get
+ var value = GetOption(codec, "maxrefframes");
+ if (string.IsNullOrEmpty(value))
{
- if (IsDirectStream)
- {
- return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
- }
+ return null;
+ }
- return GetMediaStreamCount(MediaStreamType.Video, 1);
+ if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
}
+
+ return null;
}
- public int? TargetAudioStreamCount
+ public int? GetTargetAudioChannels(string codec)
{
- get
+ var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
+
+ var value = GetOption(codec, "audiochannels");
+ if (string.IsNullOrEmpty(value))
{
- if (IsDirectStream)
- {
- return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
- }
+ return defaultValue;
+ }
- return GetMediaStreamCount(MediaStreamType.Audio, 1);
+ if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
+ {
+ return Math.Min(result, defaultValue ?? result);
}
+
+ return defaultValue;
}
private int? GetMediaStreamCount(MediaStreamType type, int limit)
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 2f9f9d3cdb..a784025e3d 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -294,13 +294,13 @@ namespace MediaBrowser.Model.Dto
public NameGuidPair[] GenreItems { get; set; }
///
- /// If the item does not have a logo, this will hold the Id of the Parent that has one.
+ /// Gets or sets wether the item has a logo, this will hold the Id of the Parent that has one.
///
/// The parent logo item id.
public string ParentLogoItemId { get; set; }
///
- /// If the item does not have any backdrops, this will hold the Id of the Parent that has one.
+ /// Gets or sets wether the item has any backdrops, this will hold the Id of the Parent that has one.
///
/// The parent backdrop item id.
public string ParentBackdropItemId { get; set; }
@@ -318,7 +318,7 @@ namespace MediaBrowser.Model.Dto
public int? LocalTrailerCount { get; set; }
///
- /// User data for this item based on the user it's being requested for.
+ /// Gets or sets the user data for this item based on the user it's being requested for.
///
/// The user data.
public UserItemDataDto UserData { get; set; }
@@ -506,7 +506,7 @@ namespace MediaBrowser.Model.Dto
public string ParentLogoImageTag { get; set; }
///
- /// If the item does not have a art, this will hold the Id of the Parent that has one.
+ /// Gets or sets wether the item has fan art, this will hold the Id of the Parent that has one.
///
/// The parent art item id.
public string ParentArtItemId { get; set; }
@@ -695,7 +695,7 @@ namespace MediaBrowser.Model.Dto
public string ChannelPrimaryImageTag { get; set; }
///
- /// The start date of the recording, in UTC.
+ /// Gets or sets the start date of the recording, in UTC.
///
public DateTime? StartDate { get; set; }
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index be682be23c..ec3b37efa3 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -12,6 +12,18 @@ namespace MediaBrowser.Model.Dto
{
public class MediaSourceInfo
{
+ public MediaSourceInfo()
+ {
+ Formats = Array.Empty();
+ MediaStreams = new List();
+ MediaAttachments = Array.Empty();
+ RequiredHttpHeaders = new Dictionary();
+ SupportsTranscoding = true;
+ SupportsDirectStream = true;
+ SupportsDirectPlay = true;
+ SupportsProbing = true;
+ }
+
public MediaProtocol Protocol { get; set; }
public string Id { get; set; }
@@ -31,6 +43,7 @@ namespace MediaBrowser.Model.Dto
public string Name { get; set; }
///
+ /// Gets or sets a value indicating whether the media is remote.
/// Differentiate internet url vs local network.
///
public bool IsRemote { get; set; }
@@ -95,16 +108,28 @@ namespace MediaBrowser.Model.Dto
public int? AnalyzeDurationMs { get; set; }
- public MediaSourceInfo()
+ [JsonIgnore]
+ public TranscodeReason[] TranscodeReasons { get; set; }
+
+ public int? DefaultAudioStreamIndex { get; set; }
+
+ public int? DefaultSubtitleStreamIndex { get; set; }
+
+ [JsonIgnore]
+ public MediaStream VideoStream
{
- Formats = Array.Empty();
- MediaStreams = new List();
- MediaAttachments = Array.Empty();
- RequiredHttpHeaders = new Dictionary();
- SupportsTranscoding = true;
- SupportsDirectStream = true;
- SupportsDirectPlay = true;
- SupportsProbing = true;
+ get
+ {
+ foreach (var i in MediaStreams)
+ {
+ if (i.Type == MediaStreamType.Video)
+ {
+ return i;
+ }
+ }
+
+ return null;
+ }
}
public void InferTotalBitrate(bool force = false)
@@ -134,13 +159,6 @@ namespace MediaBrowser.Model.Dto
}
}
- [JsonIgnore]
- public TranscodeReason[] TranscodeReasons { get; set; }
-
- public int? DefaultAudioStreamIndex { get; set; }
-
- public int? DefaultSubtitleStreamIndex { get; set; }
-
public MediaStream GetDefaultAudioStream(int? defaultIndex)
{
if (defaultIndex.HasValue)
@@ -175,23 +193,6 @@ namespace MediaBrowser.Model.Dto
return null;
}
- [JsonIgnore]
- public MediaStream VideoStream
- {
- get
- {
- foreach (var i in MediaStreams)
- {
- if (i.Type == MediaStreamType.Video)
- {
- return i;
- }
- }
-
- return null;
- }
- }
-
public MediaStream GetMediaStream(MediaStreamType type, int index)
{
foreach (var i in MediaStreams)
diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
index e4f38d6af3..e0e889f7d0 100644
--- a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
+++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
@@ -10,6 +10,15 @@ namespace MediaBrowser.Model.Dto
{
public class MetadataEditorInfo
{
+ public MetadataEditorInfo()
+ {
+ ParentalRatingOptions = Array.Empty();
+ Countries = Array.Empty();
+ Cultures = Array.Empty();
+ ExternalIdInfos = Array.Empty();
+ ContentTypeOptions = Array.Empty();
+ }
+
public ParentalRating[] ParentalRatingOptions { get; set; }
public CountryInfo[] Countries { get; set; }
@@ -21,14 +30,5 @@ namespace MediaBrowser.Model.Dto
public string ContentType { get; set; }
public NameValuePair[] ContentTypeOptions { get; set; }
-
- public MetadataEditorInfo()
- {
- ParentalRatingOptions = Array.Empty();
- Countries = Array.Empty();
- Cultures = Array.Empty();
- ExternalIdInfos = Array.Empty();
- ContentTypeOptions = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Dto/NameGuidPair.cs b/MediaBrowser.Model/Dto/NameGuidPair.cs
new file mode 100644
index 0000000000..71166df979
--- /dev/null
+++ b/MediaBrowser.Model/Dto/NameGuidPair.cs
@@ -0,0 +1,14 @@
+#nullable disable
+#pragma warning disable CS1591
+
+using System;
+
+namespace MediaBrowser.Model.Dto
+{
+ public class NameGuidPair
+ {
+ public string Name { get; set; }
+
+ public Guid Id { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Dto/NameIdPair.cs b/MediaBrowser.Model/Dto/NameIdPair.cs
index 45c2fb35db..7f18b45028 100644
--- a/MediaBrowser.Model/Dto/NameIdPair.cs
+++ b/MediaBrowser.Model/Dto/NameIdPair.cs
@@ -19,11 +19,4 @@ namespace MediaBrowser.Model.Dto
/// The identifier.
public string Id { get; set; }
}
-
- public class NameGuidPair
- {
- public string Name { get; set; }
-
- public Guid Id { get; set; }
- }
}
diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs
index 40222c9dca..256d7b10f1 100644
--- a/MediaBrowser.Model/Dto/UserDto.cs
+++ b/MediaBrowser.Model/Dto/UserDto.cs
@@ -10,6 +10,15 @@ namespace MediaBrowser.Model.Dto
///
public class UserDto : IItemDto, IHasServerId
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UserDto()
+ {
+ Configuration = new UserConfiguration();
+ Policy = new UserPolicy();
+ }
+
///
/// Gets or sets the name.
///
@@ -94,15 +103,6 @@ namespace MediaBrowser.Model.Dto
/// The primary image aspect ratio.
public double? PrimaryImageAspectRatio { get; set; }
- ///
- /// Initializes a new instance of the class.
- ///
- public UserDto()
- {
- Configuration = new UserConfiguration();
- Policy = new UserPolicy();
- }
-
///
public override string ToString()
{
diff --git a/MediaBrowser.Model/Entities/CollectionType.cs b/MediaBrowser.Model/Entities/CollectionType.cs
index 3540387129..60b69d4b01 100644
--- a/MediaBrowser.Model/Entities/CollectionType.cs
+++ b/MediaBrowser.Model/Entities/CollectionType.cs
@@ -24,36 +24,4 @@ namespace MediaBrowser.Model.Entities
public const string Playlists = "playlists";
public const string Folders = "folders";
}
-
- public static class SpecialFolder
- {
- public const string TvShowSeries = "TvShowSeries";
- public const string TvGenres = "TvGenres";
- public const string TvGenre = "TvGenre";
- public const string TvLatest = "TvLatest";
- public const string TvNextUp = "TvNextUp";
- public const string TvResume = "TvResume";
- public const string TvFavoriteSeries = "TvFavoriteSeries";
- public const string TvFavoriteEpisodes = "TvFavoriteEpisodes";
-
- public const string MovieLatest = "MovieLatest";
- public const string MovieResume = "MovieResume";
- public const string MovieMovies = "MovieMovies";
- public const string MovieCollections = "MovieCollections";
- public const string MovieFavorites = "MovieFavorites";
- public const string MovieGenres = "MovieGenres";
- public const string MovieGenre = "MovieGenre";
-
- public const string MusicArtists = "MusicArtists";
- public const string MusicAlbumArtists = "MusicAlbumArtists";
- public const string MusicAlbums = "MusicAlbums";
- public const string MusicGenres = "MusicGenres";
- public const string MusicLatest = "MusicLatest";
- public const string MusicPlaylists = "MusicPlaylists";
- public const string MusicSongs = "MusicSongs";
- public const string MusicFavorites = "MusicFavorites";
- public const string MusicFavoriteArtists = "MusicFavoriteArtists";
- public const string MusicFavoriteAlbums = "MusicFavoriteAlbums";
- public const string MusicFavoriteSongs = "MusicFavoriteSongs";
- }
}
diff --git a/MediaBrowser.Model/Entities/CollectionTypeOptions.cs b/MediaBrowser.Model/Entities/CollectionTypeOptions.cs
new file mode 100644
index 0000000000..e1894d84ad
--- /dev/null
+++ b/MediaBrowser.Model/Entities/CollectionTypeOptions.cs
@@ -0,0 +1,16 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Model.Entities
+{
+ public enum CollectionTypeOptions
+ {
+ Movies = 0,
+ TvShows = 1,
+ Music = 2,
+ MusicVideos = 3,
+ HomeVideos = 4,
+ BoxSets = 5,
+ Books = 6,
+ Mixed = 7
+ }
+}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index d85a8cde95..ade9d7e8dd 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -84,7 +84,7 @@ namespace MediaBrowser.Model.Entities
public string Title { get; set; }
///
- /// Gets or sets the video range.
+ /// Gets the video range.
///
/// The video range.
public string VideoRange
@@ -108,11 +108,11 @@ namespace MediaBrowser.Model.Entities
}
}
- public string localizedUndefined { get; set; }
+ public string LocalizedUndefined { get; set; }
- public string localizedDefault { get; set; }
+ public string LocalizedDefault { get; set; }
- public string localizedForced { get; set; }
+ public string LocalizedForced { get; set; }
public string DisplayTitle
{
@@ -154,7 +154,7 @@ namespace MediaBrowser.Model.Entities
if (IsDefault)
{
- attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault);
+ attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault);
}
if (!string.IsNullOrEmpty(Title))
@@ -229,17 +229,17 @@ namespace MediaBrowser.Model.Entities
}
else
{
- attributes.Add(string.IsNullOrEmpty(localizedUndefined) ? "Und" : localizedUndefined);
+ attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined);
}
if (IsDefault)
{
- attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault);
+ attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault);
}
if (IsForced)
{
- attributes.Add(string.IsNullOrEmpty(localizedForced) ? "Forced" : localizedForced);
+ attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced);
}
if (!string.IsNullOrEmpty(Title))
@@ -266,67 +266,6 @@ namespace MediaBrowser.Model.Entities
}
}
- private string GetResolutionText()
- {
- var i = this;
-
- if (i.Width.HasValue && i.Height.HasValue)
- {
- var width = i.Width.Value;
- var height = i.Height.Value;
-
- if (width >= 3800 || height >= 2000)
- {
- return "4K";
- }
-
- if (width >= 2500)
- {
- if (i.IsInterlaced)
- {
- return "1440i";
- }
-
- return "1440p";
- }
-
- if (width >= 1900 || height >= 1000)
- {
- if (i.IsInterlaced)
- {
- return "1080i";
- }
-
- return "1080p";
- }
-
- if (width >= 1260 || height >= 700)
- {
- if (i.IsInterlaced)
- {
- return "720i";
- }
-
- return "720p";
- }
-
- if (width >= 700 || height >= 440)
- {
-
- if (i.IsInterlaced)
- {
- return "480i";
- }
-
- return "480p";
- }
-
- return "SD";
- }
-
- return null;
- }
-
public string NalLengthSize { get; set; }
///
@@ -487,6 +426,96 @@ namespace MediaBrowser.Model.Entities
}
}
+ ///
+ /// Gets or sets a value indicating whether [supports external stream].
+ ///
+ /// true if [supports external stream]; otherwise, false.
+ public bool SupportsExternalStream { get; set; }
+
+ ///
+ /// Gets or sets the filename.
+ ///
+ /// The filename.
+ public string Path { get; set; }
+
+ ///
+ /// Gets or sets the pixel format.
+ ///
+ /// The pixel format.
+ public string PixelFormat { get; set; }
+
+ ///
+ /// Gets or sets the level.
+ ///
+ /// The level.
+ public double? Level { get; set; }
+
+ ///
+ /// Gets or sets whether this instance is anamorphic.
+ ///
+ /// true if this instance is anamorphic; otherwise, false.
+ public bool? IsAnamorphic { get; set; }
+
+ private string GetResolutionText()
+ {
+ var i = this;
+
+ if (i.Width.HasValue && i.Height.HasValue)
+ {
+ var width = i.Width.Value;
+ var height = i.Height.Value;
+
+ if (width >= 3800 || height >= 2000)
+ {
+ return "4K";
+ }
+
+ if (width >= 2500)
+ {
+ if (i.IsInterlaced)
+ {
+ return "1440i";
+ }
+
+ return "1440p";
+ }
+
+ if (width >= 1900 || height >= 1000)
+ {
+ if (i.IsInterlaced)
+ {
+ return "1080i";
+ }
+
+ return "1080p";
+ }
+
+ if (width >= 1260 || height >= 700)
+ {
+ if (i.IsInterlaced)
+ {
+ return "720i";
+ }
+
+ return "720p";
+ }
+
+ if (width >= 700 || height >= 440)
+ {
+ if (i.IsInterlaced)
+ {
+ return "480i";
+ }
+
+ return "480p";
+ }
+
+ return "SD";
+ }
+
+ return null;
+ }
+
public static bool IsTextFormat(string format)
{
string codec = format ?? string.Empty;
@@ -533,35 +562,5 @@ namespace MediaBrowser.Model.Entities
return true;
}
-
- ///
- /// Gets or sets a value indicating whether [supports external stream].
- ///
- /// true if [supports external stream]; otherwise, false.
- public bool SupportsExternalStream { get; set; }
-
- ///
- /// Gets or sets the filename.
- ///
- /// The filename.
- public string Path { get; set; }
-
- ///
- /// Gets or sets the pixel format.
- ///
- /// The pixel format.
- public string PixelFormat { get; set; }
-
- ///
- /// Gets or sets the level.
- ///
- /// The level.
- public double? Level { get; set; }
-
- ///
- /// Gets a value indicating whether this instance is anamorphic.
- ///
- /// true if this instance is anamorphic; otherwise, false.
- public bool? IsAnamorphic { get; set; }
}
}
diff --git a/MediaBrowser.Model/Entities/PackageReviewInfo.cs b/MediaBrowser.Model/Entities/PackageReviewInfo.cs
deleted file mode 100644
index 5b22b34ace..0000000000
--- a/MediaBrowser.Model/Entities/PackageReviewInfo.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Model.Entities
-{
- public class PackageReviewInfo
- {
- ///
- /// Gets or sets the package id (database key) for this review.
- ///
- public int id { get; set; }
-
- ///
- /// Gets or sets the rating value.
- ///
- public int rating { get; set; }
-
- ///
- /// Gets or sets whether or not this review recommends this item.
- ///
- public bool recommend { get; set; }
-
- ///
- /// Gets or sets a short description of the review.
- ///
- public string title { get; set; }
-
- ///
- /// Gets or sets the full review.
- ///
- public string review { get; set; }
-
- ///
- /// Gets or sets the time of review.
- ///
- public DateTime timestamp { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
index 98097477c6..4aff6e3a4f 100644
--- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
+++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
namespace MediaBrowser.Model.Entities
{
@@ -9,14 +10,26 @@ namespace MediaBrowser.Model.Entities
public static class ProviderIdsExtensions
{
///
- /// Determines whether [has provider identifier] [the specified instance].
+ /// Gets a provider id.
///
/// The instance.
- /// The provider.
- /// true if [has provider identifier] [the specified instance]; otherwise, false.
- public static bool HasProviderId(this IHasProviderIds instance, MetadataProvider provider)
+ /// The name.
+ /// The provider id.
+ /// true if a provider id with the given name was found; otherwise false.
+ public static bool TryGetProviderId(this IHasProviderIds instance, string name, [MaybeNullWhen(false)] out string id)
{
- return !string.IsNullOrEmpty(instance.GetProviderId(provider.ToString()));
+ if (instance == null)
+ {
+ throw new ArgumentNullException(nameof(instance));
+ }
+
+ if (instance.ProviderIds == null)
+ {
+ id = null;
+ return false;
+ }
+
+ return instance.ProviderIds.TryGetValue(name, out id);
}
///
@@ -24,10 +37,11 @@ namespace MediaBrowser.Model.Entities
///
/// The instance.
/// The provider.
- /// System.String.
- public static string? GetProviderId(this IHasProviderIds instance, MetadataProvider provider)
+ /// The provider id.
+ /// true if a provider id with the given name was found; otherwise false.
+ public static bool TryGetProviderId(this IHasProviderIds instance, MetadataProvider provider, [MaybeNullWhen(false)] out string id)
{
- return instance.GetProviderId(provider.ToString());
+ return instance.TryGetProviderId(provider.ToString(), out id);
}
///
@@ -38,18 +52,19 @@ namespace MediaBrowser.Model.Entities
/// System.String.
public static string? GetProviderId(this IHasProviderIds instance, string name)
{
- if (instance == null)
- {
- throw new ArgumentNullException(nameof(instance));
- }
-
- if (instance.ProviderIds == null)
- {
- return null;
- }
+ instance.TryGetProviderId(name, out string? id);
+ return id;
+ }
- instance.ProviderIds.TryGetValue(name, out string? id);
- return string.IsNullOrEmpty(id) ? null : id;
+ ///
+ /// Gets a provider id.
+ ///
+ /// The instance.
+ /// The provider.
+ /// System.String.
+ public static string? GetProviderId(this IHasProviderIds instance, MetadataProvider provider)
+ {
+ return instance.GetProviderId(provider.ToString());
}
///
@@ -68,13 +83,7 @@ namespace MediaBrowser.Model.Entities
// If it's null remove the key from the dictionary
if (string.IsNullOrEmpty(value))
{
- if (instance.ProviderIds != null)
- {
- if (instance.ProviderIds.ContainsKey(name))
- {
- instance.ProviderIds.Remove(name);
- }
- }
+ instance.ProviderIds?.Remove(name);
}
else
{
diff --git a/MediaBrowser.Model/Entities/SpecialFolder.cs b/MediaBrowser.Model/Entities/SpecialFolder.cs
new file mode 100644
index 0000000000..2250c5dffb
--- /dev/null
+++ b/MediaBrowser.Model/Entities/SpecialFolder.cs
@@ -0,0 +1,36 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Model.Entities
+{
+ public static class SpecialFolder
+ {
+ public const string TvShowSeries = "TvShowSeries";
+ public const string TvGenres = "TvGenres";
+ public const string TvGenre = "TvGenre";
+ public const string TvLatest = "TvLatest";
+ public const string TvNextUp = "TvNextUp";
+ public const string TvResume = "TvResume";
+ public const string TvFavoriteSeries = "TvFavoriteSeries";
+ public const string TvFavoriteEpisodes = "TvFavoriteEpisodes";
+
+ public const string MovieLatest = "MovieLatest";
+ public const string MovieResume = "MovieResume";
+ public const string MovieMovies = "MovieMovies";
+ public const string MovieCollections = "MovieCollections";
+ public const string MovieFavorites = "MovieFavorites";
+ public const string MovieGenres = "MovieGenres";
+ public const string MovieGenre = "MovieGenre";
+
+ public const string MusicArtists = "MusicArtists";
+ public const string MusicAlbumArtists = "MusicAlbumArtists";
+ public const string MusicAlbums = "MusicAlbums";
+ public const string MusicGenres = "MusicGenres";
+ public const string MusicLatest = "MusicLatest";
+ public const string MusicPlaylists = "MusicPlaylists";
+ public const string MusicSongs = "MusicSongs";
+ public const string MusicFavorites = "MusicFavorites";
+ public const string MusicFavoriteArtists = "MusicFavoriteArtists";
+ public const string MusicFavoriteAlbums = "MusicFavoriteAlbums";
+ public const string MusicFavoriteSongs = "MusicFavoriteSongs";
+ }
+}
diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
index f2bc6f25e0..ea3df37265 100644
--- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
+++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
@@ -11,6 +11,14 @@ namespace MediaBrowser.Model.Entities
///
public class VirtualFolderInfo
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public VirtualFolderInfo()
+ {
+ Locations = Array.Empty();
+ }
+
///
/// Gets or sets the name.
///
@@ -27,18 +35,10 @@ namespace MediaBrowser.Model.Entities
/// Gets or sets the type of the collection.
///
/// The type of the collection.
- public string CollectionType { get; set; }
+ public CollectionTypeOptions? CollectionType { get; set; }
public LibraryOptions LibraryOptions { get; set; }
- ///
- /// Initializes a new instance of the class.
- ///
- public VirtualFolderInfo()
- {
- Locations = Array.Empty();
- }
-
///
/// Gets or sets the item identifier.
///
diff --git a/MediaBrowser.Model/Globalization/CultureDto.cs b/MediaBrowser.Model/Globalization/CultureDto.cs
index 6af4a872ce..5246f87d92 100644
--- a/MediaBrowser.Model/Globalization/CultureDto.cs
+++ b/MediaBrowser.Model/Globalization/CultureDto.cs
@@ -10,6 +10,11 @@ namespace MediaBrowser.Model.Globalization
///
public class CultureDto
{
+ public CultureDto()
+ {
+ ThreeLetterISOLanguageNames = Array.Empty();
+ }
+
///
/// Gets or sets the name.
///
@@ -29,7 +34,7 @@ namespace MediaBrowser.Model.Globalization
public string TwoLetterISOLanguageName { get; set; }
///
- /// Gets or sets the name of the three letter ISO language.
+ /// Gets the name of the three letter ISO language.
///
/// The name of the three letter ISO language.
public string ThreeLetterISOLanguageName
@@ -47,10 +52,5 @@ namespace MediaBrowser.Model.Globalization
}
public string[] ThreeLetterISOLanguageNames { get; set; }
-
- public CultureDto()
- {
- ThreeLetterISOLanguageNames = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs
index dc65497876..ef08ecec66 100644
--- a/MediaBrowser.Model/IO/IFileSystem.cs
+++ b/MediaBrowser.Model/IO/IFileSystem.cs
@@ -155,13 +155,16 @@ namespace MediaBrowser.Model.IO
/// Gets the directories.
///
/// The path.
- /// if set to true [recursive].
- /// IEnumerable<DirectoryInfo>.
+ /// If set to true also searches in subdirectiories.
+ /// All found directories.
IEnumerable GetDirectories(string path, bool recursive = false);
///
/// Gets the files.
///
+ /// The path in which to search.
+ /// If set to true also searches in subdirectiories.
+ /// All found files.
IEnumerable GetFiles(string path, bool recursive = false);
IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive);
diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
index 07e76d9600..c6de4c1ab8 100644
--- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
@@ -9,7 +9,7 @@ namespace MediaBrowser.Model.LiveTv
public class BaseTimerInfoDto : IHasServerId
{
///
- /// Id of the recording.
+ /// Gets or sets the Id of the recording.
///
public string Id { get; set; }
@@ -28,7 +28,7 @@ namespace MediaBrowser.Model.LiveTv
public string ExternalId { get; set; }
///
- /// ChannelId of the recording.
+ /// Gets or sets the channel id of the recording.
///
public Guid ChannelId { get; set; }
@@ -39,7 +39,7 @@ namespace MediaBrowser.Model.LiveTv
public string ExternalChannelId { get; set; }
///
- /// ChannelName of the recording.
+ /// Gets or sets the channel name of the recording.
///
public string ChannelName { get; set; }
@@ -58,22 +58,22 @@ namespace MediaBrowser.Model.LiveTv
public string ExternalProgramId { get; set; }
///
- /// Name of the recording.
+ /// Gets or sets the name of the recording.
///
public string Name { get; set; }
///
- /// Description of the recording.
+ /// Gets or sets the description of the recording.
///
public string Overview { get; set; }
///
- /// The start date of the recording, in UTC.
+ /// Gets or sets the start date of the recording, in UTC.
///
public DateTime StartDate { get; set; }
///
- /// The end date of the recording, in UTC.
+ /// Gets or sets the end date of the recording, in UTC.
///
public DateTime EndDate { get; set; }
@@ -108,7 +108,7 @@ namespace MediaBrowser.Model.LiveTv
public bool IsPrePaddingRequired { get; set; }
///
- /// If the item does not have any backdrops, this will hold the Id of the Parent that has one.
+ /// Gets or sets the Id of the Parent that has a backdrop if the item does not have one.
///
/// The parent backdrop item id.
public string ParentBackdropItemId { get; set; }
diff --git a/MediaBrowser.Model/LiveTv/ListingsProviderInfo.cs b/MediaBrowser.Model/LiveTv/ListingsProviderInfo.cs
new file mode 100644
index 0000000000..082daeb51b
--- /dev/null
+++ b/MediaBrowser.Model/LiveTv/ListingsProviderInfo.cs
@@ -0,0 +1,58 @@
+#nullable disable
+#pragma warning disable CS1591
+
+using System;
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Model.LiveTv
+{
+ public class ListingsProviderInfo
+ {
+ public ListingsProviderInfo()
+ {
+ NewsCategories = new[] { "news", "journalism", "documentary", "current affairs" };
+ SportsCategories = new[] { "sports", "basketball", "baseball", "football" };
+ KidsCategories = new[] { "kids", "family", "children", "childrens", "disney" };
+ MovieCategories = new[] { "movie" };
+ EnabledTuners = Array.Empty();
+ EnableAllTuners = true;
+ ChannelMappings = Array.Empty();
+ }
+
+ public string Id { get; set; }
+
+ public string Type { get; set; }
+
+ public string Username { get; set; }
+
+ public string Password { get; set; }
+
+ public string ListingsId { get; set; }
+
+ public string ZipCode { get; set; }
+
+ public string Country { get; set; }
+
+ public string Path { get; set; }
+
+ public string[] EnabledTuners { get; set; }
+
+ public bool EnableAllTuners { get; set; }
+
+ public string[] NewsCategories { get; set; }
+
+ public string[] SportsCategories { get; set; }
+
+ public string[] KidsCategories { get; set; }
+
+ public string[] MovieCategories { get; set; }
+
+ public NameValuePair[] ChannelMappings { get; set; }
+
+ public string MoviePrefix { get; set; }
+
+ public string PreferredLanguage { get; set; }
+
+ public string UserAgent { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
index bcba344cc5..673d97a9ed 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
@@ -11,6 +11,12 @@ namespace MediaBrowser.Model.LiveTv
///
public class LiveTvChannelQuery
{
+ public LiveTvChannelQuery()
+ {
+ EnableUserData = true;
+ SortBy = Array.Empty();
+ }
+
///
/// Gets or sets the type of the channel.
///
@@ -48,13 +54,13 @@ namespace MediaBrowser.Model.LiveTv
public Guid UserId { get; set; }
///
- /// Skips over a given number of items within the results. Use for paging.
+ /// Gets or sets the start index. Used for paging.
///
/// The start index.
public int? StartIndex { get; set; }
///
- /// The maximum number of items to return.
+ /// Gets or sets the maximum number of items to return.
///
/// The limit.
public int? Limit { get; set; }
@@ -68,15 +74,15 @@ namespace MediaBrowser.Model.LiveTv
public bool EnableUserData { get; set; }
///
- /// Used to specific whether to return news or not.
+ /// Gets or sets a value whether to return news or not.
///
- /// If set to null, all programs will be returned
+ /// If set to null, all programs will be returned.
public bool? IsNews { get; set; }
///
- /// Used to specific whether to return movies or not.
+ /// Gets or sets a value whether to return movies or not.
///
- /// If set to null, all programs will be returned
+ /// If set to null, all programs will be returned.
public bool? IsMovie { get; set; }
///
@@ -96,15 +102,9 @@ namespace MediaBrowser.Model.LiveTv
public string[] SortBy { get; set; }
///
- /// The sort order to return results with.
+ /// Gets or sets the sort order to return results with.
///
/// The sort order.
public SortOrder? SortOrder { get; set; }
-
- public LiveTvChannelQuery()
- {
- EnableUserData = true;
- SortBy = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
index 789de3198a..4cece941cf 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
@@ -2,12 +2,19 @@
#pragma warning disable CS1591
using System;
-using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.LiveTv
{
public class LiveTvOptions
{
+ public LiveTvOptions()
+ {
+ TunerHosts = Array.Empty();
+ ListingProviders = Array.Empty();
+ MediaLocationsCreated = Array.Empty();
+ RecordingPostProcessorArguments = "\"{path}\"";
+ }
+
public int? GuideDays { get; set; }
public string RecordingPath { get; set; }
@@ -33,93 +40,5 @@ namespace MediaBrowser.Model.LiveTv
public string RecordingPostProcessor { get; set; }
public string RecordingPostProcessorArguments { get; set; }
-
- public LiveTvOptions()
- {
- TunerHosts = Array.Empty();
- ListingProviders = Array.Empty();
- MediaLocationsCreated = Array.Empty();
- RecordingPostProcessorArguments = "\"{path}\"";
- }
- }
-
- public class TunerHostInfo
- {
- public string Id { get; set; }
-
- public string Url { get; set; }
-
- public string Type { get; set; }
-
- public string DeviceId { get; set; }
-
- public string FriendlyName { get; set; }
-
- public bool ImportFavoritesOnly { get; set; }
-
- public bool AllowHWTranscoding { get; set; }
-
- public bool EnableStreamLooping { get; set; }
-
- public string Source { get; set; }
-
- public int TunerCount { get; set; }
-
- public string UserAgent { get; set; }
-
- public TunerHostInfo()
- {
- AllowHWTranscoding = true;
- }
- }
-
- public class ListingsProviderInfo
- {
- public string Id { get; set; }
-
- public string Type { get; set; }
-
- public string Username { get; set; }
-
- public string Password { get; set; }
-
- public string ListingsId { get; set; }
-
- public string ZipCode { get; set; }
-
- public string Country { get; set; }
-
- public string Path { get; set; }
-
- public string[] EnabledTuners { get; set; }
-
- public bool EnableAllTuners { get; set; }
-
- public string[] NewsCategories { get; set; }
-
- public string[] SportsCategories { get; set; }
-
- public string[] KidsCategories { get; set; }
-
- public string[] MovieCategories { get; set; }
-
- public NameValuePair[] ChannelMappings { get; set; }
-
- public string MoviePrefix { get; set; }
-
- public string PreferredLanguage { get; set; }
-
- public string UserAgent { get; set; }
-
- public ListingsProviderInfo()
- {
- NewsCategories = new[] { "news", "journalism", "documentary", "current affairs" };
- SportsCategories = new[] { "sports", "basketball", "baseball", "football" };
- KidsCategories = new[] { "kids", "family", "children", "childrens", "disney" };
- MovieCategories = new[] { "movie" };
- EnabledTuners = Array.Empty();
- EnableAllTuners = true;
- ChannelMappings = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs b/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs
index 856f638c5c..ef5c5d2f31 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs
@@ -10,6 +10,11 @@ namespace MediaBrowser.Model.LiveTv
///
public class LiveTvServiceInfo
{
+ public LiveTvServiceInfo()
+ {
+ Tuners = Array.Empty();
+ }
+
///
/// Gets or sets the name.
///
@@ -53,10 +58,5 @@ namespace MediaBrowser.Model.LiveTv
public bool IsVisible { get; set; }
public string[] Tuners { get; set; }
-
- public LiveTvServiceInfo()
- {
- Tuners = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index 69e7db4708..99bb1603c3 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -12,6 +12,11 @@ namespace MediaBrowser.Model.LiveTv
///
public class RecordingQuery
{
+ public RecordingQuery()
+ {
+ EnableTotalRecordCount = true;
+ }
+
///
/// Gets or sets the channel identifier.
///
@@ -31,13 +36,13 @@ namespace MediaBrowser.Model.LiveTv
public string Id { get; set; }
///
- /// Skips over a given number of items within the results. Use for paging.
+ /// Gets or sets the start index. Use for paging.
///
/// The start index.
public int? StartIndex { get; set; }
///
- /// The maximum number of items to return.
+ /// Gets or sets the maximum number of items to return.
///
/// The limit.
public int? Limit { get; set; }
@@ -61,7 +66,7 @@ namespace MediaBrowser.Model.LiveTv
public string SeriesTimerId { get; set; }
///
- /// Fields to return within the items, in addition to basic information.
+ /// Gets or sets the fields to return within the items, in addition to basic information.
///
/// The fields.
public ItemFields[] Fields { get; set; }
@@ -85,10 +90,5 @@ namespace MediaBrowser.Model.LiveTv
public ImageType[] EnableImageTypes { get; set; }
public bool EnableTotalRecordCount { get; set; }
-
- public RecordingQuery()
- {
- EnableTotalRecordCount = true;
- }
}
}
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
index 90422d19c3..b26f5f45fe 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
@@ -7,6 +7,14 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.LiveTv
{
+ public enum KeepUntil
+ {
+ UntilDeleted,
+ UntilSpaceNeeded,
+ UntilWatched,
+ UntilDate
+ }
+
///
/// Class SeriesTimerInfoDto.
///
@@ -83,12 +91,4 @@ namespace MediaBrowser.Model.LiveTv
/// The parent primary image tag.
public string ParentPrimaryImageTag { get; set; }
}
-
- public enum KeepUntil
- {
- UntilDeleted,
- UntilSpaceNeeded,
- UntilWatched,
- UntilDate
- }
}
diff --git a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
new file mode 100644
index 0000000000..7d4bbb2d07
--- /dev/null
+++ b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
@@ -0,0 +1,38 @@
+#nullable disable
+#pragma warning disable CS1591
+
+using System;
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Model.LiveTv
+{
+ public class TunerHostInfo
+ {
+ public TunerHostInfo()
+ {
+ AllowHWTranscoding = true;
+ }
+
+ public string Id { get; set; }
+
+ public string Url { get; set; }
+
+ public string Type { get; set; }
+
+ public string DeviceId { get; set; }
+
+ public string FriendlyName { get; set; }
+
+ public bool ImportFavoritesOnly { get; set; }
+
+ public bool AllowHWTranscoding { get; set; }
+
+ public bool EnableStreamLooping { get; set; }
+
+ public string Source { get; set; }
+
+ public int TunerCount { get; set; }
+
+ public string UserAgent { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index c534286510..b6d9169139 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -17,7 +17,7 @@
net5.0
false
true
- true
+ true
enable
latest
true
@@ -44,7 +44,7 @@
-
+
diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
index 472055c22c..a268a4fa66 100644
--- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs
+++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
@@ -10,6 +10,17 @@ namespace MediaBrowser.Model.MediaInfo
{
public class MediaInfo : MediaSourceInfo, IHasProviderIds
{
+ public MediaInfo()
+ {
+ Chapters = Array.Empty();
+ Artists = Array.Empty();
+ AlbumArtists = Array.Empty();
+ Studios = Array.Empty();
+ Genres = Array.Empty();
+ People = Array.Empty();
+ ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+
public ChapterInfo[] Chapters { get; set; }
///
@@ -69,16 +80,5 @@ namespace MediaBrowser.Model.MediaInfo
///
/// The overview.
public string Overview { get; set; }
-
- public MediaInfo()
- {
- Chapters = Array.Empty();
- Artists = Array.Empty();
- AlbumArtists = Array.Empty();
- Studios = Array.Empty();
- Genres = Array.Empty();
- People = Array.Empty();
- ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
- }
}
}
diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
index 3216856777..ecd9b8834e 100644
--- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
+++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
@@ -8,6 +8,17 @@ namespace MediaBrowser.Model.MediaInfo
{
public class PlaybackInfoRequest
{
+ public PlaybackInfoRequest()
+ {
+ EnableDirectPlay = true;
+ EnableDirectStream = true;
+ EnableTranscoding = true;
+ AllowVideoStreamCopy = true;
+ AllowAudioStreamCopy = true;
+ IsPlayback = true;
+ DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http };
+ }
+
public Guid Id { get; set; }
public Guid UserId { get; set; }
@@ -43,16 +54,5 @@ namespace MediaBrowser.Model.MediaInfo
public bool AutoOpenLiveStream { get; set; }
public MediaProtocol[] DirectPlayProtocols { get; set; }
-
- public PlaybackInfoRequest()
- {
- EnableDirectPlay = true;
- EnableDirectStream = true;
- EnableTranscoding = true;
- AllowVideoStreamCopy = true;
- AllowAudioStreamCopy = true;
- IsPlayback = true;
- DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http };
- }
}
}
diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs
index 2733501822..32971b108f 100644
--- a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs
+++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs
@@ -10,6 +10,14 @@ namespace MediaBrowser.Model.MediaInfo
///
public class PlaybackInfoResponse
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PlaybackInfoResponse()
+ {
+ MediaSources = Array.Empty();
+ }
+
///
/// Gets or sets the media sources.
///
@@ -27,13 +35,5 @@ namespace MediaBrowser.Model.MediaInfo
///
/// The error code.
public PlaybackErrorCode? ErrorCode { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public PlaybackInfoResponse()
- {
- MediaSources = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs b/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs
index 72bb3d9c63..88b00c166d 100644
--- a/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs
+++ b/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs
@@ -1,10 +1,15 @@
-#nullable disable
#pragma warning disable CS1591
namespace MediaBrowser.Model.MediaInfo
{
public class SubtitleTrackEvent
{
+ public SubtitleTrackEvent(string id, string text)
+ {
+ Id = id;
+ Text = text;
+ }
+
public string Id { get; set; }
public string Text { get; set; }
diff --git a/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs b/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs
index 37f5c55da6..b3db57b6d9 100644
--- a/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs
+++ b/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs
@@ -1,3 +1,4 @@
+#nullable enable
#pragma warning disable CS1591
using System;
@@ -7,11 +8,11 @@ namespace MediaBrowser.Model.MediaInfo
{
public class SubtitleTrackInfo
{
- public IReadOnlyList TrackEvents { get; set; }
-
public SubtitleTrackInfo()
{
TrackEvents = Array.Empty();
}
+
+ public IReadOnlyList TrackEvents { get; set; }
}
}
diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs
index 5b6ed92df1..3de41d565a 100644
--- a/MediaBrowser.Model/Net/ISocket.cs
+++ b/MediaBrowser.Model/Net/ISocket.cs
@@ -23,6 +23,12 @@ namespace MediaBrowser.Model.Net
///
/// Sends a UDP message to a particular end point (uni or multicast).
///
+ /// An array of type that contains the data to send.
+ /// The zero-based position in buffer at which to begin sending data.
+ /// The number of bytes to send.
+ /// An that represents the remote device.
+ /// The cancellation token to cancel operation.
+ /// The task object representing the asynchronous operation.
Task SendToAsync(byte[] buffer, int offset, int bytes, IPEndPoint endPoint, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs
index 363abefc19..1527ef595c 100644
--- a/MediaBrowser.Model/Net/ISocketFactory.cs
+++ b/MediaBrowser.Model/Net/ISocketFactory.cs
@@ -14,6 +14,9 @@ namespace MediaBrowser.Model.Net
///
/// Creates a new unicast socket using the specified local port number.
///
+ /// The local IP address to bind to.
+ /// The local port to bind to.
+ /// A new unicast socket using the specified local port number.
ISocket CreateSsdpUdpSocket(IPAddress localIp, int localPort);
///
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 902db1e9e5..96f5ab51ae 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -91,9 +91,9 @@ namespace MediaBrowser.Model.Net
{ ".webp", "image/webp" },
// Type font
- { ".ttf" , "font/ttf" },
- { ".woff" , "font/woff" },
- { ".woff2" , "font/woff2" },
+ { ".ttf", "font/ttf" },
+ { ".woff", "font/woff" },
+ { ".woff2", "font/woff2" },
// Type text
{ ".ass", "text/x-ssa" },
@@ -168,14 +168,17 @@ namespace MediaBrowser.Model.Net
///
/// Gets the type of the MIME.
///
- public static string? GetMimeType(string path, bool enableStreamDefault)
+ /// The filename to find the MIME type of.
+ /// Whether of not to return a default value if no fitting MIME type is found.
+ /// The worrect MIME type for the given filename, or `null` if it wasn't found and is false.
+ public static string? GetMimeType(string filename, bool enableStreamDefault)
{
- if (path.Length == 0)
+ if (filename.Length == 0)
{
- throw new ArgumentException("String can't be empty.", nameof(path));
+ throw new ArgumentException("String can't be empty.", nameof(filename));
}
- var ext = Path.GetExtension(path);
+ var ext = Path.GetExtension(filename);
if (_mimeTypeLookup.TryGetValue(ext, out string? result))
{
@@ -210,9 +213,9 @@ namespace MediaBrowser.Model.Net
return enableStreamDefault ? "application/octet-stream" : null;
}
- public static string? ToExtension(string? mimeType)
+ public static string? ToExtension(string mimeType)
{
- if (string.IsNullOrEmpty(mimeType))
+ if (mimeType.Length == 0)
{
throw new ArgumentException("String can't be empty.", nameof(mimeType));
}
diff --git a/MediaBrowser.Model/Net/NetworkShare.cs b/MediaBrowser.Model/Net/NetworkShare.cs
deleted file mode 100644
index 6344cbe21e..0000000000
--- a/MediaBrowser.Model/Net/NetworkShare.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Net
-{
- public class NetworkShare
- {
- ///
- /// The name of the computer that this share belongs to.
- ///
- public string Server { get; set; }
-
- ///
- /// Share name.
- ///
- public string Name { get; set; }
-
- ///
- /// Local path.
- ///
- public string Path { get; set; }
-
- ///
- /// Share type.
- ///
- public NetworkShareType ShareType { get; set; }
-
- ///
- /// Comment.
- ///
- public string Remark { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Net/SocketReceiveResult.cs b/MediaBrowser.Model/Net/SocketReceiveResult.cs
index 54139fe9c5..1524786ea7 100644
--- a/MediaBrowser.Model/Net/SocketReceiveResult.cs
+++ b/MediaBrowser.Model/Net/SocketReceiveResult.cs
@@ -20,12 +20,12 @@ namespace MediaBrowser.Model.Net
public int ReceivedBytes { get; set; }
///
- /// The the data was received from.
+ /// Gets or sets the the data was received from.
///
public IPEndPoint RemoteEndPoint { get; set; }
///
- /// The local .
+ /// Gets or sets the local .
///
public IPAddress LocalIPAddress { get; set; }
}
diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs
index bffbbe612d..b00158cb33 100644
--- a/MediaBrowser.Model/Net/WebSocketMessage.cs
+++ b/MediaBrowser.Model/Net/WebSocketMessage.cs
@@ -9,7 +9,7 @@ namespace MediaBrowser.Model.Net
///
/// Class WebSocketMessage.
///
- ///
+ /// The type of the data.
public class WebSocketMessage
{
///
diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs
index 239a3777e1..94bb5d6e35 100644
--- a/MediaBrowser.Model/Notifications/NotificationOptions.cs
+++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs
@@ -2,18 +2,16 @@
#pragma warning disable CS1591
using System;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Model.Extensions;
using System.Linq;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Model.Notifications
{
public class NotificationOptions
{
- public NotificationOption[] Options { get; set; }
-
public NotificationOptions()
{
Options = new[]
@@ -71,6 +69,8 @@ namespace MediaBrowser.Model.Notifications
};
}
+ public NotificationOption[] Options { get; set; }
+
public NotificationOption GetOptions(string type)
{
foreach (NotificationOption i in Options)
@@ -104,7 +104,7 @@ namespace MediaBrowser.Model.Notifications
NotificationOption opt = GetOptions(type);
return opt != null && opt.Enabled &&
- !opt.DisabledMonitorUsers.Contains(userId.ToString(""), StringComparer.OrdinalIgnoreCase);
+ !opt.DisabledMonitorUsers.Contains(userId.ToString(string.Empty), StringComparer.OrdinalIgnoreCase);
}
public bool IsEnabledToSendToUser(string type, string userId, User user)
diff --git a/MediaBrowser.Model/Notifications/NotificationRequest.cs b/MediaBrowser.Model/Notifications/NotificationRequest.cs
index febc2bc099..622c50cd88 100644
--- a/MediaBrowser.Model/Notifications/NotificationRequest.cs
+++ b/MediaBrowser.Model/Notifications/NotificationRequest.cs
@@ -7,6 +7,12 @@ namespace MediaBrowser.Model.Notifications
{
public class NotificationRequest
{
+ public NotificationRequest()
+ {
+ UserIds = Array.Empty();
+ Date = DateTime.UtcNow;
+ }
+
public string Name { get; set; }
public string Description { get; set; }
@@ -20,16 +26,10 @@ namespace MediaBrowser.Model.Notifications
public DateTime Date { get; set; }
///
- /// The corresponding type name used in configuration. Not for display.
+ /// Gets or sets the corresponding type name used in configuration. Not for display.
///
public string NotificationType { get; set; }
public SendToUserType? SendToUserMode { get; set; }
-
- public NotificationRequest()
- {
- UserIds = Array.Empty();
- Date = DateTime.UtcNow;
- }
}
}
diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs
index afe95e6eee..0ea3e96cae 100644
--- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs
+++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs
@@ -6,11 +6,11 @@ namespace MediaBrowser.Model.Providers
public class ExternalIdInfo
{
///
- /// Represents the external id information for serialization to the client.
+ /// Initializes a new instance of the class.
///
/// Name of the external id provider (IE: IMDB, MusicBrainz, etc).
/// Key for this id. This key should be unique across all providers.
- /// Specific media type for this id
+ /// Specific media type for this id.
/// URL format string.
public ExternalIdInfo(string name, string key, ExternalIdMediaType? type, string urlFormatString)
{
diff --git a/MediaBrowser.Model/Providers/RemoteImageInfo.cs b/MediaBrowser.Model/Providers/RemoteImageInfo.cs
index fb25999e0a..48207d2d41 100644
--- a/MediaBrowser.Model/Providers/RemoteImageInfo.cs
+++ b/MediaBrowser.Model/Providers/RemoteImageInfo.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Model.Providers
public string Url { get; set; }
///
- /// Gets a url used for previewing a smaller version.
+ /// Gets or sets a url used for previewing a smaller version.
///
public string ThumbnailUrl { get; set; }
diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs
index 5702c460b0..6ea1e14862 100644
--- a/MediaBrowser.Model/Providers/SubtitleOptions.cs
+++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs
@@ -7,6 +7,14 @@ namespace MediaBrowser.Model.Providers
{
public class SubtitleOptions
{
+ public SubtitleOptions()
+ {
+ DownloadLanguages = Array.Empty();
+
+ SkipIfAudioTrackMatches = true;
+ RequirePerfectMatch = true;
+ }
+
public bool SkipIfEmbeddedSubtitlesPresent { get; set; }
public bool SkipIfAudioTrackMatches { get; set; }
@@ -24,13 +32,5 @@ namespace MediaBrowser.Model.Providers
public bool IsOpenSubtitleVipAccount { get; set; }
public bool RequirePerfectMatch { get; set; }
-
- public SubtitleOptions()
- {
- DownloadLanguages = Array.Empty();
-
- SkipIfAudioTrackMatches = true;
- RequirePerfectMatch = true;
- }
}
}
diff --git a/MediaBrowser.Model/Querying/EpisodeQuery.cs b/MediaBrowser.Model/Querying/EpisodeQuery.cs
index 13b1a0dcbf..56a7f33201 100644
--- a/MediaBrowser.Model/Querying/EpisodeQuery.cs
+++ b/MediaBrowser.Model/Querying/EpisodeQuery.cs
@@ -7,6 +7,11 @@ namespace MediaBrowser.Model.Querying
{
public class EpisodeQuery
{
+ public EpisodeQuery()
+ {
+ Fields = Array.Empty();
+ }
+
///
/// Gets or sets the user identifier.
///
@@ -66,10 +71,5 @@ namespace MediaBrowser.Model.Querying
///
/// The start item identifier.
public string StartItemId { get; set; }
-
- public EpisodeQuery()
- {
- Fields = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Querying/LatestItemsQuery.cs b/MediaBrowser.Model/Querying/LatestItemsQuery.cs
index 7954ef4b43..f555ffb36d 100644
--- a/MediaBrowser.Model/Querying/LatestItemsQuery.cs
+++ b/MediaBrowser.Model/Querying/LatestItemsQuery.cs
@@ -14,31 +14,32 @@ namespace MediaBrowser.Model.Querying
}
///
- /// The user to localize search results for.
+ /// Gets or sets the user to localize search results for.
///
/// The user id.
public Guid UserId { get; set; }
///
+ /// Gets or sets the parent id.
/// Specify this to localize the search to a specific item or folder. Omit to use the root.
///
/// The parent id.
public Guid ParentId { get; set; }
///
- /// Skips over a given number of items within the results. Use for paging.
+ /// Gets or sets the start index. Used for paging.
///
/// The start index.
public int? StartIndex { get; set; }
///
- /// The maximum number of items to return.
+ /// Gets or sets the maximum number of items to return.
///
/// The limit.
public int? Limit { get; set; }
///
- /// Fields to return within the items, in addition to basic information.
+ /// Gets or sets the fields to return within the items, in addition to basic information.
///
/// The fields.
public ItemFields[] Fields { get; set; }
diff --git a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs
index 1c8875890a..b800f5de5f 100644
--- a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs
+++ b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs
@@ -7,6 +7,13 @@ namespace MediaBrowser.Model.Querying
{
public class MovieRecommendationQuery
{
+ public MovieRecommendationQuery()
+ {
+ ItemLimit = 10;
+ CategoryLimit = 6;
+ Fields = Array.Empty();
+ }
+
///
/// Gets or sets the user identifier.
///
@@ -36,12 +43,5 @@ namespace MediaBrowser.Model.Querying
///
/// The fields.
public ItemFields[] Fields { get; set; }
-
- public MovieRecommendationQuery()
- {
- ItemLimit = 10;
- CategoryLimit = 6;
- Fields = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs
index 001d0623cc..0555afc00d 100644
--- a/MediaBrowser.Model/Querying/NextUpQuery.cs
+++ b/MediaBrowser.Model/Querying/NextUpQuery.cs
@@ -8,6 +8,13 @@ namespace MediaBrowser.Model.Querying
{
public class NextUpQuery
{
+ public NextUpQuery()
+ {
+ EnableImageTypes = Array.Empty();
+ EnableTotalRecordCount = true;
+ DisableFirstEpisode = false;
+ }
+
///
/// Gets or sets the user id.
///
@@ -27,19 +34,19 @@ namespace MediaBrowser.Model.Querying
public string SeriesId { get; set; }
///
- /// Skips over a given number of items within the results. Use for paging.
+ /// Gets or sets the start index. Use for paging.
///
/// The start index.
public int? StartIndex { get; set; }
///
- /// The maximum number of items to return.
+ /// Gets or sets the maximum number of items to return.
///
/// The limit.
public int? Limit { get; set; }
///
- /// Fields to return within the items, in addition to basic information.
+ /// gets or sets the fields to return within the items, in addition to basic information.
///
/// The fields.
public ItemFields[] Fields { get; set; }
@@ -68,12 +75,5 @@ namespace MediaBrowser.Model.Querying
/// Gets or sets a value indicating whether do disable sending first episode as next up.
///
public bool DisableFirstEpisode { get; set; }
-
- public NextUpQuery()
- {
- EnableImageTypes = Array.Empty();
- EnableTotalRecordCount = true;
- DisableFirstEpisode = false;
- }
}
}
diff --git a/MediaBrowser.Model/Querying/QueryFilters.cs b/MediaBrowser.Model/Querying/QueryFilters.cs
index 6e4d251818..73b27a7b06 100644
--- a/MediaBrowser.Model/Querying/QueryFilters.cs
+++ b/MediaBrowser.Model/Querying/QueryFilters.cs
@@ -6,35 +6,16 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.Querying
{
- public class QueryFiltersLegacy
+ public class QueryFilters
{
- public string[] Genres { get; set; }
-
- public string[] Tags { get; set; }
-
- public string[] OfficialRatings { get; set; }
-
- public int[] Years { get; set; }
-
- public QueryFiltersLegacy()
+ public QueryFilters()
{
- Genres = Array.Empty();
Tags = Array.Empty();
- OfficialRatings = Array.Empty();
- Years = Array.Empty();
+ Genres = Array.Empty();
}
- }
- public class QueryFilters
- {
public NameGuidPair[] Genres { get; set; }
public string[] Tags { get; set; }
-
- public QueryFilters()
- {
- Tags = Array.Empty();
- Genres = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Querying/QueryFiltersLegacy.cs b/MediaBrowser.Model/Querying/QueryFiltersLegacy.cs
new file mode 100644
index 0000000000..fcb450ed30
--- /dev/null
+++ b/MediaBrowser.Model/Querying/QueryFiltersLegacy.cs
@@ -0,0 +1,26 @@
+#nullable disable
+#pragma warning disable CS1591
+
+using System;
+
+namespace MediaBrowser.Model.Querying
+{
+ public class QueryFiltersLegacy
+ {
+ public QueryFiltersLegacy()
+ {
+ Genres = Array.Empty();
+ Tags = Array.Empty();
+ OfficialRatings = Array.Empty();
+ Years = Array.Empty();
+ }
+
+ public string[] Genres { get; set; }
+
+ public string[] Tags { get; set; }
+
+ public string[] OfficialRatings { get; set; }
+
+ public int[] Years { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Querying/QueryResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs
index 490f48b84b..8ce794800b 100644
--- a/MediaBrowser.Model/Querying/QueryResult.cs
+++ b/MediaBrowser.Model/Querying/QueryResult.cs
@@ -8,6 +8,17 @@ namespace MediaBrowser.Model.Querying
{
public class QueryResult
{
+ public QueryResult()
+ {
+ Items = Array.Empty();
+ }
+
+ public QueryResult(IReadOnlyList items)
+ {
+ Items = items;
+ TotalRecordCount = items.Count;
+ }
+
///
/// Gets or sets the items.
///
@@ -15,26 +26,15 @@ namespace MediaBrowser.Model.Querying
public IReadOnlyList Items { get; set; }
///
- /// The total number of records available.
+ /// Gets or sets the total number of records available.
///
/// The total record count.
public int TotalRecordCount { get; set; }
///
- /// The index of the first record in Items.
+ /// Gets or sets the index of the first record in Items.
///
/// First record index.
public int StartIndex { get; set; }
-
- public QueryResult()
- {
- Items = Array.Empty();
- }
-
- public QueryResult(IReadOnlyList items)
- {
- Items = items;
- TotalRecordCount = items.Count;
- }
}
}
diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
index eb62394605..2cf0f0d5f8 100644
--- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
+++ b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
@@ -8,6 +8,11 @@ namespace MediaBrowser.Model.Querying
{
public class UpcomingEpisodesQuery
{
+ public UpcomingEpisodesQuery()
+ {
+ EnableImageTypes = Array.Empty();
+ }
+
///
/// Gets or sets the user id.
///
@@ -21,19 +26,19 @@ namespace MediaBrowser.Model.Querying
public string ParentId { get; set; }
///
- /// Skips over a given number of items within the results. Use for paging.
+ /// Gets or sets the start index. Use for paging.
///
/// The start index.
public int? StartIndex { get; set; }
///
- /// The maximum number of items to return.
+ /// Gets or sets the maximum number of items to return.
///
/// The limit.
public int? Limit { get; set; }
///
- /// Fields to return within the items, in addition to basic information.
+ /// Gets or sets the fields to return within the items, in addition to basic information.
///
/// The fields.
public ItemFields[] Fields { get; set; }
@@ -55,10 +60,5 @@ namespace MediaBrowser.Model.Querying
///
/// The enable image types.
public ImageType[] EnableImageTypes { get; set; }
-
- public UpcomingEpisodesQuery()
- {
- EnableImageTypes = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs
index ce60062cd3..aedfa4d363 100644
--- a/MediaBrowser.Model/Search/SearchQuery.cs
+++ b/MediaBrowser.Model/Search/SearchQuery.cs
@@ -7,8 +7,21 @@ namespace MediaBrowser.Model.Search
{
public class SearchQuery
{
+ public SearchQuery()
+ {
+ IncludeArtists = true;
+ IncludeGenres = true;
+ IncludeMedia = true;
+ IncludePeople = true;
+ IncludeStudios = true;
+
+ MediaTypes = Array.Empty();
+ IncludeItemTypes = Array.Empty();
+ ExcludeItemTypes = Array.Empty();
+ }
+
///
- /// The user to localize search results for.
+ /// Gets or sets the user to localize search results for.
///
/// The user id.
public Guid UserId { get; set; }
@@ -20,13 +33,13 @@ namespace MediaBrowser.Model.Search
public string SearchTerm { get; set; }
///
- /// Skips over a given number of items within the results. Use for paging.
+ /// Gets or sets the start index. Used for paging.
///
/// The start index.
public int? StartIndex { get; set; }
///
- /// The maximum number of items to return.
+ /// Gets or sets the maximum number of items to return.
///
/// The limit.
public int? Limit { get; set; }
@@ -58,18 +71,5 @@ namespace MediaBrowser.Model.Search
public bool? IsKids { get; set; }
public bool? IsSports { get; set; }
-
- public SearchQuery()
- {
- IncludeArtists = true;
- IncludeGenres = true;
- IncludeMedia = true;
- IncludePeople = true;
- IncludeStudios = true;
-
- MediaTypes = Array.Empty();
- IncludeItemTypes = Array.Empty();
- ExcludeItemTypes = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Session/BrowseRequest.cs b/MediaBrowser.Model/Session/BrowseRequest.cs
index 1c997d5846..65afe5cf34 100644
--- a/MediaBrowser.Model/Session/BrowseRequest.cs
+++ b/MediaBrowser.Model/Session/BrowseRequest.cs
@@ -7,6 +7,7 @@ namespace MediaBrowser.Model.Session
public class BrowseRequest
{
///
+ /// Gets or sets the item type.
/// Artist, Genre, Studio, Person, or any kind of BaseItem.
///
/// The type of the item.
diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs
index 5852f4e37a..d692906c64 100644
--- a/MediaBrowser.Model/Session/ClientCapabilities.cs
+++ b/MediaBrowser.Model/Session/ClientCapabilities.cs
@@ -9,6 +9,13 @@ namespace MediaBrowser.Model.Session
{
public class ClientCapabilities
{
+ public ClientCapabilities()
+ {
+ PlayableMediaTypes = Array.Empty();
+ SupportedCommands = Array.Empty();
+ SupportsPersistentIdentifier = true;
+ }
+
public IReadOnlyList PlayableMediaTypes { get; set; }
public IReadOnlyList SupportedCommands { get; set; }
@@ -28,12 +35,5 @@ namespace MediaBrowser.Model.Session
public string AppStoreUrl { get; set; }
public string IconUrl { get; set; }
-
- public ClientCapabilities()
- {
- PlayableMediaTypes = Array.Empty();
- SupportedCommands = Array.Empty();
- SupportsPersistentIdentifier = true;
- }
}
}
diff --git a/MediaBrowser.Model/Session/GeneralCommand.cs b/MediaBrowser.Model/Session/GeneralCommand.cs
index 77bb6bcf77..29528c1106 100644
--- a/MediaBrowser.Model/Session/GeneralCommand.cs
+++ b/MediaBrowser.Model/Session/GeneralCommand.cs
@@ -1,4 +1,3 @@
-#nullable disable
#pragma warning disable CS1591
using System;
@@ -8,15 +7,15 @@ namespace MediaBrowser.Model.Session
{
public class GeneralCommand
{
- public GeneralCommandType Name { get; set; }
-
- public Guid ControllingUserId { get; set; }
-
- public Dictionary Arguments { get; set; }
-
public GeneralCommand()
{
Arguments = new Dictionary();
}
+
+ public GeneralCommandType Name { get; set; }
+
+ public Guid ControllingUserId { get; set; }
+
+ public Dictionary Arguments { get; }
}
}
diff --git a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
index 73dbe6a2da..a6e7efcb0c 100644
--- a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
+++ b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
@@ -111,18 +111,4 @@ namespace MediaBrowser.Model.Session
public string PlaylistItemId { get; set; }
}
-
- public enum RepeatMode
- {
- RepeatNone = 0,
- RepeatAll = 1,
- RepeatOne = 2
- }
-
- public class QueueItem
- {
- public Guid Id { get; set; }
-
- public string PlaylistItemId { get; set; }
- }
}
diff --git a/MediaBrowser.Model/Session/PlaystateCommand.cs b/MediaBrowser.Model/Session/PlaystateCommand.cs
index 3aa091f791..df47f3b73d 100644
--- a/MediaBrowser.Model/Session/PlaystateCommand.cs
+++ b/MediaBrowser.Model/Session/PlaystateCommand.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
namespace MediaBrowser.Model.Session
{
///
@@ -46,6 +44,10 @@ namespace MediaBrowser.Model.Session
/// The fast forward.
///
FastForward,
+
+ ///
+ /// The play pause.
+ ///
PlayPause
}
}
diff --git a/MediaBrowser.Model/Session/QueueItem.cs b/MediaBrowser.Model/Session/QueueItem.cs
new file mode 100644
index 0000000000..32b19101b2
--- /dev/null
+++ b/MediaBrowser.Model/Session/QueueItem.cs
@@ -0,0 +1,14 @@
+#nullable disable
+#pragma warning disable CS1591
+
+using System;
+
+namespace MediaBrowser.Model.Session
+{
+ public class QueueItem
+ {
+ public Guid Id { get; set; }
+
+ public string PlaylistItemId { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Session/RepeatMode.cs b/MediaBrowser.Model/Session/RepeatMode.cs
new file mode 100644
index 0000000000..c6e173d6b8
--- /dev/null
+++ b/MediaBrowser.Model/Session/RepeatMode.cs
@@ -0,0 +1,11 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Model.Session
+{
+ public enum RepeatMode
+ {
+ RepeatNone = 0,
+ RepeatAll = 1,
+ RepeatOne = 2
+ }
+}
diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs
new file mode 100644
index 0000000000..e93b5d2882
--- /dev/null
+++ b/MediaBrowser.Model/Session/TranscodeReason.cs
@@ -0,0 +1,31 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Model.Session
+{
+ public enum TranscodeReason
+ {
+ ContainerNotSupported = 0,
+ VideoCodecNotSupported = 1,
+ AudioCodecNotSupported = 2,
+ ContainerBitrateExceedsLimit = 3,
+ AudioBitrateNotSupported = 4,
+ AudioChannelsNotSupported = 5,
+ VideoResolutionNotSupported = 6,
+ UnknownVideoStreamInfo = 7,
+ UnknownAudioStreamInfo = 8,
+ AudioProfileNotSupported = 9,
+ AudioSampleRateNotSupported = 10,
+ AnamorphicVideoNotSupported = 11,
+ InterlacedVideoNotSupported = 12,
+ SecondaryAudioNotSupported = 13,
+ RefFramesNotSupported = 14,
+ VideoBitDepthNotSupported = 15,
+ VideoBitrateNotSupported = 16,
+ VideoFramerateNotSupported = 17,
+ VideoLevelNotSupported = 18,
+ VideoProfileNotSupported = 19,
+ AudioBitDepthNotSupported = 20,
+ SubtitleCodecNotSupported = 21,
+ DirectPlayError = 22
+ }
+}
diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs
index e832c2f6fa..064a087d5d 100644
--- a/MediaBrowser.Model/Session/TranscodingInfo.cs
+++ b/MediaBrowser.Model/Session/TranscodingInfo.cs
@@ -7,6 +7,11 @@ namespace MediaBrowser.Model.Session
{
public class TranscodingInfo
{
+ public TranscodingInfo()
+ {
+ TranscodeReasons = Array.Empty();
+ }
+
public string AudioCodec { get; set; }
public string VideoCodec { get; set; }
@@ -30,37 +35,5 @@ namespace MediaBrowser.Model.Session
public int? AudioChannels { get; set; }
public TranscodeReason[] TranscodeReasons { get; set; }
-
- public TranscodingInfo()
- {
- TranscodeReasons = Array.Empty();
- }
- }
-
- public enum TranscodeReason
- {
- ContainerNotSupported = 0,
- VideoCodecNotSupported = 1,
- AudioCodecNotSupported = 2,
- ContainerBitrateExceedsLimit = 3,
- AudioBitrateNotSupported = 4,
- AudioChannelsNotSupported = 5,
- VideoResolutionNotSupported = 6,
- UnknownVideoStreamInfo = 7,
- UnknownAudioStreamInfo = 8,
- AudioProfileNotSupported = 9,
- AudioSampleRateNotSupported = 10,
- AnamorphicVideoNotSupported = 11,
- InterlacedVideoNotSupported = 12,
- SecondaryAudioNotSupported = 13,
- RefFramesNotSupported = 14,
- VideoBitDepthNotSupported = 15,
- VideoBitrateNotSupported = 16,
- VideoFramerateNotSupported = 17,
- VideoLevelNotSupported = 18,
- VideoProfileNotSupported = 19,
- AudioBitDepthNotSupported = 20,
- SubtitleCodecNotSupported = 21,
- DirectPlayError = 22
}
}
diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs
index b9290b6e83..3e396e5d13 100644
--- a/MediaBrowser.Model/Sync/SyncJob.cs
+++ b/MediaBrowser.Model/Sync/SyncJob.cs
@@ -7,6 +7,11 @@ namespace MediaBrowser.Model.Sync
{
public class SyncJob
{
+ public SyncJob()
+ {
+ RequestedItemIds = Array.Empty();
+ }
+
///
/// Gets or sets the identifier.
///
@@ -126,10 +131,5 @@ namespace MediaBrowser.Model.Sync
public string PrimaryImageItemId { get; set; }
public string PrimaryImageTag { get; set; }
-
- public SyncJob()
- {
- RequestedItemIds = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index 4b83fb7e61..d75ae91c02 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -30,6 +30,14 @@ namespace MediaBrowser.Model.System
///
public class SystemInfo : PublicSystemInfo
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SystemInfo()
+ {
+ CompletedInstallations = Array.Empty();
+ }
+
///
/// Gets or sets the display name of the operating system.
///
@@ -37,7 +45,7 @@ namespace MediaBrowser.Model.System
public string OperatingSystemDisplayName { get; set; }
///
- /// Get or sets the package name.
+ /// Gets or sets the package name.
///
/// The value of the '-package' command line argument.
public string PackageName { get; set; }
@@ -127,13 +135,5 @@ namespace MediaBrowser.Model.System
public FFmpegLocation EncoderLocation { get; set; }
public Architecture SystemArchitecture { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public SystemInfo()
- {
- CompletedInstallations = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/System/WakeOnLanInfo.cs b/MediaBrowser.Model/System/WakeOnLanInfo.cs
index b2cbe737d1..aba19a6baf 100644
--- a/MediaBrowser.Model/System/WakeOnLanInfo.cs
+++ b/MediaBrowser.Model/System/WakeOnLanInfo.cs
@@ -36,7 +36,7 @@ namespace MediaBrowser.Model.System
/// Gets the MAC address of the device.
///
/// The MAC address.
- public string? MacAddress { get; set; }
+ public string? MacAddress { get; }
///
/// Gets or sets the wake-on-LAN port.
diff --git a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
index 2f05e08c51..ca769e26b3 100644
--- a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
+++ b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Tasks
event EventHandler> TaskProgress;
///
- /// Gets or sets the scheduled task.
+ /// Gets the scheduled task.
///
/// The scheduled task.
IScheduledTask ScheduledTask { get; }
@@ -57,10 +57,9 @@ namespace MediaBrowser.Model.Tasks
double? CurrentProgress { get; }
///
- /// Gets the triggers that define when the task will run.
+ /// Gets or sets the triggers that define when the task will run.
///
/// The triggers.
- /// value
TaskTriggerInfo[] Triggers { get; set; }
///
diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs
index 02b29074e3..a86bf2a1c8 100644
--- a/MediaBrowser.Model/Tasks/ITaskManager.cs
+++ b/MediaBrowser.Model/Tasks/ITaskManager.cs
@@ -9,6 +9,10 @@ namespace MediaBrowser.Model.Tasks
{
public interface ITaskManager : IDisposable
{
+ event EventHandler> TaskExecuting;
+
+ event EventHandler TaskCompleted;
+
///
/// Gets the list of Scheduled Tasks.
///
@@ -18,7 +22,7 @@ namespace MediaBrowser.Model.Tasks
///
/// Cancels if running and queue.
///
- ///
+ /// An implementatin of .
/// Task options.
void CancelIfRunningAndQueue(TaskOptions options)
where T : IScheduledTask;
@@ -26,21 +30,21 @@ namespace MediaBrowser.Model.Tasks
///
/// Cancels if running and queue.
///
- ///
+ /// An implementatin of .
void CancelIfRunningAndQueue()
where T : IScheduledTask;
///
/// Cancels if running.
///
- ///
+ /// An implementatin of .
void CancelIfRunning()
where T : IScheduledTask;
///
/// Queues the scheduled task.
///
- ///
+ /// An implementatin of .
/// Task options.
void QueueScheduledTask(TaskOptions options)
where T : IScheduledTask;
@@ -48,7 +52,7 @@ namespace MediaBrowser.Model.Tasks
///
/// Queues the scheduled task.
///
- ///
+ /// An implementatin of .
void QueueScheduledTask()
where T : IScheduledTask;
@@ -58,6 +62,8 @@ namespace MediaBrowser.Model.Tasks
///
/// Queues the scheduled task.
///
+ /// The to queue.
+ /// The to use.
void QueueScheduledTask(IScheduledTask task, TaskOptions options);
///
@@ -67,12 +73,10 @@ namespace MediaBrowser.Model.Tasks
void AddTasks(IEnumerable tasks);
void Cancel(IScheduledTaskWorker task);
+
Task Execute(IScheduledTaskWorker task, TaskOptions options);
void Execute()
where T : IScheduledTask;
-
- event EventHandler> TaskExecuting;
- event EventHandler TaskCompleted;
}
}
diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs
index 5c30d6c220..cbd60cca18 100644
--- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs
+++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs
@@ -21,6 +21,10 @@ namespace MediaBrowser.Model.Tasks
///
/// Stars waiting for the trigger action.
///
+ /// Result of the last run triggerd task.
+ /// The .
+ /// The name of the task.
+ /// Wheter or not this is is fired during startup.
void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup);
///
diff --git a/MediaBrowser.Model/Tasks/TaskInfo.cs b/MediaBrowser.Model/Tasks/TaskInfo.cs
index 77100dfe76..16de0b1210 100644
--- a/MediaBrowser.Model/Tasks/TaskInfo.cs
+++ b/MediaBrowser.Model/Tasks/TaskInfo.cs
@@ -8,6 +8,14 @@ namespace MediaBrowser.Model.Tasks
///
public class TaskInfo
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TaskInfo()
+ {
+ Triggers = Array.Empty();
+ }
+
///
/// Gets or sets the name.
///
@@ -67,13 +75,5 @@ namespace MediaBrowser.Model.Tasks
///
/// The key.
public string Key { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public TaskInfo()
- {
- Triggers = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
index 5aeaffc2b3..f8a8c727ef 100644
--- a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
+++ b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
@@ -10,6 +10,12 @@ namespace MediaBrowser.Model.Tasks
///
public class TaskTriggerInfo
{
+ public const string TriggerDaily = "DailyTrigger";
+ public const string TriggerWeekly = "WeeklyTrigger";
+ public const string TriggerInterval = "IntervalTrigger";
+ public const string TriggerSystemEvent = "SystemEventTrigger";
+ public const string TriggerStartup = "StartupTrigger";
+
///
/// Gets or sets the type.
///
@@ -39,11 +45,5 @@ namespace MediaBrowser.Model.Tasks
///
/// The maximum runtime ticks.
public long? MaxRuntimeTicks { get; set; }
-
- public const string TriggerDaily = "DailyTrigger";
- public const string TriggerWeekly = "WeeklyTrigger";
- public const string TriggerInterval = "IntervalTrigger";
- public const string TriggerSystemEvent = "SystemEventTrigger";
- public const string TriggerStartup = "StartupTrigger";
}
}
diff --git a/MediaBrowser.Model/Updates/InstallationInfo.cs b/MediaBrowser.Model/Updates/InstallationInfo.cs
index eebe1a9034..cc600de9de 100644
--- a/MediaBrowser.Model/Updates/InstallationInfo.cs
+++ b/MediaBrowser.Model/Updates/InstallationInfo.cs
@@ -1,4 +1,5 @@
#nullable disable
+
using System;
using System.Text.Json.Serialization;
@@ -45,5 +46,11 @@ namespace MediaBrowser.Model.Updates
///
/// The checksum.
public string Checksum { get; set; }
+
+ ///
+ /// Gets or sets package information for the installation.
+ ///
+ /// The package information.
+ public PackageInfo PackageInfo { get; set; }
}
}
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 37da04adf7..111070d813 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -10,6 +10,56 @@ namespace MediaBrowser.Model.Users
{
public class UserPolicy
{
+ public UserPolicy()
+ {
+ IsHidden = true;
+
+ EnableContentDeletion = false;
+ EnableContentDeletionFromFolders = Array.Empty();
+
+ EnableSyncTranscoding = true;
+ EnableMediaConversion = true;
+
+ EnableMediaPlayback = true;
+ EnableAudioPlaybackTranscoding = true;
+ EnableVideoPlaybackTranscoding = true;
+ EnablePlaybackRemuxing = true;
+ ForceRemoteSourceTranscoding = false;
+ EnableLiveTvManagement = true;
+ EnableLiveTvAccess = true;
+
+ // Without this on by default, admins won't be able to do this
+ // Improve in the future
+ EnableLiveTvManagement = true;
+
+ EnableSharedDeviceControl = true;
+
+ BlockedTags = Array.Empty();
+ BlockUnratedItems = Array.Empty();
+
+ EnableUserPreferenceAccess = true;
+
+ AccessSchedules = Array.Empty();
+
+ LoginAttemptsBeforeLockout = -1;
+
+ MaxActiveSessions = 0;
+
+ EnableAllChannels = true;
+ EnabledChannels = Array.Empty();
+
+ EnableAllFolders = true;
+ EnabledFolders = Array.Empty();
+
+ EnabledDevices = Array.Empty();
+ EnableAllDevices = true;
+
+ EnableContentDownloading = true;
+ EnablePublicSharing = true;
+ EnableRemoteAccess = true;
+ SyncPlayAccess = SyncPlayUserAccessType.CreateAndJoinGroups;
+ }
+
///
/// Gets or sets a value indicating whether this instance is administrator.
///
@@ -112,55 +162,5 @@ namespace MediaBrowser.Model.Users
///
/// Access level to SyncPlay features.
public SyncPlayUserAccessType SyncPlayAccess { get; set; }
-
- public UserPolicy()
- {
- IsHidden = true;
-
- EnableContentDeletion = false;
- EnableContentDeletionFromFolders = Array.Empty();
-
- EnableSyncTranscoding = true;
- EnableMediaConversion = true;
-
- EnableMediaPlayback = true;
- EnableAudioPlaybackTranscoding = true;
- EnableVideoPlaybackTranscoding = true;
- EnablePlaybackRemuxing = true;
- ForceRemoteSourceTranscoding = false;
- EnableLiveTvManagement = true;
- EnableLiveTvAccess = true;
-
- // Without this on by default, admins won't be able to do this
- // Improve in the future
- EnableLiveTvManagement = true;
-
- EnableSharedDeviceControl = true;
-
- BlockedTags = Array.Empty();
- BlockUnratedItems = Array.Empty();
-
- EnableUserPreferenceAccess = true;
-
- AccessSchedules = Array.Empty();
-
- LoginAttemptsBeforeLockout = -1;
-
- MaxActiveSessions = 0;
-
- EnableAllChannels = true;
- EnabledChannels = Array.Empty();
-
- EnableAllFolders = true;
- EnabledFolders = Array.Empty();
-
- EnabledDevices = Array.Empty();
- EnableAllDevices = true;
-
- EnableContentDownloading = true;
- EnablePublicSharing = true;
- EnableRemoteAccess = true;
- SyncPlayAccess = SyncPlayUserAccessType.CreateAndJoinGroups;
- }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index f6926d680d..9a3e3d5fad 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -7,6 +7,8 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using TMDbLib.Objects.Find;
+using TMDbLib.Objects.Search;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -43,64 +45,89 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public async Task> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
{
- var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
-
- if (tmdbId == 0)
+ if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var id))
{
- var movieResults = await _tmdbClientManager
- .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken)
+ var movie = await _tmdbClientManager
+ .GetMovieAsync(
+ int.Parse(id, CultureInfo.InvariantCulture),
+ searchInfo.MetadataLanguage,
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ cancellationToken)
.ConfigureAwait(false);
- var remoteSearchResults = new List();
- for (var i = 0; i < movieResults.Count; i++)
+ var remoteResult = new RemoteSearchResult
{
- var movieResult = movieResults[i];
- var remoteSearchResult = new RemoteSearchResult
- {
- Name = movieResult.Title ?? movieResult.OriginalTitle,
- ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
- Overview = movieResult.Overview,
- SearchProviderName = Name
- };
+ Name = movie.Title ?? movie.OriginalTitle,
+ SearchProviderName = Name,
+ ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
+ Overview = movie.Overview
+ };
+
+ if (movie.ReleaseDate != null)
+ {
+ var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
+ remoteResult.PremiereDate = releaseDate;
+ remoteResult.ProductionYear = releaseDate.Year;
+ }
- var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
- remoteSearchResult.PremiereDate = releaseDate;
- remoteSearchResult.ProductionYear = releaseDate?.Year;
+ remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
- remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCulture));
- remoteSearchResults.Add(remoteSearchResult);
+ if (!string.IsNullOrWhiteSpace(movie.ImdbId))
+ {
+ remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId);
}
- return remoteSearchResults;
+ return new[] { remoteResult };
}
- var movie = await _tmdbClientManager
- .GetMovieAsync(tmdbId, searchInfo.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), cancellationToken)
- .ConfigureAwait(false);
-
- var remoteResult = new RemoteSearchResult
+ IReadOnlyList movieResults;
+ if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id))
{
- Name = movie.Title ?? movie.OriginalTitle,
- SearchProviderName = Name,
- ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
- Overview = movie.Overview
- };
-
- if (movie.ReleaseDate != null)
+ var result = await _tmdbClientManager.FindByExternalIdAsync(
+ id,
+ FindExternalSource.Imdb,
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ cancellationToken).ConfigureAwait(false);
+ movieResults = result.MovieResults;
+ }
+ else if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
{
- var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
- remoteResult.PremiereDate = releaseDate;
- remoteResult.ProductionYear = releaseDate.Year;
+ var result = await _tmdbClientManager.FindByExternalIdAsync(
+ id,
+ FindExternalSource.TvDb,
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ cancellationToken).ConfigureAwait(false);
+ movieResults = result.MovieResults;
+ }
+ else
+ {
+ movieResults = await _tmdbClientManager
+ .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken)
+ .ConfigureAwait(false);
}
- remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
-
- if (!string.IsNullOrWhiteSpace(movie.ImdbId))
+ var len = movieResults.Count;
+ var remoteSearchResults = new RemoteSearchResult[len];
+ for (var i = 0; i < len; i++)
{
- remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId);
+ var movieResult = movieResults[i];
+ var remoteSearchResult = new RemoteSearchResult
+ {
+ Name = movieResult.Title ?? movieResult.OriginalTitle,
+ ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
+ Overview = movieResult.Overview,
+ SearchProviderName = Name
+ };
+
+ var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
+ remoteSearchResult.PremiereDate = releaseDate;
+ remoteSearchResult.ProductionYear = releaseDate?.Year;
+
+ remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCulture));
+ remoteSearchResults[i] = remoteSearchResult;
}
- return new[] { remoteResult };
+ return remoteSearchResults;
}
public async Task> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index f0c50d8e51..eb93148c6a 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -43,19 +43,23 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.ResetPeople();
- var xml = streamReader.ReadToEnd();
+ var xmlFile = streamReader.ReadToEnd();
var srch = "";
- var index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
+ var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
+
+ var xml = xmlFile;
if (index != -1)
{
- xml = xml.Substring(0, index + srch.Length);
+ xml = xmlFile.Substring(0, index + srch.Length);
+ xmlFile = xmlFile.Substring(index + srch.Length);
}
// These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
try
{
+ // Extract episode details from the first episodedetails block
using (var stringReader = new StringReader(xml))
using (var reader = XmlReader.Create(stringReader, settings))
{
@@ -77,6 +81,25 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
}
}
+
+ // Extract the last episode number from nfo
+ // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
+ while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
+ {
+ xml = xmlFile.Substring(0, index + srch.Length);
+ xmlFile = xmlFile.Substring(index + srch.Length);
+
+ using (var stringReader = new StringReader(xml))
+ using (var reader = XmlReader.Create(stringReader, settings))
+ {
+ reader.MoveToContent();
+
+ if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num))
+ {
+ item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
+ }
+ }
+ }
}
catch (XmlException)
{
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index fa09bfb66a..81337390cc 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -32,6 +32,8 @@
+
+
diff --git a/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs b/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs
similarity index 60%
rename from tests/Jellyfin.Api.Tests/BrandingServiceTests.cs
rename to tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs
index 1cbe94c5b9..40933562db 100644
--- a/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs
+++ b/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs
@@ -1,3 +1,5 @@
+using System.Net.Mime;
+using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using MediaBrowser.Model.Branding;
@@ -5,11 +7,11 @@ using Xunit;
namespace Jellyfin.Api.Tests
{
- public sealed class BrandingServiceTests : IClassFixture
+ public sealed class BrandingControllerTests : IClassFixture
{
private readonly JellyfinApplicationFactory _factory;
- public BrandingServiceTests(JellyfinApplicationFactory factory)
+ public BrandingControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
@@ -24,8 +26,9 @@ namespace Jellyfin.Api.Tests
var response = await client.GetAsync("/Branding/Configuration");
// Assert
- response.EnsureSuccessStatusCode();
- Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
+ Assert.True(response.IsSuccessStatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
var responseBody = await response.Content.ReadAsStreamAsync();
_ = await JsonSerializer.DeserializeAsync(responseBody);
}
@@ -42,8 +45,9 @@ namespace Jellyfin.Api.Tests
var response = await client.GetAsync(url);
// Assert
- response.EnsureSuccessStatusCode();
- Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType?.ToString());
+ Assert.True(response.IsSuccessStatusCode);
+ Assert.Equal("text/css", response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
}
}
}
diff --git a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs
new file mode 100644
index 0000000000..300b2697f2
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs
@@ -0,0 +1,86 @@
+using System.IO;
+using System.Net;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models;
+using MediaBrowser.Common.Json;
+using Xunit;
+
+namespace Jellyfin.Api.Tests.Controllers
+{
+ public sealed class DashboardControllerTests : IClassFixture
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.GetOptions();
+
+ public DashboardControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task GetDashboardConfigurationPage_NonExistingPage_NotFound()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task GetDashboardConfigurationPage_ExistingPage_CorrectPage()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin").ConfigureAwait(false);
+
+ Assert.True(response.IsSuccessStatusCode);
+ Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType);
+ StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Api.Tests.TestPage.html")!);
+ Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd());
+ }
+
+ [Fact]
+ public async Task GetDashboardConfigurationPage_BrokenPage_NotFound()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task GetConfigurationPages_NoParams_AllConfigurationPages()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false);
+
+ Assert.True(response.IsSuccessStatusCode);
+
+ var res = await response.Content.ReadAsStreamAsync();
+ _ = await JsonSerializer.DeserializeAsync(res, _jsonOpions);
+ // TODO: check content
+ }
+
+ [Fact]
+ public async Task GetConfigurationPages_True_MainMenuConfigurationPages()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false);
+
+ Assert.True(response.IsSuccessStatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
+
+ var res = await response.Content.ReadAsStreamAsync();
+ var data = await JsonSerializer.DeserializeAsync(res, _jsonOpions);
+ Assert.Empty(data);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index eca3df79b3..c6a8ffbd06 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -21,7 +21,7 @@
-
+
@@ -41,4 +41,8 @@
../jellyfin-tests.ruleset
+
+
+
+
diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
index 262e8f9120..ec93d9fa35 100644
--- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
+++ b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
@@ -74,7 +74,7 @@ namespace Jellyfin.Api.Tests
_disposableComponents.Add(loggerFactory);
// Create the app host and initialize it
- var appHost = new CoreAppHost(
+ var appHost = new TestAppHost(
appPaths,
loggerFactory,
commandLineOpts,
@@ -95,7 +95,7 @@ namespace Jellyfin.Api.Tests
var testServer = base.CreateServer(builder);
// Finish initializing the app host
- var appHost = (CoreAppHost)testServer.Services.GetRequiredService();
+ var appHost = (TestAppHost)testServer.Services.GetRequiredService();
appHost.ServiceProvider = testServer.Services;
appHost.InitializeServices().GetAwaiter().GetResult();
appHost.RunStartupTasksAsync().GetAwaiter().GetResult();
diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs b/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs
index 544a74637a..92c534eaea 100644
--- a/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs
+++ b/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs
@@ -1,17 +1,11 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
namespace Jellyfin.Api.Tests.ModelBinders
{
public enum TestType
{
-#pragma warning disable SA1602 // Enumeration items should be documented
How,
Much,
Is,
The,
Fish
-#pragma warning restore SA1602 // Enumeration items should be documented
}
}
diff --git a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs
index 6c3fd0ee10..3984407ee9 100644
--- a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs
@@ -37,28 +37,28 @@ namespace Jellyfin.Api.Tests
EnableIPV6 = ip6
};
- var result = match + ',';
+ var result = match + ",";
ForwardedHeadersOptions options = new ForwardedHeadersOptions();
// Need this here as ::1 and 127.0.0.1 are in them by default.
options.KnownProxies.Clear();
options.KnownNetworks.Clear();
- ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(","), options);
+ ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(','), options);
var sb = new StringBuilder();
foreach (var item in options.KnownProxies)
{
- sb.Append(item);
- sb.Append(',');
+ sb.Append(item)
+ .Append(',');
}
foreach (var item in options.KnownNetworks)
{
- sb.Append(item.Prefix);
- sb.Append('/');
- sb.Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture));
- sb.Append(',');
+ sb.Append(item.Prefix)
+ .Append('/')
+ .Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture))
+ .Append(',');
}
Assert.Equal(sb.ToString(), result);
diff --git a/tests/Jellyfin.Api.Tests/TestAppHost.cs b/tests/Jellyfin.Api.Tests/TestAppHost.cs
new file mode 100644
index 0000000000..772e98d049
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/TestAppHost.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using System.Reflection;
+using Emby.Server.Implementations;
+using Jellyfin.Server;
+using MediaBrowser.Controller;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Api.Tests
+{
+ ///
+ /// Implementation of the abstract class.
+ ///
+ public class TestAppHost : CoreAppHost
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to be used by the .
+ /// The to be used by the .
+ /// The to be used by the .
+ /// The to be used by the .
+ /// The to be used by the .
+ public TestAppHost(
+ IServerApplicationPaths applicationPaths,
+ ILoggerFactory loggerFactory,
+ IStartupOptions options,
+ IFileSystem fileSystem,
+ IServiceCollection collection)
+ : base(
+ applicationPaths,
+ loggerFactory,
+ options,
+ fileSystem,
+ collection)
+ {
+ }
+
+ ///
+ protected override IEnumerable GetAssembliesWithPartsInternal()
+ {
+ foreach (var a in base.GetAssembliesWithPartsInternal())
+ {
+ yield return a;
+ }
+
+ yield return typeof(TestPlugin).Assembly;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/TestPage.html b/tests/Jellyfin.Api.Tests/TestPage.html
new file mode 100644
index 0000000000..8037af8a60
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/TestPage.html
@@ -0,0 +1,9 @@
+
+
+
+ TestPlugin
+
+
+ This is a Test Page.
+
+
diff --git a/tests/Jellyfin.Api.Tests/TestPlugin.cs b/tests/Jellyfin.Api.Tests/TestPlugin.cs
new file mode 100644
index 0000000000..a3b4b6994f
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/TestPlugin.cs
@@ -0,0 +1,43 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Serialization;
+
+namespace Jellyfin.Api.Tests
+{
+ public class TestPlugin : BasePlugin, IHasWebPages
+ {
+ public TestPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
+ {
+ Instance = this;
+ }
+
+ public static TestPlugin? Instance { get; private set; }
+
+ public override Guid Id => new Guid("2d350a13-0bf7-4b61-859c-d5e601b5facf");
+
+ public override string Name => nameof(TestPlugin);
+
+ public override string Description => "Server test Plugin.";
+
+ public IEnumerable GetPages()
+ {
+ yield return new PluginPageInfo
+ {
+ Name = Name,
+ EmbeddedResourcePath = GetType().Namespace + ".TestPage.html"
+ };
+
+ yield return new PluginPageInfo
+ {
+ Name = "BrokenPage",
+ EmbeddedResourcePath = GetType().Namespace + ".foobar"
+ };
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs
deleted file mode 100644
index 8bf613f05f..0000000000
--- a/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using MediaBrowser.Common.Extensions;
-using Xunit;
-
-namespace Jellyfin.Common.Tests.Extensions
-{
- public class StringExtensionsTests
- {
- [Theory]
- [InlineData("", 'q', "")]
- [InlineData("Banana split", ' ', "Banana")]
- [InlineData("Banana split", 'q', "Banana split")]
- public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult)
- {
- var result = str.AsSpan().LeftPart(needle).ToString();
- Assert.Equal(expectedResult, result);
- }
-
- [Theory]
- [InlineData("", "", "")]
- [InlineData("", "q", "")]
- [InlineData("Banana split", "", "")]
- [InlineData("Banana split", " ", "Banana")]
- [InlineData("Banana split test", " split", "Banana")]
- public void LeftPart_ValidArgsWithoutStringComparison_Correct(string str, string needle, string expectedResult)
- {
- var result = str.AsSpan().LeftPart(needle).ToString();
- Assert.Equal(expectedResult, result);
- }
-
- [Theory]
- [InlineData("", "", StringComparison.Ordinal, "")]
- [InlineData("Banana split", " ", StringComparison.Ordinal, "Banana")]
- [InlineData("Banana split test", " split", StringComparison.Ordinal, "Banana")]
- [InlineData("Banana split test", " Split", StringComparison.Ordinal, "Banana split test")]
- [InlineData("Banana split test", " Splït", StringComparison.InvariantCultureIgnoreCase, "Banana split test")]
- public void LeftPart_ValidArgs_Correct(string str, string needle, StringComparison stringComparison, string expectedResult)
- {
- var result = str.AsSpan().LeftPart(needle, stringComparison).ToString();
- Assert.Equal(expectedResult, result);
- }
- }
-}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 57edbf902c..47e2354415 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index c766c54458..fb18a8a8db 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 52a9e11932..7e4a2efad3 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 24f6fb3561..ec9cc656a2 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
index 14ad498399..3775555dec 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using System.Threading;
using MediaBrowser.MediaEncoding.Subtitles;
+using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Jellyfin.MediaEncoding.Subtitles.Tests
@@ -14,25 +15,15 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
using (var stream = File.OpenRead("Test Data/example.ass"))
{
- var parsed = new AssParser().Parse(stream, CancellationToken.None);
+ var parsed = new AssParser(new NullLogger()).Parse(stream, CancellationToken.None);
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("Like an Angel with pity on nobody\r\nThe second line in subtitle", trackEvent.Text);
+ Assert.Equal("{\\pos(400,570)}Like an Angel with pity on nobody" + Environment.NewLine + "The second line in subtitle", trackEvent.Text);
}
}
-
- [Fact]
- public void ParseFieldHeaders_Valid_Success()
- {
- const string Line = "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text";
- var headers = AssParser.ParseFieldHeaders(Line);
- Assert.Equal(1, headers["Start"]);
- Assert.Equal(2, headers["End"]);
- Assert.Equal(9, headers["Text"]);
- }
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
index 3e2d2de10e..537a944b03 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
@@ -22,7 +22,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
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\r\nour final approach into Coruscant.", trackEvent1.Text);
+ 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);
diff --git a/tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
similarity index 61%
rename from tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs
rename to tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
index d11cb242c1..5033d1de91 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
@@ -1,37 +1,42 @@
+using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
-namespace Jellyfin.MediaEncoding.Tests
+namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
public class SsaParserTests
{
// commonly shared invariant value between tests, assumes default format order
private const string InvariantDialoguePrefix = "[Events]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,";
- private SsaParser parser = new SsaParser();
+ private readonly SsaParser _parser = new SsaParser(new NullLogger());
[Theory]
[InlineData("[EvEnTs]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,text", "text")] // label casing insensitivity
[InlineData("[Events]\n,0:00:00.00,0:00:00.01,,,,,,,labelless dialogue", "labelless dialogue")] // no "Dialogue:" label, it is optional
- [InlineData("[Events]\nFormat: Text, Start, End, Layer, Effect, Style\nDialogue: reordered text,0:00:00.00,0:00:00.01", "reordered text")] // reordered formats
+ // TODO: Fix upstream
+ // [InlineData("[Events]\nFormat: Text, Start, End, Layer, Effect, Style\nDialogue: reordered text,0:00:00.00,0:00:00.01", "reordered text")] // reordered formats
[InlineData(InvariantDialoguePrefix + "Cased TEXT", "Cased TEXT")] // preserve text casing
[InlineData(InvariantDialoguePrefix + " text ", " text ")] // do not trim text
[InlineData(InvariantDialoguePrefix + "text, more text", "text, more text")] // append excess dialogue values (> 10) to text
[InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text{\\fn} end", "start text end")] // font name
[InlineData(InvariantDialoguePrefix + "start {\\fs10}text{\\fs} end", "start text end")] // font size
[InlineData(InvariantDialoguePrefix + "start {\\c&H112233}text{\\c} end", "start text end")] // color
- [InlineData(InvariantDialoguePrefix + "start {\\1c&H112233}text{\\1c} end", "start text end")] // primay color
- [InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text1 {\\fs10}text2{\\fs}{\\fn} {\\1c&H112233}text3{\\1c} end", "start text1 text2 text3 end")] // nested formatting
+ // TODO: Fix upstream
+ // [InlineData(InvariantDialoguePrefix + "start {\\1c&H112233}text{\\1c} end", "start text end")] // primay color
+ // [InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text1 {\\fs10}text2{\\fs}{\\fn} {\\1c&H112233}text3{\\1c} end", "start text1 text2 text3 end")] // nested formatting
public void Parse(string ssa, string expectedText)
{
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
{
- SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
+ SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, CancellationToken.None);
SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[0];
Assert.Equal(expectedText, actual.Text);
}
@@ -43,7 +48,7 @@ namespace Jellyfin.MediaEncoding.Tests
{
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
{
- SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
+ SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, CancellationToken.None);
Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
@@ -52,9 +57,10 @@ namespace Jellyfin.MediaEncoding.Tests
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.Text, actual.Text);
}
}
}
@@ -71,26 +77,39 @@ namespace Jellyfin.MediaEncoding.Tests
",
new List
{
- new SubtitleTrackEvent
+ new SubtitleTrackEvent("1", "dialogue1")
{
StartPositionTicks = 11800000,
- EndPositionTicks = 18500000,
- Text = "dialogue1"
+ EndPositionTicks = 18500000
},
- new SubtitleTrackEvent
+ new SubtitleTrackEvent("2", "dialogue2")
{
StartPositionTicks = 21800000,
- EndPositionTicks = 28500000,
- Text = "dialogue2"
+ EndPositionTicks = 28500000
},
- new SubtitleTrackEvent
+ new SubtitleTrackEvent("3", "dialogue3")
{
StartPositionTicks = 31800000,
- EndPositionTicks = 38500000,
- Text = "dialogue3"
+ EndPositionTicks = 38500000
}
}
};
}
+
+ [Fact]
+ public void Parse_Valid_Success()
+ {
+ using (var stream = File.OpenRead("Test Data/example.ssa"))
+ {
+ var parsed = _parser.Parse(stream, CancellationToken.None);
+ 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.MediaEncoding.Tests/Test Data/example.ssa b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa
new file mode 100644
index 0000000000..dcbb972eb9
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa
@@ -0,0 +1,20 @@
+[Script Info]
+; This is a Sub Station Alpha v4 script.
+; For Sub Station Alpha info and downloads,
+; go to http://www.eswat.demon.co.uk/
+Title: Neon Genesis Evangelion - Episode 26 (neutral Spanish)
+Original Script: RoRo
+Script Updated By: version 2.8.01
+ScriptType: v4.00
+Collisions: Normal
+PlayResY: 600
+PlayDepth: 0
+Timer: 100,0000
+
+[V4 Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
+Style: DefaultVCD, Arial,28,11861244,11861244,11861244,-2147483640,-1,0,1,1,2,2,30,30,30,0,0
+
+[Events]
+Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+Dialogue: Marked=0,0:00:01.18,0:00:06.85,DefaultVCD, NTP,0000,0000,0000,,{\pos(400,570)}Like an angel with pity on nobody
diff --git a/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs
new file mode 100644
index 0000000000..c1a1525bad
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+using Xunit;
+
+namespace Jellyfin.Model.Tests.Entities
+{
+ public class ProviderIdsExtensionsTests
+ {
+ private const string ExampleImdbId = "tt0113375";
+
+ [Fact]
+ public void GetProviderId_NullInstance_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => ProviderIdsExtensions.GetProviderId(null!, MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void GetProviderId_NullName_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => ProviderIdsExtensionsTestsObject.Empty.GetProviderId(null!));
+ }
+
+ [Fact]
+ public void GetProviderId_NotFoundName_Null()
+ {
+ Assert.Null(ProviderIdsExtensionsTestsObject.Empty.GetProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void GetProviderId_NullProvider_Null()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject()
+ {
+ ProviderIds = null!
+ };
+
+ Assert.Null(nullProvider.GetProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void TryGetProviderId_NotFoundName_False()
+ {
+ Assert.False(ProviderIdsExtensionsTestsObject.Empty.TryGetProviderId(MetadataProvider.Imdb, out _));
+ }
+
+ [Fact]
+ public void TryGetProviderId_NullProvider_False()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject()
+ {
+ ProviderIds = null!
+ };
+
+ Assert.False(nullProvider.TryGetProviderId(MetadataProvider.Imdb, out _));
+ }
+
+ [Fact]
+ public void GetProviderId_FoundName_Id()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = ExampleImdbId;
+
+ Assert.Equal(ExampleImdbId, provider.GetProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void TryGetProviderId_FoundName_True()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = ExampleImdbId;
+
+ Assert.True(provider.TryGetProviderId(MetadataProvider.Imdb, out var id));
+ Assert.Equal(ExampleImdbId, id);
+ }
+
+ [Fact]
+ public void SetProviderId_NullInstance_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => ProviderIdsExtensions.SetProviderId(null!, MetadataProvider.Imdb, ExampleImdbId));
+ }
+
+ [Fact]
+ public void SetProviderId_Null_Remove()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.SetProviderId(MetadataProvider.Imdb, null!);
+ Assert.Empty(provider.ProviderIds);
+ }
+
+ [Fact]
+ public void SetProviderId_EmptyName_Remove()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = ExampleImdbId;
+ provider.SetProviderId(MetadataProvider.Imdb, string.Empty);
+ Assert.Empty(provider.ProviderIds);
+ }
+
+ [Fact]
+ public void SetProviderId_NonEmptyId_Success()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.SetProviderId(MetadataProvider.Imdb, ExampleImdbId);
+ Assert.Single(provider.ProviderIds);
+ }
+
+ [Fact]
+ public void SetProviderId_NullProvider_Success()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject()
+ {
+ ProviderIds = null!
+ };
+
+ nullProvider.SetProviderId(MetadataProvider.Imdb, ExampleImdbId);
+ Assert.Single(nullProvider.ProviderIds);
+ }
+
+ [Fact]
+ public void SetProviderId_NullProviderAndEmptyName_Success()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject()
+ {
+ ProviderIds = null!
+ };
+
+ nullProvider.SetProviderId(MetadataProvider.Imdb, string.Empty);
+ Assert.Null(nullProvider.ProviderIds);
+ }
+
+ private class ProviderIdsExtensionsTestsObject : IHasProviderIds
+ {
+ public static readonly ProviderIdsExtensionsTestsObject Empty = new ProviderIdsExtensionsTestsObject();
+
+ public Dictionary ProviderIds { get; set; } = new Dictionary();
+ }
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 64d51e0638..6c404193c2 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -8,10 +8,10 @@
-
+
-
+
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index a4d5c0d6fd..247e6aa7ab 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
index d77645cd9e..36ff93a452 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
index b7c1510d27..9f928ded1b 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
@@ -13,34 +13,6 @@ namespace Jellyfin.Networking.Tests
{
public class NetworkParseTests
{
- ///
- /// Tries to identify the string and return an object of that class.
- ///
- /// String to parse.
- /// IPObject to return.
- /// True if the value parsed successfully.
- private static bool TryParse(string addr, out IPObject result)
- {
- if (!string.IsNullOrEmpty(addr))
- {
- // Is it an IP address
- if (IPNetAddress.TryParse(addr, out IPNetAddress nw))
- {
- result = nw;
- return true;
- }
-
- if (IPHost.TryParse(addr, out IPHost h))
- {
- result = h;
- return true;
- }
- }
-
- result = IPNetAddress.None;
- return false;
- }
-
private static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
{
var configManager = new Mock
@@ -52,15 +24,20 @@ namespace Jellyfin.Networking.Tests
}
///
- /// Checks the ability to ignore interfaces
+ /// Checks the ability to ignore virtual interfaces.
///
/// Mock network setup, in the format (IP address, interface index, interface name) | ....
/// LAN addresses.
/// Bind addresses that are excluded.
[Theory]
+ // All valid
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.0/24", "[192.168.1.208/24,200.200.200.200/24]")]
+ // eth16 only
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
- [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.1.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
+ // All interfaces excluded.
+ [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")]
+ // vEthernet1 and vEthernet212 should be excluded.
+ [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")]
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
{
var conf = new NetworkConfiguration()
@@ -118,11 +95,33 @@ namespace Jellyfin.Networking.Tests
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")]
[InlineData("fe80::7add:12ff:febb:c67b%16")]
[InlineData("[fe80::7add:12ff:febb:c67b%16]:123")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16:123")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]")]
+ [InlineData("192.168.1.2/255.255.255.0")]
+ [InlineData("192.168.1.2/24")]
+ public void ValidHostStrings(string address)
+ {
+ Assert.True(IPHost.TryParse(address, out _));
+ }
+
+ ///
+ /// Checks IP address formats.
+ ///
+ ///
+ [Theory]
+ [InlineData("127.0.0.1")]
+ [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
+ [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")]
+ [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16:123")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]")]
[InlineData("192.168.1.2/255.255.255.0")]
[InlineData("192.168.1.2/24")]
public void ValidIPStrings(string address)
{
- Assert.True(TryParse(address, out _));
+ Assert.True(IPNetAddress.TryParse(address, out _));
}
@@ -138,7 +137,8 @@ namespace Jellyfin.Networking.Tests
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517:1231]")]
public void InvalidAddressString(string address)
{
- Assert.False(TryParse(address, out _));
+ Assert.False(IPNetAddress.TryParse(address, out _));
+ Assert.False(IPHost.TryParse(address, out _));
}
@@ -172,11 +172,11 @@ namespace Jellyfin.Networking.Tests
"[]")]
[InlineData(
"192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, !10.10.10.10",
- "[192.158.1.2/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]",
+ "[192.158.1.2/16,[127.0.0.1/32,::1/128],fd23:184f:2029:0:3139:7386:67d7:d517/128]",
"[192.158.1.2/16,127.0.0.1/32]",
"[10.10.10.10/32]",
"[10.10.10.10/32]",
- "[192.158.0.0/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]")]
+ "[192.158.0.0/16,127.0.0.1/32,::1/128,fd23:184f:2029:0:3139:7386:67d7:d517/128]")]
[InlineData("192.158.1.2/255.255.0.0,192.169.1.2/8",
"[192.158.1.2/16,192.169.1.2/8]",
"[192.158.1.2/16,192.169.1.2/8]",
@@ -333,8 +333,8 @@ namespace Jellyfin.Networking.Tests
public void TestSubnetContains(string network, string ip)
{
- Assert.True(TryParse(network, out IPObject? networkObj));
- Assert.True(TryParse(ip, out IPObject? ipObj));
+ Assert.True(IPNetAddress.TryParse(network, out var networkObj));
+ Assert.True(IPNetAddress.TryParse(ip, out var ipObj));
Assert.True(networkObj.Contains(ipObj));
}
@@ -468,7 +468,7 @@ namespace Jellyfin.Networking.Tests
// User on internal network, no binding specified - so result is the 1st internal.
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
- // User on external network, internal binding only - so asumption is a proxy forward, return external override.
+ // User on external network, internal binding only - so assumption is a proxy forward, return external override.
[InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
// User on external network, no binding - so result is the 1st external which is overriden.
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index c3b3155fe9..14b8cbd54c 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index aed3e8ac5d..bc076caed6 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
index d10ef9b478..053e0a89ea 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
@@ -42,7 +42,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
- public void Fetch_Valid_Succes()
+ public void Fetch_Valid_Success()
{
var result = new MetadataResult()
{
@@ -97,6 +97,26 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
Assert.Equal(new DateTime(2017, 10, 7, 14, 25, 47), item.DateCreated);
}
+ [Fact]
+ public void Fetch_Valid_MultiEpisode_Success()
+ {
+ var result = new MetadataResult()
+ {
+ Item = new Episode()
+ };
+
+ _parser.Fetch(result, "Test Data/Rising.nfo", CancellationToken.None);
+
+ var item = result.Item;
+ Assert.Equal("Rising (1)", 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(new DateTime(2004, 7, 16), item.PremiereDate);
+ Assert.Equal(2004, item.ProductionYear);
+ }
+
[Fact]
public void Fetch_WithNullItem_ThrowsArgumentException()
{
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index 76231391e2..ff4795569c 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -57,7 +57,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
- public void Fetch_Valid_Succes()
+ public void Fetch_Valid_Success()
{
var result = new MetadataResult