Merge remote-tracking branch 'upstream/master' into FixFor5280Part2
This commit is contained in:
commit
80ca3da55c
|
@ -1,5 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1602
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
|
|
|
@ -553,7 +553,7 @@ namespace Emby.Dlna
|
|||
|
||||
private void DumpProfiles()
|
||||
{
|
||||
DeviceProfile[] list = new []
|
||||
DeviceProfile[] list = new[]
|
||||
{
|
||||
new SamsungSmartTvProfile(),
|
||||
new XboxOneProfile(),
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1602
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
|
|
|
@ -137,6 +137,9 @@ namespace Emby.Server.Implementations
|
|||
|
||||
public bool CoreStartupHasCompleted { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri PublishedServerUrl => _startupOptions.PublishedServerUrl;
|
||||
|
||||
public virtual bool CanLaunchWebBrowser
|
||||
{
|
||||
get
|
||||
|
@ -384,7 +387,7 @@ namespace Emby.Server.Implementations
|
|||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependencies.
|
||||
/// </summary>
|
||||
/// /// <typeparam name="T">The type.</typeparam>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <returns>T.</returns>
|
||||
public T CreateInstance<T>()
|
||||
=> ActivatorUtilities.CreateInstance<T>(ServiceProvider);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.1" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
|
||||
<PackageReference Include="sharpcompress" Version="0.28.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.28.1" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -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
|
|||
/// <summary>
|
||||
/// The UDP server.
|
||||
/// </summary>
|
||||
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;
|
||||
|
|
|
@ -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<char> here when Enum.TryParse supports it
|
||||
// https://github.com/dotnet/runtime/issues/20008
|
||||
if (Enum.TryParse<CollectionTypeOptions>(Path.GetExtension(file), true, out var res))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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<byte>());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -335,11 +335,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
return new Uri(url).AbsoluteUri.TrimEnd('/');
|
||||
}
|
||||
|
||||
protected EncodingOptions GetEncodingOptions()
|
||||
{
|
||||
return Config.GetConfiguration<EncodingOptions>("encoding");
|
||||
}
|
||||
|
||||
private static string GetHdHrIdFromChannelId(string channelId)
|
||||
{
|
||||
return channelId.Split('_')[1];
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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<LocalPlugin> _plugins;
|
||||
private readonly Version _minimumVersion;
|
||||
|
||||
private IHttpClientFactory? _httpClientFactory;
|
||||
|
||||
private IHttpClientFactory HttpClientFactory
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_httpClientFactory == null)
|
||||
{
|
||||
_httpClientFactory = _appHost.Resolve<IHttpClientFactory>();
|
||||
}
|
||||
|
||||
return _httpClientFactory;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginManager"/> class.
|
||||
/// </summary>
|
||||
|
@ -332,34 +351,76 @@ namespace Emby.Server.Implementations.Plugins
|
|||
ChangePluginState(plugin, PluginStatus.Malfunctioned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the manifest back to disk.
|
||||
/// </summary>
|
||||
/// <param name="manifest">The <see cref="PluginManifest"/> to save.</param>
|
||||
/// <param name="path">The path where to save the manifest.</param>
|
||||
/// <returns>True if successful.</returns>
|
||||
/// <inheritdoc/>
|
||||
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. {Path}", path);
|
||||
_logger.LogWarning(e, "Unable to save plugin manifest due to invalid value. {Path}", path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path)
|
||||
{
|
||||
if (packageInfo == null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes a plugin's load status.
|
||||
/// </summary>
|
||||
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<bool> 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;
|
||||
|
|
|
@ -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<DashboardController> _logger;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IPluginManager _pluginManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DashboardController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param>
|
||||
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
|
||||
/// <param name="pluginManager">Instance of <see cref="IPluginManager"/> interface.</param>
|
||||
public DashboardController(
|
||||
ILogger<DashboardController> 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<IEnumerable<ConfigurationPageInfo?>> GetConfigurationPages(
|
||||
public ActionResult<IEnumerable<ConfigurationPageInfo>> 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<ConfigurationPageInfo> GetConfigPages(LocalPlugin plugin)
|
||||
|
@ -120,7 +99,7 @@ namespace Jellyfin.Api.Controllers
|
|||
{
|
||||
if (plugin?.Instance is not IHasWebPages hasWebPages)
|
||||
{
|
||||
return new List<Tuple<PluginPageInfo, IPlugin>>();
|
||||
return Enumerable.Empty<Tuple<PluginPageInfo, IPlugin>>();
|
||||
}
|
||||
|
||||
return hasWebPages.GetPages().Select(i => new Tuple<PluginPageInfo, IPlugin>(i, plugin.Instance));
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> AddVirtualFolder(
|
||||
[FromQuery] string? name,
|
||||
[FromQuery] string? collectionType,
|
||||
[FromQuery] CollectionTypeOptions? collectionType,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] paths,
|
||||
[FromBody] AddVirtualFolderDto? libraryOptionsDto,
|
||||
[FromQuery] bool refreshLibrary = false)
|
||||
|
|
|
@ -113,14 +113,5 @@ namespace Jellyfin.Api.Extensions
|
|||
|
||||
return dtoOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if DtoOptions contains field.
|
||||
/// </summary>
|
||||
/// <param name="dtoOptions">DtoOptions object.</param>
|
||||
/// <param name="field">Field to check.</param>
|
||||
/// <returns>Field existence.</returns>
|
||||
internal static bool ContainsField(this DtoOptions dtoOptions, ItemFields field)
|
||||
=> dtoOptions.Fields != null && dtoOptions.Fields.Contains(field);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -523,7 +523,7 @@ namespace Jellyfin.Api.Helpers
|
|||
/// <param name="type">Dlna profile type.</param>
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.5" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.0.5" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigurationPageInfo"/> class.
|
||||
/// </summary>
|
||||
public ConfigurationPageInfo()
|
||||
{
|
||||
Name = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
|
|
|
@ -98,7 +98,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||
|
||||
private EncodingOptions GetOptions()
|
||||
{
|
||||
return _config.GetConfiguration<EncodingOptions>("encoding");
|
||||
return _config.GetEncodingOptions();
|
||||
}
|
||||
|
||||
private async void TimerCallback(object? state)
|
||||
|
|
|
@ -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.
|
||||
/// </summary>
|
||||
/// <param name="token">String to check.</param>
|
||||
/// <param name="index">Interface index number.</param>
|
||||
/// <param name="index">Interface index numbers that match.</param>
|
||||
/// <returns><c>true</c> if an interface name matches the token, <c>False</c> otherwise.</returns>
|
||||
private bool IsInterface(string token, out int index)
|
||||
private bool TryGetInterfaces(string token, [NotNullWhen(true)] out List<int>? 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<int>();
|
||||
index.Add(interfcIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return index != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<EncodingOptions>("encoding");
|
||||
var encoding = _configManager.GetEncodingOptions();
|
||||
if (encoding.EnableThrottling)
|
||||
{
|
||||
_logger.LogInformation("Disabling transcoding throttling during migration");
|
||||
|
|
51
MediaBrowser.Common/Extensions/StreamExtensions.cs
Normal file
51
MediaBrowser.Common/Extensions/StreamExtensions.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MediaBrowser.Common.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Class BaseExtensions.
|
||||
/// </summary>
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads all lines in the <see cref="Stream" />.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream" /> to read from.</param>
|
||||
/// <returns>All lines in the stream.</returns>
|
||||
public static string[] ReadAllLines(this Stream stream)
|
||||
=> ReadAllLines(stream, Encoding.UTF8);
|
||||
|
||||
/// <summary>
|
||||
/// Reads all lines in the <see cref="Stream" />.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream" /> to read from.</param>
|
||||
/// <param name="encoding">The character encoding to use.</param>
|
||||
/// <returns>All lines in the stream.</returns>
|
||||
public static string[] ReadAllLines(this Stream stream, Encoding encoding)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(stream, encoding))
|
||||
{
|
||||
return ReadAllLines(reader).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all lines in the <see cref="StreamReader" />.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="StreamReader" /> to read from.</param>
|
||||
/// <returns>All lines in the stream.</returns>
|
||||
public static IEnumerable<string> ReadAllLines(this StreamReader reader)
|
||||
{
|
||||
string? line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
#nullable enable
|
||||
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Common.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions methods to simplify string operations.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the part on the left of the <c>needle</c>.
|
||||
/// </summary>
|
||||
/// <param name="haystack">The string to seek.</param>
|
||||
/// <param name="needle">The needle to find.</param>
|
||||
/// <returns>The part left of the <paramref name="needle" />.</returns>
|
||||
public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, char needle)
|
||||
{
|
||||
var pos = haystack.IndexOf(needle);
|
||||
return pos == -1 ? haystack : haystack[..pos];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the part on the left of the <c>needle</c>.
|
||||
/// </summary>
|
||||
/// <param name="haystack">The string to seek.</param>
|
||||
/// <param name="needle">The needle to find.</param>
|
||||
/// <param name="stringComparison">One of the enumeration values that specifies the rules for the search.</param>
|
||||
/// <returns>The part left of the <c>needle</c>.</returns>
|
||||
public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle, StringComparison stringComparison = default)
|
||||
{
|
||||
var pos = haystack.IndexOf(needle, stringComparison);
|
||||
return pos == -1 ? haystack : haystack[..pos];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -128,62 +128,63 @@ namespace MediaBrowser.Common.Net
|
|||
/// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns>
|
||||
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)
|
||||
hostObj = IPHost.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 (IPNetAddress.TryParse(host, out var netAddress))
|
||||
{
|
||||
// Host name is an ip address, so fake resolve.
|
||||
hostObj = new IPHost(host, netAddress.Address);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is it a host, IPv4/6 with/out port?
|
||||
string[] hosts = host.Split(':');
|
||||
|
||||
if (hosts.Length <= 2)
|
||||
{
|
||||
// This is either a hostname: port, or an IP4:port.
|
||||
host = hosts[0];
|
||||
|
||||
if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// Is it a host or IPv4 with port?
|
||||
string[] hosts = host.Split(':');
|
||||
|
||||
if (hosts.Length > 2)
|
||||
{
|
||||
hostObj = new IPHost(string.Empty, IPAddress.None);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove port from IPv4 if it exists.
|
||||
host = hosts[0];
|
||||
|
||||
if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hostObj = new IPHost(host, new IPAddress(Ipv4Loopback));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IPNetAddress.TryParse(host, out IPNetAddress netIP))
|
||||
{
|
||||
// Host name is an ip address, so fake resolve.
|
||||
hostObj = new IPHost(host, netIP.Address);
|
||||
return true;
|
||||
}
|
||||
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];
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
|||
/// <returns>True if successful.</returns>
|
||||
bool SaveManifest(PluginManifest manifest, string path);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a manifest from repository data.
|
||||
/// </summary>
|
||||
/// <param name="packageInfo">The <see cref="PackageInfo"/> used to generate a manifest.</param>
|
||||
/// <param name="version">Version to be installed.</param>
|
||||
/// <param name="path">The path where to save the manifest.</param>
|
||||
/// <returns>True if successful.</returns>
|
||||
Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path);
|
||||
|
||||
/// <summary>
|
||||
/// Imports plugin details from a folder.
|
||||
/// </summary>
|
||||
|
|
|
@ -52,6 +52,11 @@ namespace MediaBrowser.Controller
|
|||
/// <value>The name of the friendly.</value>
|
||||
string FriendlyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured published server url.
|
||||
/// </summary>
|
||||
Uri PublishedServerUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system info.
|
||||
/// </summary>
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
public void SetFFmpegPath()
|
||||
{
|
||||
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
|
||||
if (!ValidatePath(_configurationManager.GetConfiguration<EncodingOptions>("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 <EncoderAppPathDisplay> so it appears in UI
|
||||
var config = _configurationManager.GetConfiguration<EncodingOptions>("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 <EncoderAppPath>
|
||||
// This ensures its not lost on next startup
|
||||
var config = _configurationManager.GetConfiguration<EncodingOptions>("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
|
||||
{
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BDInfo" Version="0.7.6.1" />
|
||||
<PackageReference Include="libse" Version="3.5.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
|
||||
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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
|
||||
/// <summary>
|
||||
/// Advanced SubStation Alpha subtitle parser.
|
||||
/// </summary>
|
||||
public class AssParser : SubtitleEditParser<AdvancedSubStationAlpha>
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <inheritdoc />
|
||||
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssParser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public AssParser(ILogger logger) : base(logger)
|
||||
{
|
||||
var trackInfo = new SubtitleTrackInfo();
|
||||
var trackEvents = new List<SubtitleTrackEvent>();
|
||||
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<char> time)
|
||||
{
|
||||
return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out var span)
|
||||
? span.Ticks : 0;
|
||||
}
|
||||
|
||||
internal static Dictionary<string, int> ParseFieldHeaders(string line)
|
||||
{
|
||||
const string Format = "Format: ";
|
||||
var fields = line.Substring(Format.Length).Split(',').Select(x => x.Trim()).ToList();
|
||||
|
||||
return new Dictionary<string, int>
|
||||
{
|
||||
{ "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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
/// <summary>
|
||||
/// SubRip subtitle parser.
|
||||
/// </summary>
|
||||
public class SrtParser : SubtitleEditParser<SubRip>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public SrtParser(ILogger logger)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SrtParser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public SrtParser(ILogger logger) : base(logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var trackInfo = new SubtitleTrackInfo();
|
||||
var trackEvents = new List<SubtitleTrackEvent>();
|
||||
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<string>();
|
||||
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<char> time)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// <see href="https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs">Credit</see>.
|
||||
/// SubStation Alpha subtitle parser.
|
||||
/// </summary>
|
||||
public class SsaParser : ISubtitleParser
|
||||
public class SsaParser : SubtitleEditParser<SubStationAlpha>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SsaParser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public SsaParser(ILogger logger) : base(logger)
|
||||
{
|
||||
var trackInfo = new SubtitleTrackInfo();
|
||||
var trackEvents = new List<SubtitleTrackEvent>();
|
||||
|
||||
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, "<font face=\"" + fontName + "\"" + extraTags + "><i>");
|
||||
}
|
||||
else
|
||||
{
|
||||
text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">");
|
||||
}
|
||||
|
||||
int indexOfEndTag = text.IndexOf("{\\fn}", start, StringComparison.Ordinal);
|
||||
if (indexOfEndTag > 0)
|
||||
{
|
||||
text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>");
|
||||
}
|
||||
else
|
||||
{
|
||||
text += "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, "<font size=\"" + fontSize + "\"" + extraTags + "><i>");
|
||||
}
|
||||
else
|
||||
{
|
||||
text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">");
|
||||
}
|
||||
|
||||
int indexOfEndTag = text.IndexOf("{\\fs}", start, StringComparison.Ordinal);
|
||||
if (indexOfEndTag > 0)
|
||||
{
|
||||
text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>");
|
||||
}
|
||||
else
|
||||
{
|
||||
text += "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, "<font color=\"" + color + "\"" + extraTags + "><i>");
|
||||
}
|
||||
else
|
||||
{
|
||||
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
|
||||
}
|
||||
|
||||
int indexOfEndTag = text.IndexOf("{\\c}", start, StringComparison.Ordinal);
|
||||
if (indexOfEndTag > 0)
|
||||
{
|
||||
text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>");
|
||||
}
|
||||
else
|
||||
{
|
||||
text += "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, "<font color=\"" + color + "\"" + extraTags + "><i>");
|
||||
}
|
||||
else
|
||||
{
|
||||
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
|
||||
}
|
||||
|
||||
int indexOfEndTag = text.IndexOf("{\\1c}", start, StringComparison.Ordinal);
|
||||
if (indexOfEndTag > 0)
|
||||
{
|
||||
text = text.Remove(indexOfEndTag, "{\\1c}".Length).Insert(indexOfEndTag, "</font>");
|
||||
}
|
||||
else
|
||||
{
|
||||
text += "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text = text.Replace(@"{\i1}", "<i>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\i0}", "</i>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\i}", "</i>", StringComparison.Ordinal);
|
||||
if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>"))
|
||||
{
|
||||
text += "</i>";
|
||||
}
|
||||
|
||||
text = text.Replace(@"{\u1}", "<u>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\u0}", "</u>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\u}", "</u>", StringComparison.Ordinal);
|
||||
if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>"))
|
||||
{
|
||||
text += "</u>";
|
||||
}
|
||||
|
||||
text = text.Replace(@"{\b1}", "<b>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\b0}", "</b>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\b}", "</b>", StringComparison.Ordinal);
|
||||
if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>"))
|
||||
{
|
||||
text += "</b>";
|
||||
}
|
||||
|
||||
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('\\');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
63
MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
Normal file
63
MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// SubStation Alpha subtitle parser.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="SubtitleFormat" />.</typeparam>
|
||||
public abstract class SubtitleEditParser<T> : ISubtitleParser
|
||||
where T : SubtitleFormat, new()
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SubtitleEditParser{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
protected SubtitleEditParser(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,7 +27,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
{
|
||||
public class SubtitleEncoder : ISubtitleEncoder
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger<SubtitleEncoder> _logger;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
@ -42,7 +41,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
public SubtitleEncoder(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<SubtitleEncoder> 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<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
|
||||
private async Task<Stream> 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<SubtitleInfo> 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
|
|||
/// <summary>
|
||||
/// Extracts the text subtitle.
|
||||
/// </summary>
|
||||
/// <param name="inputFile">The input file.</param>
|
||||
/// <param name="mediaSource">The mediaSource.</param>
|
||||
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
|
||||
/// <param name="outputCodec">The output codec.</param>
|
||||
|
@ -513,7 +504,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
/// <returns>Task.</returns>
|
||||
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
|
||||
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<char> filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
|
||||
ReadOnlySpan<char> 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<char> filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
|
||||
ReadOnlySpan<char> filename = (mediaSource.Path + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
|
||||
|
||||
var prefix = filename.Slice(0, 1);
|
||||
|
||||
|
|
|
@ -7,6 +7,13 @@ namespace MediaBrowser.Model.Channels
|
|||
{
|
||||
public class ChannelFeatures
|
||||
{
|
||||
public ChannelFeatures()
|
||||
{
|
||||
MediaTypes = Array.Empty<ChannelMediaType>();
|
||||
ContentTypes = Array.Empty<ChannelMediaContentType>();
|
||||
DefaultSortFields = Array.Empty<ChannelItemSortField>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
|
@ -38,7 +45,7 @@ namespace MediaBrowser.Model.Channels
|
|||
public ChannelMediaContentType[] ContentTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public int? MaxPageSize { get; set; }
|
||||
|
||||
|
@ -55,7 +62,7 @@ namespace MediaBrowser.Model.Channels
|
|||
public ChannelItemSortField[] DefaultSortFields { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool SupportsSortOrderToggle { get; set; }
|
||||
|
||||
|
@ -76,12 +83,5 @@ namespace MediaBrowser.Model.Channels
|
|||
/// </summary>
|
||||
/// <value><c>true</c> if [supports content downloading]; otherwise, <c>false</c>.</value>
|
||||
public bool SupportsContentDownloading { get; set; }
|
||||
|
||||
public ChannelFeatures()
|
||||
{
|
||||
MediaTypes = Array.Empty<ChannelMediaType>();
|
||||
ContentTypes = Array.Empty<ChannelMediaContentType>();
|
||||
DefaultSortFields = Array.Empty<ChannelItemSortField>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Channels
|
|||
public class ChannelQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>The fields.</value>
|
||||
public ItemFields[] Fields { get; set; }
|
||||
|
@ -28,13 +28,13 @@ namespace MediaBrowser.Model.Channels
|
|||
public Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skips over a given number of items within the results. Use for paging.
|
||||
/// Gets or sets the start index. Use for paging.
|
||||
/// </summary>
|
||||
/// <value>The start index.</value>
|
||||
public int? StartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of items to return.
|
||||
/// Gets or sets the maximum number of items to return.
|
||||
/// </summary>
|
||||
/// <value>The limit.</value>
|
||||
public int? Limit { get; set; }
|
||||
|
|
|
@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// FFmpeg path as set by the user via the UI.
|
||||
/// Gets or sets the FFmpeg path as set by the user via the UI.
|
||||
/// </summary>
|
||||
public string EncoderAppPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,11 @@ namespace MediaBrowser.Model.Configuration
|
|||
{
|
||||
public class ImageOption
|
||||
{
|
||||
public ImageOption()
|
||||
{
|
||||
Limit = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type.
|
||||
/// </summary>
|
||||
|
@ -23,10 +28,5 @@ namespace MediaBrowser.Model.Configuration
|
|||
/// </summary>
|
||||
/// <value>The minimum width.</value>
|
||||
public int MinWidth { get; set; }
|
||||
|
||||
public ImageOption()
|
||||
{
|
||||
Limit = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TypeOptions>();
|
||||
DisabledSubtitleFetchers = Array.Empty<string>();
|
||||
SubtitleFetcherOrder = Array.Empty<string>();
|
||||
DisabledLocalMetadataReaders = Array.Empty<string>();
|
||||
|
||||
SkipSubtitlesIfAudioTrackMatches = true;
|
||||
RequirePerfectSubtitleMatch = true;
|
||||
|
||||
EnablePhotos = true;
|
||||
SaveSubtitlesWithMedia = true;
|
||||
EnableRealtimeMonitor = true;
|
||||
PathInfos = Array.Empty<MediaPathInfo>();
|
||||
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<TypeOptions>();
|
||||
DisabledSubtitleFetchers = Array.Empty<string>();
|
||||
SubtitleFetcherOrder = Array.Empty<string>();
|
||||
DisabledLocalMetadataReaders = Array.Empty<string>();
|
||||
|
||||
SkipSubtitlesIfAudioTrackMatches = true;
|
||||
RequirePerfectSubtitleMatch = true;
|
||||
|
||||
EnablePhotos = true;
|
||||
SaveSubtitlesWithMedia = true;
|
||||
EnableRealtimeMonitor = true;
|
||||
PathInfos = Array.Empty<MediaPathInfo>();
|
||||
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<string>();
|
||||
MetadataFetcherOrder = Array.Empty<string>();
|
||||
ImageFetchers = Array.Empty<string>();
|
||||
ImageFetcherOrder = Array.Empty<string>();
|
||||
ImageOptions = Array.Empty<ImageOption>();
|
||||
}
|
||||
|
||||
public static Dictionary<string, ImageOption[]> DefaultImageOptions = new Dictionary<string, ImageOption[]>
|
||||
{
|
||||
{
|
||||
"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();
|
||||
}
|
||||
}
|
||||
|
|
12
MediaBrowser.Model/Configuration/MediaPathInfo.cs
Normal file
12
MediaBrowser.Model/Configuration/MediaPathInfo.cs
Normal file
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,16 @@ namespace MediaBrowser.Model.Configuration
|
|||
/// </summary>
|
||||
public class MetadataOptions
|
||||
{
|
||||
public MetadataOptions()
|
||||
{
|
||||
DisabledMetadataSavers = Array.Empty<string>();
|
||||
LocalMetadataReaderOrder = Array.Empty<string>();
|
||||
DisabledMetadataFetchers = Array.Empty<string>();
|
||||
MetadataFetcherOrder = Array.Empty<string>();
|
||||
DisabledImageFetchers = Array.Empty<string>();
|
||||
ImageFetcherOrder = Array.Empty<string>();
|
||||
}
|
||||
|
||||
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<string>();
|
||||
LocalMetadataReaderOrder = Array.Empty<string>();
|
||||
DisabledMetadataFetchers = Array.Empty<string>();
|
||||
MetadataFetcherOrder = Array.Empty<string>();
|
||||
DisabledImageFetchers = Array.Empty<string>();
|
||||
ImageFetcherOrder = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,12 @@ namespace MediaBrowser.Model.Configuration
|
|||
{
|
||||
public class MetadataPluginSummary
|
||||
{
|
||||
public MetadataPluginSummary()
|
||||
{
|
||||
SupportedImageTypes = Array.Empty<ImageType>();
|
||||
Plugins = Array.Empty<MetadataPlugin>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the item.
|
||||
/// </summary>
|
||||
|
@ -25,11 +31,5 @@ namespace MediaBrowser.Model.Configuration
|
|||
/// </summary>
|
||||
/// <value>The supported image types.</value>
|
||||
public ImageType[] SupportedImageTypes { get; set; }
|
||||
|
||||
public MetadataPluginSummary()
|
||||
{
|
||||
SupportedImageTypes = Array.Empty<ImageType>();
|
||||
Plugins = Array.Empty<MetadataPlugin>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
365
MediaBrowser.Model/Configuration/TypeOptions.cs
Normal file
365
MediaBrowser.Model/Configuration/TypeOptions.cs
Normal file
|
@ -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<string, ImageOption[]> DefaultImageOptions = new Dictionary<string, ImageOption[]>
|
||||
{
|
||||
{
|
||||
"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<string>();
|
||||
MetadataFetcherOrder = Array.Empty<string>();
|
||||
ImageFetchers = Array.Empty<string>();
|
||||
ImageFetcherOrder = Array.Empty<string>();
|
||||
ImageOptions = Array.Empty<ImageOption>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,24 @@ namespace MediaBrowser.Model.Configuration
|
|||
/// </summary>
|
||||
public class UserConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
|
||||
/// </summary>
|
||||
public UserConfiguration()
|
||||
{
|
||||
EnableNextEpisodeAutoPlay = true;
|
||||
RememberAudioSelections = true;
|
||||
RememberSubtitleSelections = true;
|
||||
|
||||
HidePlayedInLatest = true;
|
||||
PlayDefaultAudioTrack = true;
|
||||
|
||||
LatestItemsExcludes = Array.Empty<string>();
|
||||
OrderedViews = Array.Empty<string>();
|
||||
MyMediaExcludes = Array.Empty<string>();
|
||||
GroupedFolders = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio language preference.
|
||||
/// </summary>
|
||||
|
@ -52,23 +70,5 @@ namespace MediaBrowser.Model.Configuration
|
|||
public bool RememberSubtitleSelections { get; set; }
|
||||
|
||||
public bool EnableNextEpisodeAutoPlay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
|
||||
/// </summary>
|
||||
public UserConfiguration()
|
||||
{
|
||||
EnableNextEpisodeAutoPlay = true;
|
||||
RememberAudioSelections = true;
|
||||
RememberSubtitleSelections = true;
|
||||
|
||||
HidePlayedInLatest = true;
|
||||
PlayDefaultAudioTrack = true;
|
||||
|
||||
LatestItemsExcludes = Array.Empty<string>();
|
||||
OrderedViews = Array.Empty<string>();
|
||||
MyMediaExcludes = Array.Empty<string>();
|
||||
GroupedFolders = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,20 +34,20 @@ namespace MediaBrowser.Model.Dlna
|
|||
public DeviceProfile Profile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public int? MaxAudioChannels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The application's configured quality setting.
|
||||
/// Gets or sets the application's configured quality setting.
|
||||
/// </summary>
|
||||
public int? MaxBitrate { get; set; }
|
||||
|
||||
|
@ -66,6 +66,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
/// <summary>
|
||||
/// Gets the maximum bitrate.
|
||||
/// </summary>
|
||||
/// <param name="isAudio">Whether or not this is audio.</param>
|
||||
/// <returns>System.Nullable<System.Int32>.</returns>
|
||||
public int? GetMaxBitrate(bool isAudio)
|
||||
{
|
||||
|
|
|
@ -9,6 +9,12 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
public class CodecProfile
|
||||
{
|
||||
public CodecProfile()
|
||||
{
|
||||
Conditions = Array.Empty<ProfileCondition>();
|
||||
ApplyConditions = Array.Empty<ProfileCondition>();
|
||||
}
|
||||
|
||||
[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<ProfileCondition>();
|
||||
ApplyConditions = Array.Empty<ProfileCondition>();
|
||||
}
|
||||
|
||||
public string[] GetCodecs()
|
||||
{
|
||||
return ContainerProfile.SplitValue(Codec);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,6 +9,11 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
public class ContainerProfile
|
||||
{
|
||||
public ContainerProfile()
|
||||
{
|
||||
Conditions = Array.Empty<ProfileCondition>();
|
||||
}
|
||||
|
||||
[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<ProfileCondition>();
|
||||
}
|
||||
|
||||
public string[] GetContainers()
|
||||
{
|
||||
return SplitValue(Container);
|
||||
|
|
|
@ -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;
|
||||
//}
|
||||
// {
|
||||
// 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));
|
||||
string dlnaflags = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
";DLNA.ORG_FLAGS={0}",
|
||||
DlnaMaps.FlagsToString(flagValue));
|
||||
|
||||
ResponseProfile mediaProfile = _profile.GetVideoMediaProfile(
|
||||
container,
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
public interface IDeviceDiscovery
|
||||
{
|
||||
event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
|
||||
|
||||
event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,11 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
public class ResponseProfile
|
||||
{
|
||||
public ResponseProfile()
|
||||
{
|
||||
Conditions = Array.Empty<ProfileCondition>();
|
||||
}
|
||||
|
||||
[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<ProfileCondition>();
|
||||
}
|
||||
|
||||
public string[] GetContainers()
|
||||
{
|
||||
return ContainerProfile.SplitValue(Container);
|
||||
|
|
|
@ -7,6 +7,46 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
public class SearchCriteria
|
||||
{
|
||||
public SearchCriteria(string search)
|
||||
{
|
||||
if (search.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("String can't be empty.", nameof(search));
|
||||
}
|
||||
|
||||
SearchType = SearchType.Unknown;
|
||||
|
||||
string[] factors = RegexSplit(search, "(and|or)");
|
||||
foreach (string factor in factors)
|
||||
{
|
||||
string[] subFactors = RegexSplit(factor.Trim().Trim('(').Trim(')').Trim(), "\\s", 3);
|
||||
|
||||
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("\"object.item.imageItem\"", subFactors[2], StringComparison.Ordinal) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SearchType = SearchType.Image;
|
||||
}
|
||||
else if (string.Equals("\"object.item.videoItem\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SearchType = SearchType.Video;
|
||||
}
|
||||
else if (string.Equals("\"object.container.playlistContainer\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SearchType = SearchType.Playlist;
|
||||
}
|
||||
else if (string.Equals("\"object.container.album.musicAlbum\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SearchType = SearchType.MusicAlbum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SearchType SearchType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -31,45 +71,5 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
return Regex.Split(str, term, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
public SearchCriteria(string search)
|
||||
{
|
||||
if (search.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("String can't be empty.", nameof(search));
|
||||
}
|
||||
|
||||
SearchType = SearchType.Unknown;
|
||||
|
||||
string[] factors = RegexSplit(search, "(and|or)");
|
||||
foreach (string factor in factors)
|
||||
{
|
||||
string[] subFactors = RegexSplit(factor.Trim().Trim('(').Trim(')').Trim(), "\\s", 3);
|
||||
|
||||
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("\"object.item.imageItem\"", subFactors[2], StringComparison.Ordinal) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SearchType = SearchType.Image;
|
||||
}
|
||||
else if (string.Equals("\"object.item.videoItem\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SearchType = SearchType.Video;
|
||||
}
|
||||
else if (string.Equals("\"object.container.playlistContainer\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SearchType = SearchType.Playlist;
|
||||
}
|
||||
else if (string.Equals("\"object.container.album.musicAlbum\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SearchType = SearchType.MusicAlbum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -294,13 +294,13 @@ namespace MediaBrowser.Model.Dto
|
|||
public NameGuidPair[] GenreItems { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>The parent logo item id.</value>
|
||||
public string ParentLogoItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>The parent backdrop item id.</value>
|
||||
public string ParentBackdropItemId { get; set; }
|
||||
|
@ -318,7 +318,7 @@ namespace MediaBrowser.Model.Dto
|
|||
public int? LocalTrailerCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>The user data.</value>
|
||||
public UserItemDataDto UserData { get; set; }
|
||||
|
@ -506,7 +506,7 @@ namespace MediaBrowser.Model.Dto
|
|||
public string ParentLogoImageTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>The parent art item id.</value>
|
||||
public string ParentArtItemId { get; set; }
|
||||
|
@ -695,7 +695,7 @@ namespace MediaBrowser.Model.Dto
|
|||
public string ChannelPrimaryImageTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start date of the recording, in UTC.
|
||||
/// Gets or sets the start date of the recording, in UTC.
|
||||
/// </summary>
|
||||
public DateTime? StartDate { get; set; }
|
||||
|
||||
|
|
|
@ -12,6 +12,18 @@ namespace MediaBrowser.Model.Dto
|
|||
{
|
||||
public class MediaSourceInfo
|
||||
{
|
||||
public MediaSourceInfo()
|
||||
{
|
||||
Formats = Array.Empty<string>();
|
||||
MediaStreams = new List<MediaStream>();
|
||||
MediaAttachments = Array.Empty<MediaAttachment>();
|
||||
RequiredHttpHeaders = new Dictionary<string, string>();
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the media is remote.
|
||||
/// Differentiate internet url vs local network.
|
||||
/// </summary>
|
||||
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<string>();
|
||||
MediaStreams = new List<MediaStream>();
|
||||
MediaAttachments = Array.Empty<MediaAttachment>();
|
||||
RequiredHttpHeaders = new Dictionary<string, string>();
|
||||
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)
|
||||
|
|
|
@ -10,6 +10,15 @@ namespace MediaBrowser.Model.Dto
|
|||
{
|
||||
public class MetadataEditorInfo
|
||||
{
|
||||
public MetadataEditorInfo()
|
||||
{
|
||||
ParentalRatingOptions = Array.Empty<ParentalRating>();
|
||||
Countries = Array.Empty<CountryInfo>();
|
||||
Cultures = Array.Empty<CultureDto>();
|
||||
ExternalIdInfos = Array.Empty<ExternalIdInfo>();
|
||||
ContentTypeOptions = Array.Empty<NameValuePair>();
|
||||
}
|
||||
|
||||
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<ParentalRating>();
|
||||
Countries = Array.Empty<CountryInfo>();
|
||||
Cultures = Array.Empty<CultureDto>();
|
||||
ExternalIdInfos = Array.Empty<ExternalIdInfo>();
|
||||
ContentTypeOptions = Array.Empty<NameValuePair>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
14
MediaBrowser.Model/Dto/NameGuidPair.cs
Normal file
14
MediaBrowser.Model/Dto/NameGuidPair.cs
Normal file
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -19,11 +19,4 @@ namespace MediaBrowser.Model.Dto
|
|||
/// <value>The identifier.</value>
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
public class NameGuidPair
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public Guid Id { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,15 @@ namespace MediaBrowser.Model.Dto
|
|||
/// </summary>
|
||||
public class UserDto : IItemDto, IHasServerId
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserDto"/> class.
|
||||
/// </summary>
|
||||
public UserDto()
|
||||
{
|
||||
Configuration = new UserConfiguration();
|
||||
Policy = new UserPolicy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
|
@ -94,15 +103,6 @@ namespace MediaBrowser.Model.Dto
|
|||
/// <value>The primary image aspect ratio.</value>
|
||||
public double? PrimaryImageAspectRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserDto"/> class.
|
||||
/// </summary>
|
||||
public UserDto()
|
||||
{
|
||||
Configuration = new UserConfiguration();
|
||||
Policy = new UserPolicy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
16
MediaBrowser.Model/Entities/CollectionTypeOptions.cs
Normal file
16
MediaBrowser.Model/Entities/CollectionTypeOptions.cs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -84,7 +84,7 @@ namespace MediaBrowser.Model.Entities
|
|||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the video range.
|
||||
/// Gets the video range.
|
||||
/// </summary>
|
||||
/// <value>The video range.</value>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -487,6 +426,96 @@ namespace MediaBrowser.Model.Entities
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [supports external stream].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [supports external stream]; otherwise, <c>false</c>.</value>
|
||||
public bool SupportsExternalStream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the filename.
|
||||
/// </summary>
|
||||
/// <value>The filename.</value>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pixel format.
|
||||
/// </summary>
|
||||
/// <value>The pixel format.</value>
|
||||
public string PixelFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the level.
|
||||
/// </summary>
|
||||
/// <value>The level.</value>
|
||||
public double? Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this instance is anamorphic.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is anamorphic; otherwise, <c>false</c>.</value>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [supports external stream].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [supports external stream]; otherwise, <c>false</c>.</value>
|
||||
public bool SupportsExternalStream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the filename.
|
||||
/// </summary>
|
||||
/// <value>The filename.</value>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pixel format.
|
||||
/// </summary>
|
||||
/// <value>The pixel format.</value>
|
||||
public string PixelFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the level.
|
||||
/// </summary>
|
||||
/// <value>The level.</value>
|
||||
public double? Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is anamorphic.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is anamorphic; otherwise, <c>false</c>.</value>
|
||||
public bool? IsAnamorphic { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
#nullable disable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Model.Entities
|
||||
{
|
||||
public class PackageReviewInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the package id (database key) for this review.
|
||||
/// </summary>
|
||||
public int id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rating value.
|
||||
/// </summary>
|
||||
public int rating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not this review recommends this item.
|
||||
/// </summary>
|
||||
public bool recommend { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a short description of the review.
|
||||
/// </summary>
|
||||
public string title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the full review.
|
||||
/// </summary>
|
||||
public string review { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time of review.
|
||||
/// </summary>
|
||||
public DateTime timestamp { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace MediaBrowser.Model.Entities
|
||||
{
|
||||
|
@ -9,14 +10,50 @@ namespace MediaBrowser.Model.Entities
|
|||
public static class ProviderIdsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether [has provider identifier] [the specified instance].
|
||||
/// Gets a provider id.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="id">The provider id.</param>
|
||||
/// <returns><c>true</c> if a provider id with the given name was found; otherwise <c>false</c>.</returns>
|
||||
public static bool TryGetProviderId(this IHasProviderIds instance, string name, [MaybeNullWhen(false)] out string id)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
if (instance.ProviderIds == null)
|
||||
{
|
||||
id = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return instance.ProviderIds.TryGetValue(name, out id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a provider id.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <returns><c>true</c> if [has provider identifier] [the specified instance]; otherwise, <c>false</c>.</returns>
|
||||
public static bool HasProviderId(this IHasProviderIds instance, MetadataProvider provider)
|
||||
/// <param name="id">The provider id.</param>
|
||||
/// <returns><c>true</c> if a provider id with the given name was found; otherwise <c>false</c>.</returns>
|
||||
public static bool TryGetProviderId(this IHasProviderIds instance, MetadataProvider provider, [MaybeNullWhen(false)] out string id)
|
||||
{
|
||||
return !string.IsNullOrEmpty(instance.GetProviderId(provider.ToString()));
|
||||
return instance.TryGetProviderId(provider.ToString(), out id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a provider id.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
public static string? GetProviderId(this IHasProviderIds instance, string name)
|
||||
{
|
||||
instance.TryGetProviderId(name, out string? id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -30,28 +67,6 @@ namespace MediaBrowser.Model.Entities
|
|||
return instance.GetProviderId(provider.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a provider id.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
public static string? GetProviderId(this IHasProviderIds instance, string name)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
if (instance.ProviderIds == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
instance.ProviderIds.TryGetValue(name, out string? id);
|
||||
return string.IsNullOrEmpty(id) ? null : id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a provider id.
|
||||
/// </summary>
|
||||
|
@ -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
|
||||
{
|
||||
|
|
36
MediaBrowser.Model/Entities/SpecialFolder.cs
Normal file
36
MediaBrowser.Model/Entities/SpecialFolder.cs
Normal file
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -11,6 +11,14 @@ namespace MediaBrowser.Model.Entities
|
|||
/// </summary>
|
||||
public class VirtualFolderInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VirtualFolderInfo"/> class.
|
||||
/// </summary>
|
||||
public VirtualFolderInfo()
|
||||
{
|
||||
Locations = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
|
@ -27,18 +35,10 @@ namespace MediaBrowser.Model.Entities
|
|||
/// Gets or sets the type of the collection.
|
||||
/// </summary>
|
||||
/// <value>The type of the collection.</value>
|
||||
public string CollectionType { get; set; }
|
||||
public CollectionTypeOptions? CollectionType { get; set; }
|
||||
|
||||
public LibraryOptions LibraryOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VirtualFolderInfo"/> class.
|
||||
/// </summary>
|
||||
public VirtualFolderInfo()
|
||||
{
|
||||
Locations = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item identifier.
|
||||
/// </summary>
|
||||
|
|
|
@ -10,6 +10,11 @@ namespace MediaBrowser.Model.Globalization
|
|||
/// </summary>
|
||||
public class CultureDto
|
||||
{
|
||||
public CultureDto()
|
||||
{
|
||||
ThreeLetterISOLanguageNames = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
|
@ -29,7 +34,7 @@ namespace MediaBrowser.Model.Globalization
|
|||
public string TwoLetterISOLanguageName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the three letter ISO language.
|
||||
/// Gets the name of the three letter ISO language.
|
||||
/// </summary>
|
||||
/// <value>The name of the three letter ISO language.</value>
|
||||
public string ThreeLetterISOLanguageName
|
||||
|
@ -47,10 +52,5 @@ namespace MediaBrowser.Model.Globalization
|
|||
}
|
||||
|
||||
public string[] ThreeLetterISOLanguageNames { get; set; }
|
||||
|
||||
public CultureDto()
|
||||
{
|
||||
ThreeLetterISOLanguageNames = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,13 +155,16 @@ namespace MediaBrowser.Model.IO
|
|||
/// Gets the directories.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="recursive">if set to <c>true</c> [recursive].</param>
|
||||
/// <returns>IEnumerable<DirectoryInfo>.</returns>
|
||||
/// <param name="recursive">If set to <c>true</c> also searches in subdirectiories.</param>
|
||||
/// <returns>All found directories.</returns>
|
||||
IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the files.
|
||||
/// </summary>
|
||||
/// <param name="path">The path in which to search.</param>
|
||||
/// <param name="recursive">If set to <c>true</c> also searches in subdirectiories.</param>
|
||||
/// <returns>All found files.</returns>
|
||||
IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false);
|
||||
|
||||
IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive);
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public class BaseTimerInfoDto : IHasServerId
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the recording.
|
||||
/// Gets or sets the Id of the recording.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public string ExternalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ChannelId of the recording.
|
||||
/// Gets or sets the channel id of the recording.
|
||||
/// </summary>
|
||||
public Guid ChannelId { get; set; }
|
||||
|
||||
|
@ -39,7 +39,7 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public string ExternalChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ChannelName of the recording.
|
||||
/// Gets or sets the channel name of the recording.
|
||||
/// </summary>
|
||||
public string ChannelName { get; set; }
|
||||
|
||||
|
@ -58,22 +58,22 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public string ExternalProgramId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the recording.
|
||||
/// Gets or sets the name of the recording.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Description of the recording.
|
||||
/// Gets or sets the description of the recording.
|
||||
/// </summary>
|
||||
public string Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start date of the recording, in UTC.
|
||||
/// Gets or sets the start date of the recording, in UTC.
|
||||
/// </summary>
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end date of the recording, in UTC.
|
||||
/// Gets or sets the end date of the recording, in UTC.
|
||||
/// </summary>
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
|
@ -108,7 +108,7 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public bool IsPrePaddingRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>The parent backdrop item id.</value>
|
||||
public string ParentBackdropItemId { get; set; }
|
||||
|
|
58
MediaBrowser.Model/LiveTv/ListingsProviderInfo.cs
Normal file
58
MediaBrowser.Model/LiveTv/ListingsProviderInfo.cs
Normal file
|
@ -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<string>();
|
||||
EnableAllTuners = true;
|
||||
ChannelMappings = Array.Empty<NameValuePair>();
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
|
@ -11,6 +11,12 @@ namespace MediaBrowser.Model.LiveTv
|
|||
/// </summary>
|
||||
public class LiveTvChannelQuery
|
||||
{
|
||||
public LiveTvChannelQuery()
|
||||
{
|
||||
EnableUserData = true;
|
||||
SortBy = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the channel.
|
||||
/// </summary>
|
||||
|
@ -48,13 +54,13 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skips over a given number of items within the results. Use for paging.
|
||||
/// Gets or sets the start index. Used for paging.
|
||||
/// </summary>
|
||||
/// <value>The start index.</value>
|
||||
public int? StartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of items to return.
|
||||
/// Gets or sets the maximum number of items to return.
|
||||
/// </summary>
|
||||
/// <value>The limit.</value>
|
||||
public int? Limit { get; set; }
|
||||
|
@ -68,15 +74,15 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public bool EnableUserData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to specific whether to return news or not.
|
||||
/// Gets or sets a value whether to return news or not.
|
||||
/// </summary>
|
||||
/// <remarks>If set to null, all programs will be returned</remarks>
|
||||
/// <remarks>If set to <c>null</c>, all programs will be returned.</remarks>
|
||||
public bool? IsNews { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to specific whether to return movies or not.
|
||||
/// Gets or sets a value whether to return movies or not.
|
||||
/// </summary>
|
||||
/// <remarks>If set to null, all programs will be returned</remarks>
|
||||
/// <remarks>If set to <c>null</c>, all programs will be returned.</remarks>
|
||||
public bool? IsMovie { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -96,15 +102,9 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public string[] SortBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sort order to return results with.
|
||||
/// Gets or sets the sort order to return results with.
|
||||
/// </summary>
|
||||
/// <value>The sort order.</value>
|
||||
public SortOrder? SortOrder { get; set; }
|
||||
|
||||
public LiveTvChannelQuery()
|
||||
{
|
||||
EnableUserData = true;
|
||||
SortBy = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TunerHostInfo>();
|
||||
ListingProviders = Array.Empty<ListingsProviderInfo>();
|
||||
MediaLocationsCreated = Array.Empty<string>();
|
||||
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<TunerHostInfo>();
|
||||
ListingProviders = Array.Empty<ListingsProviderInfo>();
|
||||
MediaLocationsCreated = Array.Empty<string>();
|
||||
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<string>();
|
||||
EnableAllTuners = true;
|
||||
ChannelMappings = Array.Empty<NameValuePair>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ namespace MediaBrowser.Model.LiveTv
|
|||
/// </summary>
|
||||
public class LiveTvServiceInfo
|
||||
{
|
||||
public LiveTvServiceInfo()
|
||||
{
|
||||
Tuners = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
|
@ -53,10 +58,5 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public bool IsVisible { get; set; }
|
||||
|
||||
public string[] Tuners { get; set; }
|
||||
|
||||
public LiveTvServiceInfo()
|
||||
{
|
||||
Tuners = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,11 @@ namespace MediaBrowser.Model.LiveTv
|
|||
/// </summary>
|
||||
public class RecordingQuery
|
||||
{
|
||||
public RecordingQuery()
|
||||
{
|
||||
EnableTotalRecordCount = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channel identifier.
|
||||
/// </summary>
|
||||
|
@ -31,13 +36,13 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skips over a given number of items within the results. Use for paging.
|
||||
/// Gets or sets the start index. Use for paging.
|
||||
/// </summary>
|
||||
/// <value>The start index.</value>
|
||||
public int? StartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of items to return.
|
||||
/// Gets or sets the maximum number of items to return.
|
||||
/// </summary>
|
||||
/// <value>The limit.</value>
|
||||
public int? Limit { get; set; }
|
||||
|
@ -61,7 +66,7 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public string SeriesTimerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>The fields.</value>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,14 @@ using MediaBrowser.Model.Entities;
|
|||
|
||||
namespace MediaBrowser.Model.LiveTv
|
||||
{
|
||||
public enum KeepUntil
|
||||
{
|
||||
UntilDeleted,
|
||||
UntilSpaceNeeded,
|
||||
UntilWatched,
|
||||
UntilDate
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class SeriesTimerInfoDto.
|
||||
/// </summary>
|
||||
|
@ -83,12 +91,4 @@ namespace MediaBrowser.Model.LiveTv
|
|||
/// <value>The parent primary image tag.</value>
|
||||
public string ParentPrimaryImageTag { get; set; }
|
||||
}
|
||||
|
||||
public enum KeepUntil
|
||||
{
|
||||
UntilDeleted,
|
||||
UntilSpaceNeeded,
|
||||
UntilWatched,
|
||||
UntilDate
|
||||
}
|
||||
}
|
||||
|
|
38
MediaBrowser.Model/LiveTv/TunerHostInfo.cs
Normal file
38
MediaBrowser.Model/LiveTv/TunerHostInfo.cs
Normal file
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
@ -44,7 +44,7 @@
|
|||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<!-- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
|
|
|
@ -10,6 +10,17 @@ namespace MediaBrowser.Model.MediaInfo
|
|||
{
|
||||
public class MediaInfo : MediaSourceInfo, IHasProviderIds
|
||||
{
|
||||
public MediaInfo()
|
||||
{
|
||||
Chapters = Array.Empty<ChapterInfo>();
|
||||
Artists = Array.Empty<string>();
|
||||
AlbumArtists = Array.Empty<string>();
|
||||
Studios = Array.Empty<string>();
|
||||
Genres = Array.Empty<string>();
|
||||
People = Array.Empty<BaseItemPerson>();
|
||||
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public ChapterInfo[] Chapters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -69,16 +80,5 @@ namespace MediaBrowser.Model.MediaInfo
|
|||
/// </summary>
|
||||
/// <value>The overview.</value>
|
||||
public string Overview { get; set; }
|
||||
|
||||
public MediaInfo()
|
||||
{
|
||||
Chapters = Array.Empty<ChapterInfo>();
|
||||
Artists = Array.Empty<string>();
|
||||
AlbumArtists = Array.Empty<string>();
|
||||
Studios = Array.Empty<string>();
|
||||
Genres = Array.Empty<string>();
|
||||
People = Array.Empty<BaseItemPerson>();
|
||||
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user