mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 18:08:53 -07:00
Merge branch 'master' into usings
This commit is contained in:
commit
f31efce52d
@ -1,6 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1402
|
||||
#pragma warning disable SA1600
|
||||
#pragma warning disable SA1649
|
||||
|
||||
using System;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration;
|
||||
namespace Emby.Server.Implementations.AppBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class to hold common application paths used by both the Ui and Server.
|
||||
/// Provides a base class to hold common application paths used by both the UI and Server.
|
||||
/// This can be subclassed to add application-specific paths.
|
||||
/// </summary>
|
||||
public abstract class BaseApplicationPaths : IApplicationPaths
|
||||
@ -37,10 +37,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// <value>The program data path.</value>
|
||||
public string ProgramDataPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the web UI resources folder.
|
||||
/// </summary>
|
||||
/// <value>The web UI resources path.</value>
|
||||
/// <inheritdoc/>
|
||||
public string WebPath { get; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -235,11 +235,6 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
public int HttpsPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content root for the webhost.
|
||||
/// </summary>
|
||||
public string ContentRoot { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server configuration manager.
|
||||
/// </summary>
|
||||
@ -612,13 +607,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
DiscoverTypes();
|
||||
|
||||
await RegisterResources(serviceCollection, startupConfig).ConfigureAwait(false);
|
||||
|
||||
ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
|
||||
if (string.IsNullOrEmpty(ContentRoot))
|
||||
{
|
||||
ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
|
||||
}
|
||||
await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
|
||||
@ -649,9 +638,9 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers resources that classes will depend on
|
||||
/// Registers services/resources with the service collection that will be available via DI.
|
||||
/// </summary>
|
||||
protected async Task RegisterResources(IServiceCollection serviceCollection, IConfiguration startupConfig)
|
||||
protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig)
|
||||
{
|
||||
serviceCollection.AddMemoryCache();
|
||||
|
||||
@ -769,20 +758,8 @@ namespace Emby.Server.Implementations
|
||||
CertificateInfo = GetCertificateInfo(true);
|
||||
Certificate = GetCertificate(CertificateInfo);
|
||||
|
||||
HttpServer = new HttpListenerHost(
|
||||
this,
|
||||
LoggerFactory.CreateLogger<HttpListenerHost>(),
|
||||
ServerConfigurationManager,
|
||||
startupConfig,
|
||||
NetworkManager,
|
||||
JsonSerializer,
|
||||
XmlSerializer,
|
||||
CreateHttpListener())
|
||||
{
|
||||
GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading")
|
||||
};
|
||||
|
||||
serviceCollection.AddSingleton(HttpServer);
|
||||
serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>();
|
||||
serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
|
||||
|
||||
ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger<ImageProcessor>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
|
||||
serviceCollection.AddSingleton(ImageProcessor);
|
||||
@ -844,10 +821,15 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
|
||||
|
||||
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
||||
ChapterManager = new ChapterManager(ItemRepository);
|
||||
serviceCollection.AddSingleton(ChapterManager);
|
||||
|
||||
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
|
||||
EncodingManager = new MediaEncoder.EncodingManager(
|
||||
LoggerFactory.CreateLogger<MediaEncoder.EncodingManager>(),
|
||||
FileSystemManager,
|
||||
MediaEncoder,
|
||||
ChapterManager,
|
||||
LibraryManager);
|
||||
serviceCollection.AddSingleton(EncodingManager);
|
||||
|
||||
var activityLogRepo = GetActivityLogRepository();
|
||||
@ -890,6 +872,14 @@ namespace Emby.Server.Implementations
|
||||
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create services registered with the service container that need to be initialized at application startup.
|
||||
/// </summary>
|
||||
public void InitializeServices()
|
||||
{
|
||||
HttpServer = Resolve<IHttpServer>();
|
||||
}
|
||||
|
||||
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
||||
{
|
||||
// Distinct these to prevent users from reporting problems that aren't actually problems
|
||||
@ -1167,7 +1157,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
exportedTypes = ass.GetExportedTypes();
|
||||
}
|
||||
catch (TypeLoadException ex)
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
|
||||
continue;
|
||||
@ -1207,8 +1197,6 @@ namespace Emby.Server.Implementations
|
||||
});
|
||||
}
|
||||
|
||||
protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(LoggerFactory.CreateLogger<WebSocketSharpListener>());
|
||||
|
||||
private CertificateInfo GetCertificateInfo(bool generateCertificate)
|
||||
{
|
||||
// Custom cert
|
||||
|
@ -1,51 +1,48 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Browser
|
||||
{
|
||||
/// <summary>
|
||||
/// Class BrowserLauncher.
|
||||
/// Assists in opening application URLs in an external browser.
|
||||
/// </summary>
|
||||
public static class BrowserLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// Opens the dashboard page.
|
||||
/// </summary>
|
||||
/// <param name="page">The page.</param>
|
||||
/// <param name="appHost">The app host.</param>
|
||||
private static void OpenDashboardPage(string page, IServerApplicationHost appHost)
|
||||
{
|
||||
var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page;
|
||||
|
||||
OpenUrl(appHost, url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the web client.
|
||||
/// Opens the home page of the web client.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The app host.</param>
|
||||
public static void OpenWebApp(IServerApplicationHost appHost)
|
||||
{
|
||||
OpenDashboardPage("index.html", appHost);
|
||||
TryOpenUrl(appHost, "/web/index.html");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the URL.
|
||||
/// Opens the swagger API page.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The application host instance.</param>
|
||||
/// <param name="appHost">The app host.</param>
|
||||
public static void OpenSwaggerPage(IServerApplicationHost appHost)
|
||||
{
|
||||
TryOpenUrl(appHost, "/swagger/index.html");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="url">The URL.</param>
|
||||
private static void OpenUrl(IServerApplicationHost appHost, string url)
|
||||
private static void TryOpenUrl(IServerApplicationHost appHost, string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
appHost.LaunchUrl(url);
|
||||
string baseUrl = appHost.GetLocalApiUrl("localhost");
|
||||
appHost.LaunchUrl(baseUrl + url);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = appHost.Resolve<ILogger>();
|
||||
logger?.LogError(ex, "Failed to open browser window with URL {URL}", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using MediaBrowser.Providers.Music;
|
||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class containing the default configuration options for the web server.
|
||||
/// </summary>
|
||||
public static class ConfigurationOptions
|
||||
{
|
||||
public static Dictionary<string, string> Configuration => new Dictionary<string, string>
|
||||
/// <summary>
|
||||
/// Gets a new copy of the default configuration options.
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> DefaultConfiguration => new Dictionary<string, string>
|
||||
{
|
||||
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
|
||||
{ HostWebClientKey, bool.TrueString },
|
||||
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
||||
|
@ -2006,7 +2006,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// <summary>
|
||||
/// Saves the chapters.
|
||||
/// </summary>
|
||||
public void SaveChapters(Guid id, List<ChapterInfo> chapters)
|
||||
public void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
@ -2035,22 +2035,24 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertChapters(byte[] idBlob, List<ChapterInfo> chapters, IDatabaseConnection db)
|
||||
private void InsertChapters(byte[] idBlob, IReadOnlyList<ChapterInfo> chapters, IDatabaseConnection db)
|
||||
{
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
var chapterIndex = 0;
|
||||
|
||||
const string StartInsertText = "insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values ";
|
||||
var insertText = new StringBuilder(StartInsertText, 256);
|
||||
|
||||
while (startIndex < chapters.Count)
|
||||
{
|
||||
var insertText = new StringBuilder("insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values ");
|
||||
|
||||
var endIndex = Math.Min(chapters.Count, startIndex + limit);
|
||||
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
insertText.Length -= 1; // Remove last ,
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
@ -2077,6 +2079,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
insertText.Length = StartInsertText.Length;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,9 @@ using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Browser;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
@ -11,10 +13,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// </summary>
|
||||
public sealed class StartupWizard : IServerEntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The app host.
|
||||
/// </summary>
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IConfiguration _appConfig;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
/// <summary>
|
||||
@ -22,9 +22,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// </summary>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
public StartupWizard(IServerApplicationHost appHost, IServerConfigurationManager config)
|
||||
public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_appConfig = appConfig;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
@ -36,7 +37,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (!_config.Configuration.IsStartupWizardCompleted)
|
||||
if (!_appConfig.HostWebClient())
|
||||
{
|
||||
BrowserLauncher.OpenSwaggerPage(_appHost);
|
||||
}
|
||||
else if (!_config.Configuration.IsStartupWizardCompleted)
|
||||
{
|
||||
BrowserLauncher.OpenWebApp(_appHost);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -29,6 +30,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
public class HttpListenerHost : IHttpServer, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The key for a setting that specifies the default redirect path
|
||||
/// to use for requests where the URL base prefix is invalid or missing.
|
||||
/// </summary>
|
||||
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly INetworkManager _networkManager;
|
||||
@ -52,12 +59,13 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
INetworkManager networkManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IXmlSerializer xmlSerializer,
|
||||
IHttpListener socketListener)
|
||||
IHttpListener socketListener,
|
||||
ILocalizationManager localizationManager)
|
||||
{
|
||||
_appHost = applicationHost;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
|
||||
_defaultRedirectPath = configuration[DefaultRedirectKey];
|
||||
_baseUrlPrefix = _config.Configuration.BaseUrl;
|
||||
_networkManager = networkManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
@ -69,6 +77,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
Instance = this;
|
||||
ResponseFilters = Array.Empty<Action<IRequest, HttpResponse, object>>();
|
||||
GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
|
||||
}
|
||||
|
||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"Albums": "Албуми",
|
||||
"AppDeviceValues": "Програма: {0}, устройство: {1}",
|
||||
"AppDeviceValues": "Програма: {0}, Устройство: {1}",
|
||||
"Application": "Програма",
|
||||
"Artists": "Изпълнители",
|
||||
"Artists": "Артисти",
|
||||
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
|
||||
"Books": "Книги",
|
||||
"CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}",
|
||||
@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} спря {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека",
|
||||
"ValueSpecialEpisodeName": "Специални - {0}",
|
||||
"VersionNumber": "Версия {0}"
|
||||
"VersionNumber": "Версия {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи поднадписи, на база конфигурацията за мета-данни.",
|
||||
"TaskDownloadMissingSubtitles": "Изтегляне на липсващи поднадписи",
|
||||
"TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.",
|
||||
"TaskRefreshChannels": "Обновяване на Канали",
|
||||
"TaskCleanTranscodeDescription": "Изтрива прекодирани файлове по-стари от един ден.",
|
||||
"TaskCleanTranscode": "Изчиства директорията за прекодиране",
|
||||
"TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.",
|
||||
"TaskUpdatePlugins": "Актуализира добавките",
|
||||
"TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.",
|
||||
"TaskRefreshPeople": "Обновяване на участниците",
|
||||
"TaskCleanLogsDescription": "Изтрива лог файлове по-стари от {0} дни.",
|
||||
"TaskCleanLogs": "Изчисти директорията с логове",
|
||||
"TaskRefreshLibraryDescription": "Сканира Вашата библиотека с медия за нови файлове и обновява мета-данните.",
|
||||
"TaskRefreshLibrary": "Сканиране на библиотеката с медия",
|
||||
"TaskRefreshChapterImagesDescription": "Създава иконки за видеа, които имат епизоди.",
|
||||
"TaskRefreshChapterImages": "Извличане на изображения за епизода",
|
||||
"TaskCleanCacheDescription": "Изтриване на ненужните от системата файлове.",
|
||||
"TaskCleanCache": "Изчистване на Кеш-директорията",
|
||||
"TasksChannelsCategory": "Интернет Канали",
|
||||
"TasksApplicationCategory": "Приложение",
|
||||
"TasksLibraryCategory": "Библиотека",
|
||||
"TasksMaintenanceCategory": "Поддръжка"
|
||||
}
|
||||
|
@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
|
||||
"ValueSpecialEpisodeName": "Spécial - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TasksChannelsCategory": "Chaines en ligne",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
|
||||
"TaskDownloadMissingSubtitles": "Télécharge les sous-titres manquant",
|
||||
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
|
||||
"TaskRefreshChannels": "Rafraîchit les chaines",
|
||||
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
|
||||
"TaskCleanTranscode": "Nettoie les dossier des transcodages",
|
||||
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des plugins configurés pour être mis à jour automatiquement.",
|
||||
"TaskUpdatePlugins": "Mettre à jour les plugins",
|
||||
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et directeurs dans votre bibliothèque.",
|
||||
"TaskRefreshPeople": "Rafraîchit les acteurs",
|
||||
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
|
||||
"TaskCleanLogs": "Nettoie le répertoire des journaux",
|
||||
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
|
||||
"TaskRefreshLibrary": "Scanne toute les Bibliothèques",
|
||||
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
|
||||
"TaskRefreshChapterImages": "Extrait les images de chapitre",
|
||||
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
|
||||
"TaskCleanCache": "Vider le répertoire cache",
|
||||
"TasksApplicationCategory": "Application",
|
||||
"TasksLibraryCategory": "Bibliothèque",
|
||||
"TasksMaintenanceCategory": "Maintenance"
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
"Artists": "Artisti",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticato con successo",
|
||||
"Books": "Libri",
|
||||
"CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera da {0}",
|
||||
"CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera dal device {0}",
|
||||
"Channels": "Canali",
|
||||
"ChapterNameValue": "Capitolo {0}",
|
||||
"Collections": "Collezioni",
|
||||
@ -15,7 +15,7 @@
|
||||
"Favorites": "Preferiti",
|
||||
"Folders": "Cartelle",
|
||||
"Genres": "Generi",
|
||||
"HeaderAlbumArtists": "Artisti dell' Album",
|
||||
"HeaderAlbumArtists": "Artisti degli Album",
|
||||
"HeaderCameraUploads": "Caricamenti Fotocamera",
|
||||
"HeaderContinueWatching": "Continua a guardare",
|
||||
"HeaderFavoriteAlbums": "Album Preferiti",
|
||||
@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
|
||||
"ValueSpecialEpisodeName": "Speciale - {0}",
|
||||
"VersionNumber": "Versione {0}"
|
||||
"VersionNumber": "Versione {0}",
|
||||
"TaskRefreshChannelsDescription": "Aggiorna le informazioni dei canali Internet.",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Cerca su internet i sottotitoli mancanti basandosi sulle configurazioni dei metadati.",
|
||||
"TaskDownloadMissingSubtitles": "Scarica i sottotitoli mancanti",
|
||||
"TaskRefreshChannels": "Aggiorna i canali",
|
||||
"TaskCleanTranscodeDescription": "Cancella i file di transcode più vecchi di un giorno.",
|
||||
"TaskCleanTranscode": "Svuota la cartella del transcoding",
|
||||
"TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin che sono stati configurati per essere aggiornati contemporaneamente.",
|
||||
"TaskUpdatePlugins": "Aggiorna i Plugin",
|
||||
"TaskRefreshPeopleDescription": "Aggiorna i metadati per gli attori e registi nella tua libreria multimediale.",
|
||||
"TaskRefreshPeople": "Aggiorna persone",
|
||||
"TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.",
|
||||
"TaskCleanLogs": "Pulisci la cartella dei log",
|
||||
"TaskRefreshLibraryDescription": "Analizza la tua libreria multimediale per nuovi file e rinnova i metadati.",
|
||||
"TaskRefreshLibrary": "Analizza la libreria dei contenuti multimediali",
|
||||
"TaskRefreshChapterImagesDescription": "Crea le thumbnail per i video che hanno capitoli.",
|
||||
"TaskRefreshChapterImages": "Estrai immagini capitolo",
|
||||
"TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.",
|
||||
"TaskCleanCache": "Pulisci la directory della cache",
|
||||
"TasksChannelsCategory": "Canali su Internet",
|
||||
"TasksApplicationCategory": "Applicazione",
|
||||
"TasksLibraryCategory": "Libreria",
|
||||
"TasksMaintenanceCategory": "Manutenzione"
|
||||
}
|
||||
|
@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} parou de reproduzir {1} em {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca de mídia",
|
||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||
"VersionNumber": "Versão {0}"
|
||||
"VersionNumber": "Versão {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas faltando baseado na configuração de metadados.",
|
||||
"TaskDownloadMissingSubtitles": "Baixar legendas que estão faltando",
|
||||
"TaskRefreshChannelsDescription": "Atualizar informação de canais da internet .",
|
||||
"TaskRefreshChannels": "Atualizar Canais",
|
||||
"TaskCleanTranscodeDescription": "Deletar arquivos de transcodificação com mais de um dia de criação.",
|
||||
"TaskCleanTranscode": "Limpar pasta de transcodificação",
|
||||
"TaskUpdatePluginsDescription": "Baixa e instala atualizações para plugins que estão configurados para atualizar automaticamente.",
|
||||
"TaskUpdatePlugins": "Atualizar Plugins",
|
||||
"TaskRefreshPeopleDescription": "Atualiza metadados para atores e diretores na sua biblioteca de mídia.",
|
||||
"TaskRefreshPeople": "Atualizar pessoas",
|
||||
"TaskCleanLogsDescription": "Deletar arquivos temporários com mais de {0} dias.",
|
||||
"TaskCleanLogs": "Limpar pasta de logs",
|
||||
"TaskRefreshLibraryDescription": "Escaneie a sua biblioteca de mídia para arquivos novos e atualize os metadados.",
|
||||
"TaskRefreshLibrary": "Escanear a Biblioteca de Mídia",
|
||||
"TaskRefreshChapterImagesDescription": "Criar miniaturas para vídeos que tem capítulos.",
|
||||
"TaskRefreshChapterImages": "Extrair imagens dos capítulos",
|
||||
"TaskCleanCacheDescription": "Deletar arquivos temporários que não são mais necessários para o sistema.",
|
||||
"TaskCleanCache": "Limpar Arquivos Temporários",
|
||||
"TasksChannelsCategory": "Canais da Internet",
|
||||
"TasksApplicationCategory": "Aplicativo",
|
||||
"TasksLibraryCategory": "Biblioteca",
|
||||
"TasksMaintenanceCategory": "Manutenção"
|
||||
}
|
||||
|
@ -26,14 +26,20 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
private readonly IChapterManager _chapterManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The first chapter ticks.
|
||||
/// </summary>
|
||||
private static readonly long _firstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
|
||||
|
||||
public EncodingManager(
|
||||
ILogger<EncodingManager> logger,
|
||||
IFileSystem fileSystem,
|
||||
ILoggerFactory loggerFactory,
|
||||
IMediaEncoder encoder,
|
||||
IChapterManager chapterManager, ILibraryManager libraryManager)
|
||||
IChapterManager chapterManager,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = loggerFactory.CreateLogger(nameof(EncodingManager));
|
||||
_encoder = encoder;
|
||||
_chapterManager = chapterManager;
|
||||
_libraryManager = libraryManager;
|
||||
@ -97,12 +103,7 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
return video.DefaultVideoStreamIndex.HasValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The first chapter ticks
|
||||
/// </summary>
|
||||
private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
|
||||
|
||||
public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
|
||||
public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!IsEligibleForChapterImageExtraction(video))
|
||||
{
|
||||
@ -135,7 +136,7 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
try
|
||||
{
|
||||
// Add some time for the first chapter to make sure we don't end up with a black image
|
||||
var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
|
||||
var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(_firstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
|
||||
|
||||
var protocol = MediaProtocol.File;
|
||||
|
||||
@ -152,9 +153,9 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
{
|
||||
_fileSystem.DeleteFile(tempFile);
|
||||
}
|
||||
catch
|
||||
catch (IOException ex)
|
||||
{
|
||||
|
||||
_logger.LogError(ex, "Error deleting temporary chapter image encoding file {Path}", tempFile);
|
||||
}
|
||||
|
||||
chapter.ImagePath = path;
|
||||
@ -184,7 +185,7 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
|
||||
if (saveChapters && changesMade)
|
||||
{
|
||||
_chapterManager.SaveChapters(video.Id.ToString(), chapters);
|
||||
_chapterManager.SaveChapters(video.Id, chapters);
|
||||
}
|
||||
|
||||
DeleteDeadImages(currentImages, chapters);
|
||||
@ -199,22 +200,21 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
return Path.Combine(GetChapterImagesPath(video), filename);
|
||||
}
|
||||
|
||||
private static List<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
|
||||
private static IReadOnlyList<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
|
||||
{
|
||||
var path = GetChapterImagesPath(video);
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return new List<string>();
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return directoryService.GetFilePaths(path)
|
||||
.ToList();
|
||||
return directoryService.GetFilePaths(path);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new List<string>();
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
|
||||
foreach (var image in deadImages)
|
||||
{
|
||||
_logger.LogDebug("Deleting dead chapter image {path}", image);
|
||||
_logger.LogDebug("Deleting dead chapter image {Path}", image);
|
||||
|
||||
try
|
||||
{
|
||||
@ -235,7 +235,7 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting {path}.", image);
|
||||
_logger.LogError(ex, "Error deleting {Path}.", image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,14 @@ using System.Threading.Tasks;
|
||||
using CommandLine;
|
||||
using Emby.Drawing;
|
||||
using Emby.Server.Implementations;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Emby.Server.Implementations.IO;
|
||||
using Emby.Server.Implementations.Networking;
|
||||
using Jellyfin.Drawing.Skia;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.WebDashboard.Api;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -109,9 +112,10 @@ namespace Jellyfin.Server
|
||||
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
|
||||
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
|
||||
|
||||
// Create an instance of the application configuration to use for application startup
|
||||
await InitLoggingConfigFile(appPaths).ConfigureAwait(false);
|
||||
IConfiguration startupConfig = CreateAppConfiguration(appPaths);
|
||||
|
||||
// Create an instance of the application configuration to use for application startup
|
||||
IConfiguration startupConfig = CreateAppConfiguration(options, appPaths);
|
||||
|
||||
// Initialize logging framework
|
||||
InitializeLoggingFramework(startupConfig, appPaths);
|
||||
@ -180,15 +184,31 @@ namespace Jellyfin.Server
|
||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||
GetImageEncoder(appPaths),
|
||||
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()));
|
||||
|
||||
try
|
||||
{
|
||||
// If hosting the web client, validate the client content path
|
||||
if (startupConfig.HostWebClient())
|
||||
{
|
||||
string webContentPath = DashboardService.GetDashboardUIPath(startupConfig, appHost.ServerConfigurationManager);
|
||||
if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The server is expected to host the web client, but the provided content directory is either " +
|
||||
$"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
|
||||
"server, you may set the '--nowebclient' command line flag, or set" +
|
||||
$"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
|
||||
}
|
||||
}
|
||||
|
||||
ServiceCollection serviceCollection = new ServiceCollection();
|
||||
await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false);
|
||||
|
||||
var webHost = CreateWebHostBuilder(appHost, serviceCollection, appPaths).Build();
|
||||
var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
||||
|
||||
// A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection.
|
||||
// Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
|
||||
appHost.ServiceProvider = webHost.Services;
|
||||
appHost.InitializeServices();
|
||||
appHost.FindParts();
|
||||
Migrations.MigrationRunner.Run(appHost, _loggerFactory);
|
||||
|
||||
@ -230,7 +250,12 @@ namespace Jellyfin.Server
|
||||
}
|
||||
}
|
||||
|
||||
private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection, IApplicationPaths appPaths)
|
||||
private static IWebHostBuilder CreateWebHostBuilder(
|
||||
ApplicationHost appHost,
|
||||
IServiceCollection serviceCollection,
|
||||
StartupOptions commandLineOpts,
|
||||
IConfiguration startupConfig,
|
||||
IApplicationPaths appPaths)
|
||||
{
|
||||
return new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
@ -270,9 +295,8 @@ namespace Jellyfin.Server
|
||||
}
|
||||
}
|
||||
})
|
||||
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths))
|
||||
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig))
|
||||
.UseSerilog()
|
||||
.UseContentRoot(appHost.ContentRoot)
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
// Merge the external ServiceCollection into ASP.NET DI
|
||||
@ -395,9 +419,8 @@ namespace Jellyfin.Server
|
||||
// webDir
|
||||
// IF --webdir
|
||||
// ELSE IF $JELLYFIN_WEB_DIR
|
||||
// ELSE use <bindir>/jellyfin-web
|
||||
// ELSE <bindir>/jellyfin-web
|
||||
var webDir = options.WebDir;
|
||||
|
||||
if (string.IsNullOrEmpty(webDir))
|
||||
{
|
||||
webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR");
|
||||
@ -468,21 +491,33 @@ namespace Jellyfin.Server
|
||||
await resource.CopyToAsync(dst).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static IConfiguration CreateAppConfiguration(IApplicationPaths appPaths)
|
||||
private static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
|
||||
{
|
||||
return new ConfigurationBuilder()
|
||||
.ConfigureAppConfiguration(appPaths)
|
||||
.ConfigureAppConfiguration(commandLineOpts, appPaths)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths)
|
||||
private static IConfigurationBuilder ConfigureAppConfiguration(
|
||||
this IConfigurationBuilder config,
|
||||
StartupOptions commandLineOpts,
|
||||
IApplicationPaths appPaths,
|
||||
IConfiguration? startupConfig = null)
|
||||
{
|
||||
// Use the swagger API page as the default redirect path if not hosting the web client
|
||||
var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration;
|
||||
if (startupConfig != null && !startupConfig.HostWebClient())
|
||||
{
|
||||
inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html";
|
||||
}
|
||||
|
||||
return config
|
||||
.SetBasePath(appPaths.ConfigurationDirectoryPath)
|
||||
.AddInMemoryCollection(ConfigurationOptions.Configuration)
|
||||
.AddInMemoryCollection(inMemoryDefaultConfig)
|
||||
.AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true)
|
||||
.AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true)
|
||||
.AddEnvironmentVariables("JELLYFIN_");
|
||||
.AddEnvironmentVariables("JELLYFIN_")
|
||||
.AddInMemoryCollection(commandLineOpts.ConvertToConfig());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
11
Jellyfin.Server/Properties/launchSettings.json
Normal file
11
Jellyfin.Server/Properties/launchSettings.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Jellyfin.Server": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"Jellyfin.Server (nowebclient)": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "--nowebclient"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using CommandLine;
|
||||
using Emby.Server.Implementations;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
|
||||
namespace Jellyfin.Server
|
||||
{
|
||||
@ -15,6 +18,12 @@ namespace Jellyfin.Server
|
||||
[Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")]
|
||||
public string? DataDir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the server should not host the web client.
|
||||
/// </summary>
|
||||
[Option("nowebclient", Required = false, HelpText = "Indicates that the web server should not host the web client.")]
|
||||
public bool NoWebClient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path to the web directory.
|
||||
/// </summary>
|
||||
@ -66,5 +75,21 @@ namespace Jellyfin.Server
|
||||
/// <inheritdoc />
|
||||
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
|
||||
public string? RestartArgs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command line options as a dictionary that can be used in the .NET configuration system.
|
||||
/// </summary>
|
||||
/// <returns>The configuration dictionary.</returns>
|
||||
public Dictionary<string, string> ConvertToConfig()
|
||||
{
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (NoWebClient)
|
||||
{
|
||||
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
namespace MediaBrowser.Common.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
@ -12,9 +14,12 @@ namespace MediaBrowser.Common.Configuration
|
||||
string ProgramDataPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the web UI resources folder
|
||||
/// Gets the path to the web UI resources folder.
|
||||
/// </summary>
|
||||
/// <value>The web UI resources path.</value>
|
||||
/// <remarks>
|
||||
/// This value is not relevant if the server is configured to not host any static web content. Additionally,
|
||||
/// the value for <see cref="ServerConfiguration.DashboardSourcePath"/> takes precedence over this one.
|
||||
/// </remarks>
|
||||
string WebPath { get; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,16 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Chapters
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface IChapterManager
|
||||
/// Interface IChapterManager.
|
||||
/// </summary>
|
||||
public interface IChapterManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves the chapters.
|
||||
/// </summary>
|
||||
void SaveChapters(string itemId, List<ChapterInfo> chapters);
|
||||
void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@ -28,7 +30,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
public class Folder : BaseItem
|
||||
{
|
||||
public static IUserManager UserManager { get; set; }
|
||||
public static IUserViewManager UserViewManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -620,7 +621,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
EnableImages = false
|
||||
}
|
||||
|
||||
}).TotalRecordCount;
|
||||
}
|
||||
|
||||
@ -1713,7 +1713,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
EnableImages = false
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
double unplayedCount = unplayedQueryResult.TotalRecordCount;
|
||||
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface
|
||||
/// Marker interface.
|
||||
/// </summary>
|
||||
public interface IItemByName
|
||||
{
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace MediaBrowser.Controller.Extensions
|
||||
@ -7,6 +8,11 @@ namespace MediaBrowser.Controller.Extensions
|
||||
/// </summary>
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The key for a setting that indicates whether the application should host web client content.
|
||||
/// </summary>
|
||||
public const string HostWebClientKey = "hostwebclient";
|
||||
|
||||
/// <summary>
|
||||
/// The key for the FFmpeg probe size option.
|
||||
/// </summary>
|
||||
@ -22,6 +28,15 @@ namespace MediaBrowser.Controller.Extensions
|
||||
/// </summary>
|
||||
public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates";
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration to retrieve the value from.</param>
|
||||
/// <returns>The parsed config value.</returns>
|
||||
/// <exception cref="FormatException">The config value is not a valid bool string. See <see cref="bool.Parse(string)"/>.</exception>
|
||||
public static bool HostWebClient(this IConfiguration configuration)
|
||||
=> configuration.GetValue<bool>(HostWebClientKey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FFmpeg probe size from the <see cref="IConfiguration" />.
|
||||
/// </summary>
|
||||
|
@ -82,6 +82,11 @@ namespace MediaBrowser.Controller
|
||||
/// <returns>The local API URL.</returns>
|
||||
string GetLocalApiUrl(IPAddress address);
|
||||
|
||||
/// <summary>
|
||||
/// Open a URL in an external browser window.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to open.</param>
|
||||
/// <exception cref="NotSupportedException"><see cref="CanLaunchWebBrowser"/> is false.</exception>
|
||||
void LaunchUrl(string url);
|
||||
|
||||
void EnableLoopback(string appName);
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -12,6 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
/// <summary>
|
||||
/// Refreshes the chapter images.
|
||||
/// </summary>
|
||||
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
|
||||
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.Persistence
|
||||
/// <summary>
|
||||
/// Saves the chapters.
|
||||
/// </summary>
|
||||
void SaveChapters(Guid id, List<ChapterInfo> chapters);
|
||||
void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the media streams.
|
||||
|
@ -66,12 +66,10 @@ namespace MediaBrowser.Controller.Providers
|
||||
return file;
|
||||
}
|
||||
|
||||
public List<string> GetFilePaths(string path)
|
||||
{
|
||||
return GetFilePaths(path, false);
|
||||
}
|
||||
public IReadOnlyList<string> GetFilePaths(string path)
|
||||
=> GetFilePaths(path, false);
|
||||
|
||||
public List<string> GetFilePaths(string path, bool clearCache)
|
||||
public IReadOnlyList<string> GetFilePaths(string path, bool clearCache)
|
||||
{
|
||||
if (clearCache || !_filePathCache.TryGetValue(path, out List<string> result))
|
||||
{
|
||||
|
@ -11,8 +11,8 @@ namespace MediaBrowser.Controller.Providers
|
||||
|
||||
FileSystemMetadata GetFile(string path);
|
||||
|
||||
List<string> GetFilePaths(string path);
|
||||
IReadOnlyList<string> GetFilePaths(string path);
|
||||
|
||||
List<string> GetFilePaths(string path, bool clearCache);
|
||||
IReadOnlyList<string> GetFilePaths(string path, bool clearCache);
|
||||
}
|
||||
}
|
||||
|
@ -429,7 +429,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
}
|
||||
|
||||
return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
|
||||
return new ProbeResultNormalizer(_logger, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@ -19,13 +18,11 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, ILocalizationManager localization)
|
||||
public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
@ -40,7 +37,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
FFProbeHelpers.NormalizeFFProbeResult(data);
|
||||
SetSize(data, info);
|
||||
|
||||
var internalStreams = data.Streams ?? new MediaStreamInfo[] { };
|
||||
var internalStreams = data.Streams ?? Array.Empty<MediaStreamInfo>();
|
||||
|
||||
info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.Format))
|
||||
.Where(i => i != null)
|
||||
|
@ -148,9 +148,9 @@ namespace MediaBrowser.Model.Configuration
|
||||
public bool EnableDashboardResponseCaching { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows the dashboard to be served from a custom path.
|
||||
/// Gets or sets a custom path to serve the dashboard from.
|
||||
/// </summary>
|
||||
/// <value>The dashboard source path.</value>
|
||||
/// <value>The dashboard source path, or null if the default path should be used.</value>
|
||||
public string DashboardSourcePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,36 +1,26 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Chapters;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Providers.Chapters
|
||||
{
|
||||
public class ChapterManager : IChapterManager
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
public ChapterManager(
|
||||
ILibraryManager libraryManager,
|
||||
ILoggerFactory loggerFactory,
|
||||
IServerConfigurationManager config,
|
||||
IItemRepository itemRepo)
|
||||
public ChapterManager(IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = loggerFactory.CreateLogger(nameof(ChapterManager));
|
||||
_config = config;
|
||||
_itemRepo = itemRepo;
|
||||
}
|
||||
|
||||
public void SaveChapters(string itemId, List<ChapterInfo> chapters)
|
||||
/// <inheritdoc />
|
||||
public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
|
||||
{
|
||||
_itemRepo.SaveChapters(new Guid(itemId), chapters);
|
||||
_itemRepo.SaveChapters(itemId, chapters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@ -897,7 +901,10 @@ namespace MediaBrowser.Providers.Manager
|
||||
return new ExternalUrl
|
||||
{
|
||||
Name = i.Name,
|
||||
Url = string.Format(i.UrlFormatString, value)
|
||||
Url = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
i.UrlFormatString,
|
||||
value)
|
||||
};
|
||||
|
||||
}).Where(i => i != null).Concat(item.GetRelatedUrls());
|
||||
@ -911,11 +918,10 @@ namespace MediaBrowser.Providers.Manager
|
||||
Name = i.Name,
|
||||
Key = i.Key,
|
||||
UrlFormatString = i.UrlFormatString
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private Dictionary<Guid, double> _activeRefreshes = new Dictionary<Guid, double>();
|
||||
private ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
|
||||
|
||||
public Dictionary<Guid, Guid> GetRefreshQueue()
|
||||
{
|
||||
@ -927,66 +933,54 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
dict[item.Item1] = item.Item1;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnRefreshStart(BaseItem item)
|
||||
{
|
||||
//_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
var id = item.Id;
|
||||
|
||||
lock (_activeRefreshes)
|
||||
{
|
||||
_activeRefreshes[id] = 0;
|
||||
}
|
||||
|
||||
_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
_activeRefreshes[item.Id] = 0;
|
||||
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
||||
}
|
||||
|
||||
public void OnRefreshComplete(BaseItem item)
|
||||
{
|
||||
//_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
lock (_activeRefreshes)
|
||||
{
|
||||
_activeRefreshes.Remove(item.Id);
|
||||
}
|
||||
_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
_activeRefreshes.Remove(item.Id, out _);
|
||||
|
||||
RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
||||
}
|
||||
|
||||
public double? GetRefreshProgress(Guid id)
|
||||
{
|
||||
lock (_activeRefreshes)
|
||||
if (_activeRefreshes.TryGetValue(id, out double value))
|
||||
{
|
||||
if (_activeRefreshes.TryGetValue(id, out double value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void OnRefreshProgress(BaseItem item, double progress)
|
||||
{
|
||||
//_logger.LogInformation("OnRefreshProgress {0} {1}", item.Id.ToString("N", CultureInfo.InvariantCulture), progress);
|
||||
var id = item.Id;
|
||||
_logger.LogInformation("OnRefreshProgress {0} {1}", id.ToString("N", CultureInfo.InvariantCulture), progress);
|
||||
|
||||
lock (_activeRefreshes)
|
||||
{
|
||||
if (_activeRefreshes.ContainsKey(id))
|
||||
{
|
||||
_activeRefreshes[id] = progress;
|
||||
// TODO: Need to hunt down the conditions for this happening
|
||||
_activeRefreshes.AddOrUpdate(
|
||||
id,
|
||||
(_) => throw new Exception(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Cannot update refresh progress of item '{0}' ({1}) because a refresh for this item is not running",
|
||||
item.GetType().Name,
|
||||
item.Id.ToString("N", CultureInfo.InvariantCulture))),
|
||||
(_, __) => progress);
|
||||
|
||||
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Need to hunt down the conditions for this happening
|
||||
//throw new Exception(string.Format("Refresh for item {0} {1} is not in progress", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture)));
|
||||
}
|
||||
}
|
||||
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
|
||||
}
|
||||
|
||||
private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
|
||||
@ -1040,10 +1034,9 @@ namespace MediaBrowser.Providers.Manager
|
||||
// Try to throttle this a little bit.
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
|
||||
var artist = item as MusicArtist;
|
||||
var task = artist == null
|
||||
? RefreshItem(item, refreshItem.Item2, cancellationToken)
|
||||
: RefreshArtist(artist, refreshItem.Item2, cancellationToken);
|
||||
var task = item is MusicArtist artist
|
||||
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
|
||||
: RefreshItem(item, refreshItem.Item2, cancellationToken);
|
||||
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
@ -1125,8 +1118,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
return RefreshItem(item, options, cancellationToken);
|
||||
}
|
||||
|
@ -24,6 +24,18 @@
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Plugins\AudioDb\Configuration\config.html" />
|
||||
<EmbeddedResource Include="Plugins\AudioDb\Configuration\config.html" />
|
||||
|
@ -194,7 +194,19 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
FetchShortcutInfo(item);
|
||||
}
|
||||
|
||||
var prober = new FFProbeVideoInfo(_logger, _mediaSourceManager, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager, _chapterManager, _libraryManager);
|
||||
var prober = new FFProbeVideoInfo(
|
||||
_logger,
|
||||
_mediaSourceManager,
|
||||
_mediaEncoder,
|
||||
_itemRepo,
|
||||
_blurayExaminer,
|
||||
_localization,
|
||||
_encodingManager,
|
||||
_fileSystem,
|
||||
_config,
|
||||
_subtitleManager,
|
||||
_chapterManager,
|
||||
_libraryManager);
|
||||
|
||||
return prober.ProbeVideo(item, options, cancellationToken);
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@ -25,7 +27,6 @@ using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
@ -33,13 +34,10 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
public class FFProbeVideoInfo
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IIsoManager _isoManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly IBlurayExaminer _blurayExaminer;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly IEncodingManager _encodingManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
@ -48,16 +46,27 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public FFProbeVideoInfo(ILogger logger, IMediaSourceManager mediaSourceManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager)
|
||||
private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
|
||||
|
||||
public FFProbeVideoInfo(
|
||||
ILogger logger,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepo,
|
||||
IBlurayExaminer blurayExaminer,
|
||||
ILocalizationManager localization,
|
||||
IEncodingManager encodingManager,
|
||||
IFileSystem fileSystem,
|
||||
IServerConfigurationManager config,
|
||||
ISubtitleManager subtitleManager,
|
||||
IChapterManager chapterManager,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_isoManager = isoManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepo = itemRepo;
|
||||
_blurayExaminer = blurayExaminer;
|
||||
_localization = localization;
|
||||
_appPaths = appPaths;
|
||||
_json = json;
|
||||
_encodingManager = encodingManager;
|
||||
_fileSystem = fileSystem;
|
||||
_config = config;
|
||||
@ -159,7 +168,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
List<MediaStream> mediaStreams;
|
||||
IReadOnlyList<MediaAttachment> mediaAttachments;
|
||||
List<ChapterInfo> chapters;
|
||||
ChapterInfo[] chapters;
|
||||
|
||||
if (mediaInfo != null)
|
||||
{
|
||||
@ -177,6 +186,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
video.RunTimeTicks = mediaInfo.RunTimeTicks;
|
||||
}
|
||||
|
||||
video.Size = mediaInfo.Size;
|
||||
|
||||
if (video.VideoType == VideoType.VideoFile)
|
||||
@ -189,19 +199,20 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
video.Container = null;
|
||||
}
|
||||
|
||||
video.Container = mediaInfo.Container;
|
||||
|
||||
chapters = mediaInfo.Chapters == null ? new List<ChapterInfo>() : mediaInfo.Chapters.ToList();
|
||||
chapters = mediaInfo.Chapters == null ? Array.Empty<ChapterInfo>() : mediaInfo.Chapters;
|
||||
if (blurayInfo != null)
|
||||
{
|
||||
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
|
||||
FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaStreams = new List<MediaStream>();
|
||||
mediaAttachments = Array.Empty<MediaAttachment>();
|
||||
chapters = new List<ChapterInfo>();
|
||||
chapters = Array.Empty<ChapterInfo>();
|
||||
}
|
||||
|
||||
await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
|
||||
@ -231,9 +242,9 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
|
||||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
|
||||
{
|
||||
if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
|
||||
if (chapters.Length == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
|
||||
{
|
||||
AddDummyChapters(video, chapters);
|
||||
chapters = CreateDummyChapters(video);
|
||||
}
|
||||
|
||||
NormalizeChapterNames(chapters);
|
||||
@ -246,28 +257,29 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_chapterManager.SaveChapters(video.Id.ToString(), chapters);
|
||||
_chapterManager.SaveChapters(video.Id, chapters);
|
||||
}
|
||||
}
|
||||
|
||||
private void NormalizeChapterNames(List<ChapterInfo> chapters)
|
||||
private void NormalizeChapterNames(ChapterInfo[] chapters)
|
||||
{
|
||||
var index = 1;
|
||||
|
||||
foreach (var chapter in chapters)
|
||||
for (int i = 0; i < chapters.Length; i++)
|
||||
{
|
||||
string name = chapters[i].Name;
|
||||
// Check if the name is empty and/or if the name is a time
|
||||
// Some ripping programs do that.
|
||||
if (string.IsNullOrWhiteSpace(chapter.Name) ||
|
||||
TimeSpan.TryParse(chapter.Name, out var time))
|
||||
if (string.IsNullOrWhiteSpace(name) ||
|
||||
TimeSpan.TryParse(name, out _))
|
||||
{
|
||||
chapter.Name = string.Format(_localization.GetLocalizedString("ChapterNameValue"), index.ToString(CultureInfo.InvariantCulture));
|
||||
chapters[i].Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ChapterNameValue"),
|
||||
(i + 1).ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
|
||||
private void FetchBdInfo(BaseItem item, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
|
||||
{
|
||||
var video = (Video)item;
|
||||
|
||||
@ -301,13 +313,15 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
if (blurayInfo.Chapters != null)
|
||||
{
|
||||
chapters.Clear();
|
||||
|
||||
chapters.AddRange(blurayInfo.Chapters.Select(c => new ChapterInfo
|
||||
double[] brChapter = blurayInfo.Chapters;
|
||||
chapters = new ChapterInfo[brChapter.Length];
|
||||
for (int i = 0; i < brChapter.Length; i++)
|
||||
{
|
||||
StartPositionTicks = TimeSpan.FromSeconds(c).Ticks
|
||||
|
||||
}));
|
||||
chapters[i] = new ChapterInfo
|
||||
{
|
||||
StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
|
||||
@ -495,17 +509,17 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
var libraryOptions = _libraryManager.GetLibraryOptions(video);
|
||||
|
||||
string[] subtitleDownloadLanguages;
|
||||
bool SkipIfEmbeddedSubtitlesPresent;
|
||||
bool SkipIfAudioTrackMatches;
|
||||
bool RequirePerfectMatch;
|
||||
bool skipIfEmbeddedSubtitlesPresent;
|
||||
bool skipIfAudioTrackMatches;
|
||||
bool requirePerfectMatch;
|
||||
bool enabled;
|
||||
|
||||
if (libraryOptions.SubtitleDownloadLanguages == null)
|
||||
{
|
||||
subtitleDownloadLanguages = subtitleOptions.DownloadLanguages;
|
||||
SkipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
|
||||
SkipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
|
||||
RequirePerfectMatch = subtitleOptions.RequirePerfectMatch;
|
||||
skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
|
||||
skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
|
||||
requirePerfectMatch = subtitleOptions.RequirePerfectMatch;
|
||||
enabled = (subtitleOptions.DownloadEpisodeSubtitles &&
|
||||
video is Episode) ||
|
||||
(subtitleOptions.DownloadMovieSubtitles &&
|
||||
@ -514,9 +528,9 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
else
|
||||
{
|
||||
subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
|
||||
SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
|
||||
SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
|
||||
RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
|
||||
skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
|
||||
skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
|
||||
requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
@ -526,9 +540,9 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
_subtitleManager)
|
||||
.DownloadSubtitles(video,
|
||||
currentStreams.Concat(externalSubtitleStreams).ToList(),
|
||||
SkipIfEmbeddedSubtitlesPresent,
|
||||
SkipIfAudioTrackMatches,
|
||||
RequirePerfectMatch,
|
||||
skipIfEmbeddedSubtitlesPresent,
|
||||
skipIfAudioTrackMatches,
|
||||
requirePerfectMatch,
|
||||
subtitleDownloadLanguages,
|
||||
libraryOptions.DisabledSubtitleFetchers,
|
||||
libraryOptions.SubtitleFetcherOrder,
|
||||
@ -547,43 +561,45 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The dummy chapter duration
|
||||
/// </summary>
|
||||
private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the dummy chapters.
|
||||
/// Creates dummy chapters.
|
||||
/// </summary>
|
||||
/// <param name="video">The video.</param>
|
||||
/// <param name="chapters">The chapters.</param>
|
||||
private void AddDummyChapters(Video video, List<ChapterInfo> chapters)
|
||||
/// <return>An array of dummy chapters.</returns>
|
||||
private ChapterInfo[] CreateDummyChapters(Video video)
|
||||
{
|
||||
var runtime = video.RunTimeTicks ?? 0;
|
||||
|
||||
if (runtime < 0)
|
||||
{
|
||||
throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime));
|
||||
throw new ArgumentException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} has invalid runtime of {1}",
|
||||
video.Name,
|
||||
runtime));
|
||||
}
|
||||
|
||||
if (runtime < _dummyChapterDuration)
|
||||
{
|
||||
return;
|
||||
return Array.Empty<ChapterInfo>();
|
||||
}
|
||||
|
||||
long currentChapterTicks = 0;
|
||||
var index = 1;
|
||||
|
||||
// Limit to 100 chapters just in case there's some incorrect metadata here
|
||||
while (currentChapterTicks < runtime && index < 100)
|
||||
int chapterCount = (int)Math.Min(runtime / _dummyChapterDuration, 100);
|
||||
var chapters = new ChapterInfo[chapterCount];
|
||||
|
||||
long currentChapterTicks = 0;
|
||||
for (int i = 0; i < chapterCount; i++)
|
||||
{
|
||||
chapters.Add(new ChapterInfo
|
||||
chapters[i] = new ChapterInfo
|
||||
{
|
||||
StartPositionTicks = currentChapterTicks
|
||||
});
|
||||
};
|
||||
|
||||
index++;
|
||||
currentChapterTicks += _dummyChapterDuration;
|
||||
}
|
||||
|
||||
return chapters;
|
||||
}
|
||||
|
||||
private string[] FetchFromDvdLib(Video item)
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
@ -36,7 +37,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IApplicationHost _appHost;
|
||||
|
||||
@ -48,7 +48,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies
|
||||
IFileSystem fileSystem,
|
||||
IServerConfigurationManager configurationManager,
|
||||
ILogger<TmdbMovieProvider> logger,
|
||||
ILocalizationManager localization,
|
||||
ILibraryManager libraryManager,
|
||||
IApplicationHost appHost)
|
||||
{
|
||||
@ -57,7 +56,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_logger = logger;
|
||||
_localization = localization;
|
||||
_libraryManager = libraryManager;
|
||||
_appHost = appHost;
|
||||
Current = this;
|
||||
@ -409,15 +407,15 @@ namespace MediaBrowser.Providers.Tmdb.Movies
|
||||
|
||||
private static long _lastRequestTicks;
|
||||
// The limit is 40 requests per 10 seconds
|
||||
private static int requestIntervalMs = 300;
|
||||
private const int RequestIntervalMs = 300;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the movie db response.
|
||||
/// </summary>
|
||||
internal async Task<HttpResponseInfo> GetMovieDbResponse(HttpRequestOptions options)
|
||||
{
|
||||
var delayTicks = (requestIntervalMs * 10000) - (DateTime.UtcNow.Ticks - _lastRequestTicks);
|
||||
var delayMs = Math.Min(delayTicks / 10000, requestIntervalMs);
|
||||
var delayTicks = (RequestIntervalMs * 10000) - (DateTime.UtcNow.Ticks - _lastRequestTicks);
|
||||
var delayMs = Math.Min(delayTicks / 10000, RequestIntervalMs);
|
||||
|
||||
if (delayMs > 0)
|
||||
{
|
||||
@ -430,11 +428,13 @@ namespace MediaBrowser.Providers.Tmdb.Movies
|
||||
options.BufferContent = true;
|
||||
options.UserAgent = _appHost.ApplicationUserAgent;
|
||||
|
||||
return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
|
||||
return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order => 1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
|
@ -12,12 +12,14 @@ using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.WebDashboard.Api
|
||||
@ -102,6 +104,7 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
/// <value>The HTTP result factory.</value>
|
||||
private readonly IHttpResultFactory _resultFactory;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IConfiguration _appConfig;
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IResourceFileManager _resourceFileManager;
|
||||
@ -111,6 +114,7 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="appConfig">The application configuration.</param>
|
||||
/// <param name="resourceFileManager">The resource file manager.</param>
|
||||
/// <param name="serverConfigurationManager">The server configuration manager.</param>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
@ -118,6 +122,7 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
public DashboardService(
|
||||
ILogger<DashboardService> logger,
|
||||
IServerApplicationHost appHost,
|
||||
IConfiguration appConfig,
|
||||
IResourceFileManager resourceFileManager,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
IFileSystem fileSystem,
|
||||
@ -125,6 +130,7 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
{
|
||||
_logger = logger;
|
||||
_appHost = appHost;
|
||||
_appConfig = appConfig;
|
||||
_resourceFileManager = resourceFileManager;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
@ -138,20 +144,30 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
public IRequest Request { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path for the web interface.
|
||||
/// Gets the path of the directory containing the static web interface content, or null if the server is not
|
||||
/// hosting the web client.
|
||||
/// </summary>
|
||||
/// <value>The path for the web interface.</value>
|
||||
public string DashboardUIPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.DashboardSourcePath))
|
||||
{
|
||||
return _serverConfigurationManager.Configuration.DashboardSourcePath;
|
||||
}
|
||||
public string DashboardUIPath => GetDashboardUIPath(_appConfig, _serverConfigurationManager);
|
||||
|
||||
return _serverConfigurationManager.ApplicationPaths.WebPath;
|
||||
/// <summary>
|
||||
/// Gets the path of the directory containing the static web interface content.
|
||||
/// </summary>
|
||||
/// <param name="appConfig">The app configuration.</param>
|
||||
/// <param name="serverConfigManager">The server configuration manager.</param>
|
||||
/// <returns>The directory path, or null if the server is not hosting the web client.</returns>
|
||||
public static string GetDashboardUIPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager)
|
||||
{
|
||||
if (!appConfig.HostWebClient())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath))
|
||||
{
|
||||
return serverConfigManager.Configuration.DashboardSourcePath;
|
||||
}
|
||||
|
||||
return serverConfigManager.ApplicationPaths.WebPath;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
@ -209,7 +225,7 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream));
|
||||
}
|
||||
|
||||
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersionString, null));
|
||||
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => PackageCreator.ModifyHtml(false, stream, null, _appHost.ApplicationVersionString, null));
|
||||
}
|
||||
|
||||
throw new ResourceNotFoundException();
|
||||
@ -307,6 +323,11 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
/// <returns>System.Object.</returns>
|
||||
public async Task<object> Get(GetDashboardResource request)
|
||||
{
|
||||
if (!_appConfig.HostWebClient() || DashboardUIPath == null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
var path = request.ResourceName;
|
||||
|
||||
var contentType = MimeTypes.GetMimeType(path);
|
||||
@ -378,6 +399,11 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
|
||||
public async Task<object> Get(GetDashboardPackage request)
|
||||
{
|
||||
if (!_appConfig.HostWebClient() || DashboardUIPath == null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
var mode = request.Mode;
|
||||
|
||||
var inputPath = string.IsNullOrWhiteSpace(mode) ?
|
||||
|
@ -31,7 +31,8 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
|
||||
if (resourceStream != null && IsCoreHtml(virtualPath))
|
||||
{
|
||||
resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false);
|
||||
bool isMainIndexPage = string.Equals(virtualPath, "index.html", StringComparison.OrdinalIgnoreCase);
|
||||
resourceStream = await ModifyHtml(isMainIndexPage, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return resourceStream;
|
||||
@ -47,16 +48,25 @@ namespace MediaBrowser.WebDashboard.Api
|
||||
return string.Equals(Path.GetExtension(path), ".html", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// Modifies the HTML by adding common meta tags, css and js.
|
||||
public async Task<Stream> ModifyHtml(
|
||||
string path,
|
||||
/// <summary>
|
||||
/// Modifies the source HTML stream by adding common meta tags, css and js.
|
||||
/// </summary>
|
||||
/// <param name="isMainIndexPage">True if the stream contains content for the main index page.</param>
|
||||
/// <param name="sourceStream">The stream whose content should be modified.</param>
|
||||
/// <param name="mode">The client mode ('cordova', 'android', etc).</param>
|
||||
/// <param name="appVersion">The application version.</param>
|
||||
/// <param name="localizationCulture">The localization culture.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the async operation to read and modify the input stream.
|
||||
/// The task result contains a stream containing the modified HTML content.
|
||||
/// </returns>
|
||||
public static async Task<Stream> ModifyHtml(
|
||||
bool isMainIndexPage,
|
||||
Stream sourceStream,
|
||||
string mode,
|
||||
string appVersion,
|
||||
string localizationCulture)
|
||||
{
|
||||
var isMainIndexPage = string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
string html;
|
||||
using (var reader = new StreamReader(sourceStream, Encoding.UTF8))
|
||||
{
|
||||
|
@ -12,7 +12,7 @@
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.11.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
|
@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
|
@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
|
@ -14,7 +14,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user