mirror of https://github.com/jellyfin/jellyfin.git
commit
6a6e02e1ec
|
@ -841,16 +841,14 @@ namespace Emby.Server.Implementations
|
|||
serviceCollection.AddSingleton(ChapterManager);
|
||||
|
||||
MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
|
||||
LoggerFactory,
|
||||
JsonSerializer,
|
||||
StartupOptions.FFmpegPath,
|
||||
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
|
||||
ServerConfigurationManager,
|
||||
FileSystemManager,
|
||||
() => SubtitleEncoder,
|
||||
() => MediaSourceManager,
|
||||
ProcessFactory,
|
||||
5000,
|
||||
LocalizationManager);
|
||||
LocalizationManager,
|
||||
() => SubtitleEncoder,
|
||||
_configuration,
|
||||
StartupOptions.FFmpegPath);
|
||||
serviceCollection.AddSingleton(MediaEncoder);
|
||||
|
||||
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
|
||||
|
@ -867,10 +865,19 @@ namespace Emby.Server.Implementations
|
|||
AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
|
||||
serviceCollection.AddSingleton(AuthService);
|
||||
|
||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
|
||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(
|
||||
LibraryManager,
|
||||
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(),
|
||||
ApplicationPaths,
|
||||
FileSystemManager,
|
||||
MediaEncoder,
|
||||
HttpClient,
|
||||
MediaSourceManager,
|
||||
ProcessFactory);
|
||||
serviceCollection.AddSingleton(SubtitleEncoder);
|
||||
|
||||
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
|
||||
serviceCollection.AddSingleton<EncodingHelper>();
|
||||
|
||||
_displayPreferencesRepository.Initialize();
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
{
|
||||
public static class ConfigurationOptions
|
||||
{
|
||||
public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
|
||||
public static Dictionary<string, string> Configuration => new Dictionary<string, string>
|
||||
{
|
||||
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
|
||||
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }
|
||||
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" },
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"Albums": "Àlbums",
|
||||
"AppDeviceValues": "App: {0}, Dispositiu: {1}",
|
||||
"Application": "Application",
|
||||
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
|
||||
"Application": "Aplicació",
|
||||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
|
||||
"Books": "Llibres",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Una nova imatge de càmera ha sigut pujada des de {0}",
|
||||
"Channels": "Canals",
|
||||
"ChapterNameValue": "Episodi {0}",
|
||||
"Collections": "Col·leccions",
|
||||
|
@ -15,8 +15,8 @@
|
|||
"Favorites": "Preferits",
|
||||
"Folders": "Directoris",
|
||||
"Genres": "Gèneres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderAlbumArtists": "Artistes dels Àlbums",
|
||||
"HeaderCameraUploads": "Pujades de Càmera",
|
||||
"HeaderContinueWatching": "Continua Veient",
|
||||
"HeaderFavoriteAlbums": "Àlbums Preferits",
|
||||
"HeaderFavoriteArtists": "Artistes Preferits",
|
||||
|
@ -27,71 +27,71 @@
|
|||
"HeaderNextUp": "A continuació",
|
||||
"HeaderRecordingGroups": "Grups d'Enregistrament",
|
||||
"HomeVideos": "Vídeos domèstics",
|
||||
"Inherit": "Heretat",
|
||||
"ItemAddedWithName": "{0} afegit a la biblioteca",
|
||||
"ItemRemovedWithName": "{0} eliminat de la biblioteca",
|
||||
"Inherit": "Hereta",
|
||||
"ItemAddedWithName": "{0} ha estat afegit a la biblioteca",
|
||||
"ItemRemovedWithName": "{0} ha estat eliminat de la biblioteca",
|
||||
"LabelIpAddressValue": "Adreça IP: {0}",
|
||||
"LabelRunningTimeValue": "Temps en marxa: {0}",
|
||||
"LabelRunningTimeValue": "Temps en funcionament: {0}",
|
||||
"Latest": "Darreres",
|
||||
"MessageApplicationUpdated": "El Servidor d'Jellyfin ha estat actualitzat",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La secció de configuració {0} ha estat actualitzada",
|
||||
"MessageApplicationUpdated": "El Servidor de Jellyfin ha estat actualitzat",
|
||||
"MessageApplicationUpdatedTo": "El Servidor de Jellyfin ha estat actualitzat a {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada",
|
||||
"MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor",
|
||||
"MixedContent": "Contingut mesclat",
|
||||
"Movies": "Pel·lícules",
|
||||
"Music": "Música",
|
||||
"MusicVideos": "Vídeos musicals",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "Instalació de {0} fallida",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NameSeasonUnknown": "Temporada Desconeguda",
|
||||
"NewVersionIsAvailable": "Una nova versió del Servidor Jellyfin està disponible per descarregar.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Actualització d'aplicació disponible",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Actualització d'aplicació instal·lada",
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Un component ha fallat",
|
||||
"NotificationOptionPluginInstalled": "Complement instal·lat",
|
||||
"NotificationOptionPluginUninstalled": "Complement desinstal·lat",
|
||||
"NotificationOptionPluginUpdateInstalled": "Actualització de complement instal·lada",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
||||
"NotificationOptionAudioPlayback": "Reproducció d'audio iniciada",
|
||||
"NotificationOptionAudioPlaybackStopped": "Reproducció d'audio aturada",
|
||||
"NotificationOptionCameraImageUploaded": "Imatge de càmera pujada",
|
||||
"NotificationOptionInstallationFailed": "Instalació fallida",
|
||||
"NotificationOptionNewLibraryContent": "Nou contingut afegit",
|
||||
"NotificationOptionPluginError": "Un connector ha fallat",
|
||||
"NotificationOptionPluginInstalled": "Connector instal·lat",
|
||||
"NotificationOptionPluginUninstalled": "Connector desinstal·lat",
|
||||
"NotificationOptionPluginUpdateInstalled": "Actualització de connector instal·lada",
|
||||
"NotificationOptionServerRestartRequired": "Reinici del servidor requerit",
|
||||
"NotificationOptionTaskFailed": "Tasca programada fallida",
|
||||
"NotificationOptionUserLockedOut": "Usuari tancat",
|
||||
"NotificationOptionVideoPlayback": "Reproducció de video iniciada",
|
||||
"NotificationOptionVideoPlaybackStopped": "Reproducció de video aturada",
|
||||
"Photos": "Fotos",
|
||||
"Playlists": "Llistes de reproducció",
|
||||
"Plugin": "Plugin",
|
||||
"Plugin": "Connector",
|
||||
"PluginInstalledWithName": "{0} ha estat instal·lat",
|
||||
"PluginUninstalledWithName": "{0} ha estat desinstal·lat",
|
||||
"PluginUpdatedWithName": "{0} ha estat actualitzat",
|
||||
"ProviderValue": "Proveïdor: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} ha fallat",
|
||||
"ScheduledTaskStartedWithName": "{0} iniciat",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"Shows": "Espectacles",
|
||||
"ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciat",
|
||||
"Shows": "Programes",
|
||||
"Songs": "Cançons",
|
||||
"StartupEmbyServerIsLoading": "El Servidor d'Jellyfin està carregant. Si et plau, prova de nou en breus.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}",
|
||||
"SubtitlesDownloadedForItem": "Subtítols descarregats per a {0}",
|
||||
"Sync": "Sync",
|
||||
"Sync": "Sincronitzar",
|
||||
"System": "System",
|
||||
"TvShows": "Espectacles de TV",
|
||||
"User": "User",
|
||||
"UserCreatedWithName": "S'ha creat l'usuari {0}",
|
||||
"UserDeletedWithName": "L'usuari {0} ha estat eliminat",
|
||||
"UserDownloadingItemWithValues": "{0} està descarregant {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserLockedOutWithName": "L'usuari {0} ha sigut tancat",
|
||||
"UserOfflineFromDevice": "{0} s'ha desconnectat de {1}",
|
||||
"UserOnlineFromDevice": "{0} està connectat des de {1}",
|
||||
"UserPasswordChangedWithName": "La contrasenya ha estat canviada per a l'usuari {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva llibreria",
|
||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||
"VersionNumber": "Versió {0}"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -22,4 +23,16 @@
|
|||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" 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>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -4,10 +4,19 @@ using SkiaSharp;
|
|||
|
||||
namespace Jellyfin.Drawing.Skia
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper class used to draw percentage-played indicators on images.
|
||||
/// </summary>
|
||||
public static class PercentPlayedDrawer
|
||||
{
|
||||
private const int IndicatorHeight = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Draw a percentage played indicator on a canvas.
|
||||
/// </summary>
|
||||
/// <param name="canvas">The canvas to draw the indicator on.</param>
|
||||
/// <param name="imageSize">The size of the image being drawn on.</param>
|
||||
/// <param name="percent">The percentage played to display with the indicator.</param>
|
||||
public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent)
|
||||
{
|
||||
using (var paint = new SKPaint())
|
||||
|
|
|
@ -3,10 +3,21 @@ using SkiaSharp;
|
|||
|
||||
namespace Jellyfin.Drawing.Skia
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper class for drawing 'played' indicators.
|
||||
/// </summary>
|
||||
public static class PlayedIndicatorDrawer
|
||||
{
|
||||
private const int OffsetFromTopRightCorner = 38;
|
||||
|
||||
/// <summary>
|
||||
/// Draw a 'played' indicator in the top right corner of a canvas.
|
||||
/// </summary>
|
||||
/// <param name="canvas">The canvas to draw the indicator on.</param>
|
||||
/// <param name="imageSize">
|
||||
/// The dimensions of the image to draw the indicator on. The width is used to determine the x-position of the
|
||||
/// indicator.
|
||||
/// </param>
|
||||
public static void DrawPlayedIndicator(SKCanvas canvas, ImageDimensions imageSize)
|
||||
{
|
||||
var x = imageSize.Width - OffsetFromTopRightCorner;
|
||||
|
@ -26,10 +37,10 @@ namespace Jellyfin.Drawing.Skia
|
|||
paint.TextSize = 30;
|
||||
paint.IsAntialias = true;
|
||||
|
||||
// or:
|
||||
// var emojiChar = 0x1F680;
|
||||
var text = "✔️";
|
||||
var emojiChar = StringUtilities.GetUnicodeCharacterCode(text, SKTextEncoding.Utf32);
|
||||
// or:
|
||||
//var emojiChar = 0x1F680;
|
||||
|
||||
// ask the font manager for a font with that character
|
||||
var fontManager = SKFontManager.Default;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using SkiaSharp;
|
||||
|
||||
|
@ -8,16 +9,10 @@ namespace Jellyfin.Drawing.Skia
|
|||
/// </summary>
|
||||
public class SkiaCodecException : SkiaException
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the non-successfull codec result returned by Skia.
|
||||
/// </summary>
|
||||
/// <value>The non-successfull codec result returned by Skia.</value>
|
||||
public SKCodecResult CodecResult { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkiaCodecException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="result">The non-successfull codec result returned by Skia.</param>
|
||||
/// <param name="result">The non-successful codec result returned by Skia.</param>
|
||||
public SkiaCodecException(SKCodecResult result) : base()
|
||||
{
|
||||
CodecResult = result;
|
||||
|
@ -27,7 +22,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
/// Initializes a new instance of the <see cref="SkiaCodecException" /> class
|
||||
/// with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="result">The non-successfull codec result returned by Skia.</param>
|
||||
/// <param name="result">The non-successful codec result returned by Skia.</param>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public SkiaCodecException(SKCodecResult result, string message)
|
||||
: base(message)
|
||||
|
@ -35,6 +30,11 @@ namespace Jellyfin.Drawing.Skia
|
|||
CodecResult = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the non-successful codec result returned by Skia.
|
||||
/// </summary>
|
||||
public SKCodecResult CodecResult { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
=> string.Format(
|
||||
|
|
|
@ -13,6 +13,9 @@ using static Jellyfin.Drawing.Skia.SkiaHelper;
|
|||
|
||||
namespace Jellyfin.Drawing.Skia
|
||||
{
|
||||
/// <summary>
|
||||
/// Image encoder that uses <see cref="SkiaSharp"/> to manipulate images.
|
||||
/// </summary>
|
||||
public class SkiaEncoder : IImageEncoder
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
@ -22,6 +25,12 @@ namespace Jellyfin.Drawing.Skia
|
|||
private static readonly HashSet<string> _transparentImageTypes
|
||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkiaEncoder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The application logger.</param>
|
||||
/// <param name="appPaths">The application paths.</param>
|
||||
/// <param name="localizationManager">The application localization manager.</param>
|
||||
public SkiaEncoder(
|
||||
ILogger<SkiaEncoder> logger,
|
||||
IApplicationPaths appPaths,
|
||||
|
@ -32,12 +41,16 @@ namespace Jellyfin.Drawing.Skia
|
|||
_localizationManager = localizationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "Skia";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool SupportsImageCollageCreation => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool SupportsImageEncoding => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyCollection<string> SupportedInputFormats =>
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
|
@ -65,11 +78,12 @@ namespace Jellyfin.Drawing.Skia
|
|||
"arw"
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
|
||||
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
|
||||
|
||||
/// <summary>
|
||||
/// Test to determine if the native lib is available
|
||||
/// Test to determine if the native lib is available.
|
||||
/// </summary>
|
||||
public static void TestSkia()
|
||||
{
|
||||
|
@ -80,6 +94,11 @@ namespace Jellyfin.Drawing.Skia
|
|||
private static bool IsTransparent(SKColor color)
|
||||
=> (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Convert a <see cref="ImageFormat"/> to a <see cref="SKEncodedImageFormat"/>.
|
||||
/// </summary>
|
||||
/// <param name="selectedFormat">The format to convert.</param>
|
||||
/// <returns>The converted format.</returns>
|
||||
public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat)
|
||||
{
|
||||
switch (selectedFormat)
|
||||
|
@ -186,6 +205,9 @@ namespace Jellyfin.Drawing.Skia
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentNullException">The path is null.</exception>
|
||||
/// <exception cref="FileNotFoundException">The path is not valid.</exception>
|
||||
/// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
|
||||
public ImageDimensions GetImageSize(string path)
|
||||
{
|
||||
if (path == null)
|
||||
|
@ -269,6 +291,14 @@ namespace Jellyfin.Drawing.Skia
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode an image.
|
||||
/// </summary>
|
||||
/// <param name="path">The filepath of the image to decode.</param>
|
||||
/// <param name="forceCleanBitmap">Whether to force clean the bitmap.</param>
|
||||
/// <param name="orientation">The orientation of the image.</param>
|
||||
/// <param name="origin">The detected origin of the image.</param>
|
||||
/// <returns>The resulting bitmap of the image.</returns>
|
||||
internal SKBitmap Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
|
@ -358,16 +388,6 @@ namespace Jellyfin.Drawing.Skia
|
|||
|
||||
private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
|
||||
{
|
||||
//var transformations = {
|
||||
// 2: { rotate: 0, flip: true},
|
||||
// 3: { rotate: 180, flip: false},
|
||||
// 4: { rotate: 180, flip: true},
|
||||
// 5: { rotate: 90, flip: true},
|
||||
// 6: { rotate: 90, flip: false},
|
||||
// 7: { rotate: 270, flip: true},
|
||||
// 8: { rotate: 270, flip: false},
|
||||
//}
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
case SKEncodedOrigin.TopRight:
|
||||
|
@ -497,6 +517,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(inputPath))
|
||||
|
@ -520,7 +541,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
{
|
||||
if (bitmap == null)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath));
|
||||
throw new ArgumentOutOfRangeException($"Skia unable to read image {inputPath}");
|
||||
}
|
||||
|
||||
var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height);
|
||||
|
@ -556,7 +577,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
}
|
||||
|
||||
// create bitmap to use for canvas drawing used to draw into bitmap
|
||||
using (var saveBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType))
|
||||
using (var saveBitmap = new SKBitmap(width, height)) // , bitmap.ColorType, bitmap.AlphaType))
|
||||
using (var canvas = new SKCanvas(saveBitmap))
|
||||
{
|
||||
// set background color if present
|
||||
|
@ -609,9 +630,11 @@ namespace Jellyfin.Drawing.Skia
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CreateImageCollage(ImageCollageOptions options)
|
||||
{
|
||||
double ratio = (double)options.Width / options.Height;
|
||||
|
|
|
@ -7,17 +7,30 @@ namespace Jellyfin.Drawing.Skia
|
|||
/// </summary>
|
||||
public class SkiaException : Exception
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkiaException"/> class.
|
||||
/// </summary>
|
||||
public SkiaException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkiaException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public SkiaException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkiaException"/> class with a specified error message and a
|
||||
/// reference to the inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">
|
||||
/// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if
|
||||
/// no inner exception is specified.
|
||||
/// </param>
|
||||
public SkiaException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
|
|
|
@ -5,15 +5,27 @@ using SkiaSharp;
|
|||
|
||||
namespace Jellyfin.Drawing.Skia
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to build collages of multiple images arranged in vertical strips.
|
||||
/// </summary>
|
||||
public class StripCollageBuilder
|
||||
{
|
||||
private readonly SkiaEncoder _skiaEncoder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StripCollageBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="skiaEncoder">The encoder to use for building collages.</param>
|
||||
public StripCollageBuilder(SkiaEncoder skiaEncoder)
|
||||
{
|
||||
_skiaEncoder = skiaEncoder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check which format an image has been encoded with using its filename extension.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">The path to the image to get the format for.</param>
|
||||
/// <returns>The image format.</returns>
|
||||
public static SKEncodedImageFormat GetEncodedFormat(string outputPath)
|
||||
{
|
||||
if (outputPath == null)
|
||||
|
@ -48,6 +60,13 @@ namespace Jellyfin.Drawing.Skia
|
|||
return SKEncodedImageFormat.Png;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a square collage.
|
||||
/// </summary>
|
||||
/// <param name="paths">The paths of the images to use in the collage.</param>
|
||||
/// <param name="outputPath">The path at which to place the resulting collage image.</param>
|
||||
/// <param name="width">The desired width of the collage.</param>
|
||||
/// <param name="height">The desired height of the collage.</param>
|
||||
public void BuildSquareCollage(string[] paths, string outputPath, int width, int height)
|
||||
{
|
||||
using (var bitmap = BuildSquareCollageBitmap(paths, width, height))
|
||||
|
@ -58,6 +77,13 @@ namespace Jellyfin.Drawing.Skia
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a thumb collage.
|
||||
/// </summary>
|
||||
/// <param name="paths">The paths of the images to use in the collage.</param>
|
||||
/// <param name="outputPath">The path at which to place the resulting image.</param>
|
||||
/// <param name="width">The desired width of the collage.</param>
|
||||
/// <param name="height">The desired height of the collage.</param>
|
||||
public void BuildThumbCollage(string[] paths, string outputPath, int width, int height)
|
||||
{
|
||||
using (var bitmap = BuildThumbCollageBitmap(paths, width, height))
|
||||
|
@ -98,6 +124,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType))
|
||||
{
|
||||
currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
|
||||
|
||||
// crop image
|
||||
int ix = (int)Math.Abs((iWidth - iSlice) / 2);
|
||||
using (var image = SKImage.FromBitmap(resizeBitmap))
|
||||
|
|
|
@ -4,10 +4,25 @@ using SkiaSharp;
|
|||
|
||||
namespace Jellyfin.Drawing.Skia
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper class for drawing unplayed count indicators.
|
||||
/// </summary>
|
||||
public static class UnplayedCountIndicator
|
||||
{
|
||||
/// <summary>
|
||||
/// The x-offset used when drawing an unplayed count indicator.
|
||||
/// </summary>
|
||||
private const int OffsetFromTopRightCorner = 38;
|
||||
|
||||
/// <summary>
|
||||
/// Draw an unplayed count indicator in the top right corner of a canvas.
|
||||
/// </summary>
|
||||
/// <param name="canvas">The canvas to draw the indicator on.</param>
|
||||
/// <param name="imageSize">
|
||||
/// The dimensions of the image to draw the indicator on. The width is used to determine the x-position of the
|
||||
/// indicator.
|
||||
/// </param>
|
||||
/// <param name="count">The number to draw in the indicator.</param>
|
||||
public static void DrawUnplayedCountIndicator(SKCanvas canvas, ImageDimensions imageSize, int count)
|
||||
{
|
||||
var x = imageSize.Width - OffsetFromTopRightCorner;
|
||||
|
@ -19,6 +34,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
paint.Style = SKPaintStyle.Fill;
|
||||
canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
|
||||
}
|
||||
|
||||
using (var paint = new SKPaint())
|
||||
{
|
||||
paint.Color = new SKColor(255, 255, 255, 255);
|
||||
|
@ -33,6 +49,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
{
|
||||
x -= 7;
|
||||
}
|
||||
|
||||
if (text.Length == 2)
|
||||
{
|
||||
x -= 13;
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Net;
|
|||
using System.Net.Security;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -145,6 +146,10 @@ namespace Jellyfin.Server
|
|||
|
||||
ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
|
||||
|
||||
// Make sure we have all the code pages we can get
|
||||
// Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
// Increase the max http request limit
|
||||
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
|
||||
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
|
||||
|
@ -456,9 +461,9 @@ namespace Jellyfin.Server
|
|||
|
||||
return new ConfigurationBuilder()
|
||||
.SetBasePath(appPaths.ConfigurationDirectoryPath)
|
||||
.AddInMemoryCollection(ConfigurationOptions.Configuration)
|
||||
.AddJsonFile("logging.json", false, true)
|
||||
.AddEnvironmentVariables("JELLYFIN_")
|
||||
.AddInMemoryCollection(ConfigurationOptions.Configuration)
|
||||
.Build();
|
||||
}
|
||||
|
||||
|
|
|
@ -63,8 +63,6 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
protected IDeviceManager DeviceManager { get; private set; }
|
||||
|
||||
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
|
||||
|
||||
protected IMediaSourceManager MediaSourceManager { get; private set; }
|
||||
|
||||
protected IJsonSerializer JsonSerializer { get; private set; }
|
||||
|
@ -92,11 +90,11 @@ namespace MediaBrowser.Api.Playback
|
|||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext)
|
||||
IAuthorizationContext authorizationContext,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(logger, serverConfigurationManager, httpResultFactory)
|
||||
{
|
||||
UserManager = userManager;
|
||||
|
@ -105,13 +103,12 @@ namespace MediaBrowser.Api.Playback
|
|||
MediaEncoder = mediaEncoder;
|
||||
FileSystem = fileSystem;
|
||||
DlnaManager = dlnaManager;
|
||||
SubtitleEncoder = subtitleEncoder;
|
||||
DeviceManager = deviceManager;
|
||||
MediaSourceManager = mediaSourceManager;
|
||||
JsonSerializer = jsonSerializer;
|
||||
AuthorizationContext = authorizationContext;
|
||||
|
||||
EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder);
|
||||
EncodingHelper = encodingHelper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -148,8 +145,6 @@ namespace MediaBrowser.Api.Playback
|
|||
return Path.Combine(folder, filename + ext);
|
||||
}
|
||||
|
||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
protected virtual string GetDefaultEncoderPreset()
|
||||
{
|
||||
return "superfast";
|
||||
|
@ -764,13 +759,13 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
if (mediaSource == null)
|
||||
{
|
||||
var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList();
|
||||
var mediaSources = await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? mediaSources[0]
|
||||
: mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId));
|
||||
|
||||
if (mediaSource == null && request.MediaSourceId.Equals(request.Id))
|
||||
if (mediaSource == null && Guid.Parse(request.MediaSourceId) == request.Id)
|
||||
{
|
||||
mediaSource = mediaSources[0];
|
||||
}
|
||||
|
|
|
@ -34,26 +34,26 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
httpResultFactory,
|
||||
userManager,
|
||||
libraryManager,
|
||||
isoManager,
|
||||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
subtitleEncoder,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext)
|
||||
IAuthorizationContext authorizationContext,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
httpResultFactory,
|
||||
userManager,
|
||||
libraryManager,
|
||||
isoManager,
|
||||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext,
|
||||
encodingHelper)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -104,12 +104,12 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext,
|
||||
INetworkManager networkManager)
|
||||
INetworkManager networkManager,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
|
@ -120,11 +120,11 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
subtitleEncoder,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext)
|
||||
authorizationContext,
|
||||
encodingHelper)
|
||||
{
|
||||
NetworkManager = networkManager;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,39 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
[Authenticated]
|
||||
public class VideoHlsService : BaseHlsService
|
||||
{
|
||||
public VideoHlsService(
|
||||
ILogger<VideoHlsService> logger,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
IHttpResultFactory httpResultFactory,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
IIsoManager isoManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
httpResultFactory,
|
||||
userManager,
|
||||
libraryManager,
|
||||
isoManager,
|
||||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext,
|
||||
encodingHelper)
|
||||
{
|
||||
}
|
||||
|
||||
public Task<object> Get(GetLiveHlsStream request)
|
||||
{
|
||||
return ProcessRequestAsync(request, true);
|
||||
|
@ -136,38 +169,5 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
return args;
|
||||
}
|
||||
|
||||
public VideoHlsService(
|
||||
ILogger<VideoHlsService> logger,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
IHttpResultFactory httpResultFactory,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
IIsoManager isoManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
httpResultFactory,
|
||||
userManager,
|
||||
libraryManager,
|
||||
isoManager,
|
||||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
subtitleEncoder,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,11 +43,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext)
|
||||
IAuthorizationContext authorizationContext,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
|
@ -59,11 +59,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
subtitleEncoder,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext)
|
||||
authorizationContext,
|
||||
encodingHelper)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -38,11 +38,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext)
|
||||
IAuthorizationContext authorizationContext,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
|
@ -53,11 +53,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
subtitleEncoder,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext)
|
||||
authorizationContext,
|
||||
encodingHelper)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
}
|
||||
|
|
|
@ -80,11 +80,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext)
|
||||
IAuthorizationContext authorizationContext,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
|
@ -96,11 +96,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
subtitleEncoder,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext)
|
||||
authorizationContext,
|
||||
encodingHelper)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ using MediaBrowser.Common.Net;
|
|||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
|
@ -75,6 +74,9 @@ namespace MediaBrowser.Api.Playback
|
|||
[Authenticated]
|
||||
public class UniversalAudioService : BaseApiService
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly EncodingHelper _encodingHelper;
|
||||
|
||||
public UniversalAudioService(
|
||||
ILogger<UniversalAudioService> logger,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
|
@ -87,11 +89,11 @@ namespace MediaBrowser.Api.Playback
|
|||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
IDeviceManager deviceManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext,
|
||||
INetworkManager networkManager)
|
||||
INetworkManager networkManager,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(logger, serverConfigurationManager, httpResultFactory)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
|
@ -102,11 +104,11 @@ namespace MediaBrowser.Api.Playback
|
|||
FileSystem = fileSystem;
|
||||
DlnaManager = dlnaManager;
|
||||
DeviceManager = deviceManager;
|
||||
SubtitleEncoder = subtitleEncoder;
|
||||
MediaSourceManager = mediaSourceManager;
|
||||
JsonSerializer = jsonSerializer;
|
||||
AuthorizationContext = authorizationContext;
|
||||
NetworkManager = networkManager;
|
||||
_encodingHelper = encodingHelper;
|
||||
}
|
||||
|
||||
protected IHttpClient HttpClient { get; private set; }
|
||||
|
@ -117,7 +119,6 @@ namespace MediaBrowser.Api.Playback
|
|||
protected IFileSystem FileSystem { get; private set; }
|
||||
protected IDlnaManager DlnaManager { get; private set; }
|
||||
protected IDeviceManager DeviceManager { get; private set; }
|
||||
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
|
||||
protected IMediaSourceManager MediaSourceManager { get; private set; }
|
||||
protected IJsonSerializer JsonSerializer { get; private set; }
|
||||
protected IAuthorizationContext AuthorizationContext { get; private set; }
|
||||
|
@ -287,12 +288,12 @@ namespace MediaBrowser.Api.Playback
|
|||
MediaEncoder,
|
||||
FileSystem,
|
||||
DlnaManager,
|
||||
SubtitleEncoder,
|
||||
DeviceManager,
|
||||
MediaSourceManager,
|
||||
JsonSerializer,
|
||||
AuthorizationContext,
|
||||
NetworkManager)
|
||||
NetworkManager,
|
||||
_encodingHelper)
|
||||
{
|
||||
Request = Request
|
||||
};
|
||||
|
@ -337,11 +338,11 @@ namespace MediaBrowser.Api.Playback
|
|||
MediaEncoder,
|
||||
FileSystem,
|
||||
DlnaManager,
|
||||
SubtitleEncoder,
|
||||
DeviceManager,
|
||||
MediaSourceManager,
|
||||
JsonSerializer,
|
||||
AuthorizationContext)
|
||||
AuthorizationContext,
|
||||
_encodingHelper)
|
||||
{
|
||||
Request = Request
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace MediaBrowser.Controller.Drawing
|
|||
/// </summary>
|
||||
/// <value>The supported input formats.</value>
|
||||
IReadOnlyCollection<string> SupportedInputFormats { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supported output formats.
|
||||
/// </summary>
|
||||
|
@ -18,9 +19,9 @@ namespace MediaBrowser.Controller.Drawing
|
|||
IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// Gets the display name for the encoder.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
/// <value>The display name.</value>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -35,17 +36,22 @@ namespace MediaBrowser.Controller.Drawing
|
|||
/// <value><c>true</c> if [supports image encoding]; otherwise, <c>false</c>.</value>
|
||||
bool SupportsImageEncoding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the dimensions of an image from the filesystem.
|
||||
/// </summary>
|
||||
/// <param name="path">The filepath of the image.</param>
|
||||
/// <returns>The image dimensions.</returns>
|
||||
ImageDimensions GetImageSize(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the image.
|
||||
/// Encode an image.
|
||||
/// </summary>
|
||||
string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the image collage.
|
||||
/// Create an image collage.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="options">The options to use when creating the collage.</param>
|
||||
void CreateImageCollage(ImageCollageOptions options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <value>The video3 D format.</value>
|
||||
public Video3DFormat? Video3DFormat { get; set; }
|
||||
|
||||
public string[] GetPlayableStreamFileNames(IMediaEncoder mediaEncoder)
|
||||
public string[] GetPlayableStreamFileNames()
|
||||
{
|
||||
var videoType = VideoType;
|
||||
|
||||
|
@ -153,7 +153,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
return mediaEncoder.GetPlayableStreamFileNames(Path, videoType);
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace MediaBrowser.Controller.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration extensions for <c>MediaBrowser.Controller</c>.
|
||||
/// </summary>
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The key for the FFmpeg probe size option.
|
||||
/// </summary>
|
||||
public const string FfmpegProbeSizeKey = "FFmpeg:probesize";
|
||||
|
||||
/// <summary>
|
||||
/// The key for the FFmpeg analyse duration option.
|
||||
/// </summary>
|
||||
public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration";
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the FFmpeg probe size from the <see cref="IConfiguration" />.
|
||||
/// </summary>
|
||||
/// <param name="configuration">This configuration.</param>
|
||||
/// <returns>The FFmpeg probe size option.</returns>
|
||||
public static string GetFFmpegProbeSize(this IConfiguration configuration)
|
||||
=> configuration[FfmpegProbeSizeKey];
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the FFmpeg analyse duration from the <see cref="IConfiguration" />.
|
||||
/// </summary>
|
||||
/// <param name="configuration">This configuration.</param>
|
||||
/// <returns>The FFmpeg analyse duration option.</returns>
|
||||
public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration)
|
||||
=> configuration[FfmpegAnalyzeDurationKey];
|
||||
}
|
||||
}
|
|
@ -7,6 +7,10 @@
|
|||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
|
|
|
@ -6,12 +6,14 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
|
@ -22,6 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
private static readonly string[] _videoProfiles = new[]
|
||||
{
|
||||
|
@ -34,11 +37,16 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
"ConstrainedHigh"
|
||||
};
|
||||
|
||||
public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder)
|
||||
public EncodingHelper(
|
||||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_fileSystem = fileSystem;
|
||||
_subtitleEncoder = subtitleEncoder;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
|
||||
|
@ -172,7 +180,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
return string.Empty;
|
||||
}
|
||||
|
||||
public string GetInputFormat(string container)
|
||||
public static string GetInputFormat(string container)
|
||||
{
|
||||
if (string.IsNullOrEmpty(container))
|
||||
{
|
||||
|
@ -662,7 +670,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
||||
{
|
||||
var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
|
||||
var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
|
||||
subtitlePath,
|
||||
state.SubtitleStream.Language,
|
||||
state.MediaSource.Protocol,
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (!string.IsNullOrEmpty(charenc))
|
||||
{
|
||||
|
@ -1948,7 +1960,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
// If transcoding from 10 bit, transform colour spaces too
|
||||
if (!string.IsNullOrEmpty(videoStream.PixelFormat)
|
||||
&& videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
|
||||
&& string.Equals(outputVideoCodec,"libx264", StringComparison.OrdinalIgnoreCase))
|
||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filters.Add("format=p010le");
|
||||
filters.Add("format=nv12");
|
||||
|
@ -2011,7 +2023,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
var output = string.Empty;
|
||||
|
||||
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
|
||||
if (state.SubtitleStream != null
|
||||
&& state.SubtitleStream.IsTextSubtitleStream
|
||||
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
|
||||
{
|
||||
var subParam = GetTextSubtitleParam(state);
|
||||
|
||||
|
@ -2100,11 +2114,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
}
|
||||
|
||||
public static string GetProbeSizeArgument(int numInputFiles)
|
||||
=> numInputFiles > 1 ? "-probesize 1G" : "";
|
||||
public string GetProbeSizeArgument(int numInputFiles)
|
||||
=> numInputFiles > 1 ? "-probesize " + _configuration.GetFFmpegProbeSize() : string.Empty;
|
||||
|
||||
public static string GetAnalyzeDurationArgument(int numInputFiles)
|
||||
=> numInputFiles > 1 ? "-analyzeduration 200M" : "";
|
||||
public string GetAnalyzeDurationArgument(int numInputFiles)
|
||||
=> numInputFiles > 1 ? "-analyzeduration " + _configuration.GetFFmpegAnalyzeDuration() : string.Empty;
|
||||
|
||||
public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions)
|
||||
{
|
||||
|
|
|
@ -15,6 +15,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// </summary>
|
||||
public interface IMediaEncoder : ITranscoderSupport
|
||||
{
|
||||
/// <summary>
|
||||
/// The location of the discovered FFmpeg tool.
|
||||
/// </summary>
|
||||
FFmpegLocation EncoderLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -97,7 +100,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
void UpdateEncoderPath(string path, string pathType);
|
||||
bool SupportsEncoder(string encoder);
|
||||
|
||||
string[] GetPlayableStreamFileNames(string path, VideoType videoType);
|
||||
IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.MediaEncoding.Probing;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
@ -19,9 +19,9 @@ using MediaBrowser.Model.Entities;
|
|||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
|
@ -31,55 +31,60 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
public class MediaEncoder : IMediaEncoder, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the encoder path.
|
||||
/// The default image extraction timeout in milliseconds.
|
||||
/// </summary>
|
||||
/// <value>The encoder path.</value>
|
||||
public string EncoderPath => FFmpegPath;
|
||||
|
||||
/// <summary>
|
||||
/// The location of the discovered FFmpeg tool.
|
||||
/// </summary>
|
||||
public FFmpegLocation EncoderLocation { get; private set; }
|
||||
internal const int DefaultImageExtractionTimeout = 5000;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private string FFmpegPath;
|
||||
private string FFprobePath;
|
||||
protected readonly IServerConfigurationManager ConfigurationManager;
|
||||
protected readonly IFileSystem FileSystem;
|
||||
protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
|
||||
protected readonly Func<IMediaSourceManager> MediaSourceManager;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
private readonly int DefaultImageExtractionTimeoutMs;
|
||||
private readonly string StartupOptionFFmpegPath;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly Func<ISubtitleEncoder> _subtitleEncoder;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly string _startupOptionFFmpegPath;
|
||||
|
||||
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
|
||||
|
||||
private readonly object _runningProcessesLock = new object();
|
||||
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
private EncodingHelper _encodingHelper;
|
||||
|
||||
private string _ffmpegPath;
|
||||
private string _ffprobePath;
|
||||
|
||||
public MediaEncoder(
|
||||
ILoggerFactory loggerFactory,
|
||||
IJsonSerializer jsonSerializer,
|
||||
string startupOptionsFFmpegPath,
|
||||
ILogger<MediaEncoder> logger,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
Func<ISubtitleEncoder> subtitleEncoder,
|
||||
Func<IMediaSourceManager> mediaSourceManager,
|
||||
IProcessFactory processFactory,
|
||||
int defaultImageExtractionTimeoutMs,
|
||||
ILocalizationManager localization)
|
||||
ILocalizationManager localization,
|
||||
Func<ISubtitleEncoder> subtitleEncoder,
|
||||
IConfiguration configuration,
|
||||
string startupOptionsFFmpegPath)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger(nameof(MediaEncoder));
|
||||
_jsonSerializer = jsonSerializer;
|
||||
StartupOptionFFmpegPath = startupOptionsFFmpegPath;
|
||||
ConfigurationManager = configurationManager;
|
||||
FileSystem = fileSystem;
|
||||
SubtitleEncoder = subtitleEncoder;
|
||||
_logger = logger;
|
||||
_configurationManager = configurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
_processFactory = processFactory;
|
||||
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
|
||||
_localization = localization;
|
||||
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
|
||||
_subtitleEncoder = subtitleEncoder;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
private EncodingHelper EncodingHelper
|
||||
=> LazyInitializer.EnsureInitialized(
|
||||
ref _encodingHelper,
|
||||
() => new EncodingHelper(this, _fileSystem, _subtitleEncoder(), _configuration));
|
||||
|
||||
/// <inheritdoc />
|
||||
public string EncoderPath => _ffmpegPath;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FFmpegLocation EncoderLocation { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Run at startup or if the user removes a Custom path from transcode page.
|
||||
/// Sets global variables FFmpegPath.
|
||||
|
@ -88,39 +93,39 @@ 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.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
|
||||
{
|
||||
// 2) Check if the --ffmpeg CLI switch has been given
|
||||
if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument))
|
||||
if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
|
||||
{
|
||||
// 3) Search system $PATH environment variable for valid FFmpeg
|
||||
if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
|
||||
{
|
||||
EncoderLocation = FFmpegLocation.NotFound;
|
||||
FFmpegPath = null;
|
||||
_ffmpegPath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
|
||||
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
|
||||
config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty;
|
||||
ConfigurationManager.SaveConfiguration("encoding", config);
|
||||
var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
|
||||
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
|
||||
_configurationManager.SaveConfiguration("encoding", config);
|
||||
|
||||
// Only if mpeg path is set, try and set path to probe
|
||||
if (FFmpegPath != null)
|
||||
if (_ffmpegPath != null)
|
||||
{
|
||||
// Determine a probe path from the mpeg path
|
||||
FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
|
||||
_ffprobePath = Regex.Replace(_ffmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
|
||||
|
||||
// Interrogate to understand what coders are supported
|
||||
var validator = new EncoderValidator(_logger, FFmpegPath);
|
||||
var validator = new EncoderValidator(_logger, _ffmpegPath);
|
||||
|
||||
SetAvailableDecoders(validator.GetDecoders());
|
||||
SetAvailableEncoders(validator.GetEncoders());
|
||||
}
|
||||
|
||||
_logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, FFmpegPath ?? string.Empty);
|
||||
_logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, _ffmpegPath ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -160,9 +165,9 @@ 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.GetConfiguration<EncodingOptions>("encoding");
|
||||
config.EncoderAppPath = newPath;
|
||||
ConfigurationManager.SaveConfiguration("encoding", config);
|
||||
_configurationManager.SaveConfiguration("encoding", config);
|
||||
|
||||
// Trigger SetFFmpegPath so we validate the new path and setup probe path
|
||||
SetFFmpegPath();
|
||||
|
@ -193,7 +198,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
// ToDo - Enable the ffmpeg validator. At the moment any version can be used.
|
||||
rc = true;
|
||||
|
||||
FFmpegPath = path;
|
||||
_ffmpegPath = path;
|
||||
EncoderLocation = location;
|
||||
}
|
||||
else
|
||||
|
@ -209,7 +214,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
try
|
||||
{
|
||||
var files = FileSystem.GetFilePaths(path);
|
||||
var files = _fileSystem.GetFilePaths(path);
|
||||
|
||||
var excludeExtensions = new[] { ".c" };
|
||||
|
||||
|
@ -304,7 +309,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
|
||||
|
||||
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames);
|
||||
var inputFiles = MediaEncoderHelpers.GetInputArgument(_fileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames);
|
||||
|
||||
var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length);
|
||||
string analyzeDuration;
|
||||
|
@ -365,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
||||
RedirectStandardOutput = true,
|
||||
|
||||
FileName = FFprobePath,
|
||||
FileName = _ffprobePath,
|
||||
Arguments = args,
|
||||
|
||||
|
||||
|
@ -383,7 +388,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
}
|
||||
|
||||
using (var processWrapper = new ProcessWrapper(process, this, _logger))
|
||||
using (var processWrapper = new ProcessWrapper(process, this))
|
||||
{
|
||||
_logger.LogDebug("Starting ffprobe with args {Args}", args);
|
||||
StartProcess(processWrapper);
|
||||
|
@ -391,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
InternalMediaInfoResult result;
|
||||
try
|
||||
{
|
||||
result = await _jsonSerializer.DeserializeFromStreamAsync<InternalMediaInfoResult>(
|
||||
result = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(
|
||||
process.StandardOutput.BaseStream).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
|
@ -423,7 +428,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
return new ProbeResultNormalizer(_logger, FileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
|
||||
return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,7 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
throw new ArgumentNullException(nameof(inputPath));
|
||||
}
|
||||
|
||||
var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
|
||||
var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
|
||||
|
||||
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600.
|
||||
|
@ -545,7 +550,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
|
||||
}
|
||||
|
||||
var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder());
|
||||
if (videoStream != null)
|
||||
{
|
||||
/* fix
|
||||
|
@ -559,7 +563,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
if (!string.IsNullOrWhiteSpace(container))
|
||||
{
|
||||
var inputFormat = encodinghelper.GetInputFormat(container);
|
||||
var inputFormat = EncodingHelper.GetInputFormat(container);
|
||||
if (!string.IsNullOrWhiteSpace(inputFormat))
|
||||
{
|
||||
args = "-f " + inputFormat + " " + args;
|
||||
|
@ -570,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = FFmpegPath,
|
||||
FileName = _ffmpegPath,
|
||||
Arguments = args,
|
||||
IsHidden = true,
|
||||
ErrorDialog = false,
|
||||
|
@ -579,7 +583,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
using (var processWrapper = new ProcessWrapper(process, this, _logger))
|
||||
using (var processWrapper = new ProcessWrapper(process, this))
|
||||
{
|
||||
bool ranToCompletion;
|
||||
|
||||
|
@ -588,10 +592,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
StartProcess(processWrapper);
|
||||
|
||||
var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
|
||||
var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs;
|
||||
if (timeoutMs <= 0)
|
||||
{
|
||||
timeoutMs = DefaultImageExtractionTimeoutMs;
|
||||
timeoutMs = DefaultImageExtractionTimeout;
|
||||
}
|
||||
|
||||
ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
|
||||
|
@ -607,7 +611,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
|
||||
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
||||
var file = FileSystem.GetFileInfo(tempExtractPath);
|
||||
var file = _fileSystem.GetFileInfo(tempExtractPath);
|
||||
|
||||
if (exitCode == -1 || !file.Exists || file.Length == 0)
|
||||
{
|
||||
|
@ -675,7 +679,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
args = analyzeDurationArgument + " " + args;
|
||||
}
|
||||
|
||||
var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder());
|
||||
if (videoStream != null)
|
||||
{
|
||||
/* fix
|
||||
|
@ -689,7 +692,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
if (!string.IsNullOrWhiteSpace(container))
|
||||
{
|
||||
var inputFormat = encodinghelper.GetInputFormat(container);
|
||||
var inputFormat = EncodingHelper.GetInputFormat(container);
|
||||
if (!string.IsNullOrWhiteSpace(inputFormat))
|
||||
{
|
||||
args = "-f " + inputFormat + " " + args;
|
||||
|
@ -700,7 +703,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = FFmpegPath,
|
||||
FileName = _ffmpegPath,
|
||||
Arguments = args,
|
||||
IsHidden = true,
|
||||
ErrorDialog = false,
|
||||
|
@ -713,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
bool ranToCompletion = false;
|
||||
|
||||
using (var processWrapper = new ProcessWrapper(process, this, _logger))
|
||||
using (var processWrapper = new ProcessWrapper(process, this))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -736,10 +739,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var jpegCount = FileSystem.GetFilePaths(targetDirectory)
|
||||
var jpegCount = _fileSystem.GetFilePaths(targetDirectory)
|
||||
.Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
isResponsive = (jpegCount > lastCount);
|
||||
isResponsive = jpegCount > lastCount;
|
||||
lastCount = jpegCount;
|
||||
}
|
||||
|
||||
|
@ -770,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
process.Process.Start();
|
||||
|
||||
lock (_runningProcesses)
|
||||
lock (_runningProcessesLock)
|
||||
{
|
||||
_runningProcesses.Add(process);
|
||||
}
|
||||
|
@ -804,7 +807,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
private void StopProcesses()
|
||||
{
|
||||
List<ProcessWrapper> proceses;
|
||||
lock (_runningProcesses)
|
||||
lock (_runningProcessesLock)
|
||||
{
|
||||
proceses = _runningProcesses.ToList();
|
||||
_runningProcesses.Clear();
|
||||
|
@ -827,12 +830,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -852,11 +854,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string[] GetPlayableStreamFileNames(string path, VideoType videoType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
@ -870,21 +867,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
private class ProcessWrapper : IDisposable
|
||||
{
|
||||
public readonly IProcess Process;
|
||||
public bool HasExited;
|
||||
public int? ExitCode;
|
||||
private readonly MediaEncoder _mediaEncoder;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger)
|
||||
private bool _disposed = false;
|
||||
|
||||
public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
|
||||
{
|
||||
Process = process;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_logger = logger;
|
||||
Process.Exited += Process_Exited;
|
||||
Process.Exited += OnProcessExited;
|
||||
}
|
||||
|
||||
void Process_Exited(object sender, EventArgs e)
|
||||
public IProcess Process { get; }
|
||||
|
||||
public bool HasExited { get; private set; }
|
||||
|
||||
public int? ExitCode { get; private set; }
|
||||
|
||||
void OnProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
var process = (IProcess)sender;
|
||||
|
||||
|
@ -903,7 +903,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
private void DisposeProcess(IProcess process)
|
||||
{
|
||||
lock (_mediaEncoder._runningProcesses)
|
||||
lock (_mediaEncoder._runningProcessesLock)
|
||||
{
|
||||
_mediaEncoder._runningProcesses.Remove(this);
|
||||
}
|
||||
|
@ -917,23 +917,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
private readonly object _syncLock = new object();
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_syncLock)
|
||||
if (!_disposed)
|
||||
{
|
||||
if (!_disposed)
|
||||
if (Process != null)
|
||||
{
|
||||
if (Process != null)
|
||||
{
|
||||
Process.Exited -= Process_Exited;
|
||||
DisposeProcess(Process);
|
||||
}
|
||||
Process.Exited -= OnProcessExited;
|
||||
DisposeProcess(Process);
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ using MediaBrowser.Model.MediaInfo;
|
|||
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface ISubtitleWriter
|
||||
/// Interface ISubtitleWriter.
|
||||
/// </summary>
|
||||
public interface ISubtitleWriter
|
||||
{
|
||||
|
|
|
@ -1,27 +1,39 @@
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON subtitle writer.
|
||||
/// </summary>
|
||||
public class JsonWriter : ISubtitleWriter
|
||||
{
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
public JsonWriter(IJsonSerializer json)
|
||||
{
|
||||
_json = json;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
using (var writer = new Utf8JsonWriter(stream))
|
||||
{
|
||||
var json = _json.SerializeToString(info);
|
||||
var trackevents = info.TrackEvents;
|
||||
writer.WriteStartArray("TrackEvents");
|
||||
|
||||
writer.Write(json);
|
||||
for (int i = 0; i < trackevents.Count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var current = trackevents[i];
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WriteString("Id", current.Id);
|
||||
writer.WriteString("Text", current.Text);
|
||||
writer.WriteNumber("StartPositionTicks", current.StartPositionTicks);
|
||||
writer.WriteNumber("EndPositionTicks", current.EndPositionTicks);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,14 +14,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
{
|
||||
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
{
|
||||
var index = 1;
|
||||
var trackEvents = info.TrackEvents;
|
||||
|
||||
foreach (var trackEvent in info.TrackEvents)
|
||||
for (int i = 0; i < trackEvents.Count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
writer.WriteLine(index.ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks));
|
||||
var trackEvent = trackEvents[i];
|
||||
|
||||
writer.WriteLine((i + 1).ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteLine(
|
||||
@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}",
|
||||
TimeSpan.FromTicks(trackEvent.StartPositionTicks),
|
||||
TimeSpan.FromTicks(trackEvent.EndPositionTicks));
|
||||
|
||||
var text = trackEvent.Text;
|
||||
|
||||
|
@ -29,9 +34,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase);
|
||||
|
||||
writer.WriteLine(text);
|
||||
writer.WriteLine(string.Empty);
|
||||
|
||||
index++;
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ using MediaBrowser.Model.Dto;
|
|||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using UtfUnknown;
|
||||
|
||||
|
@ -30,28 +29,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
|
||||
public SubtitleEncoder(
|
||||
ILibraryManager libraryManager,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILogger<SubtitleEncoder> logger,
|
||||
IApplicationPaths appPaths,
|
||||
IFileSystem fileSystem,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IJsonSerializer json,
|
||||
IHttpClient httpClient,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IProcessFactory processFactory)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = loggerFactory.CreateLogger(nameof(SubtitleEncoder));
|
||||
_logger = logger;
|
||||
_appPaths = appPaths;
|
||||
_fileSystem = fileSystem;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_json = json;
|
||||
_httpClient = httpClient;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_processFactory = processFactory;
|
||||
|
@ -59,7 +55,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
|
||||
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
|
||||
|
||||
private Stream ConvertSubtitles(Stream stream,
|
||||
private Stream ConvertSubtitles(
|
||||
Stream stream,
|
||||
string inputFormat,
|
||||
string outputFormat,
|
||||
long startTimeTicks,
|
||||
|
@ -170,7 +167,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
&& (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd))
|
||||
{
|
||||
var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id));
|
||||
inputFiles = mediaSourceItem.GetPlayableStreamFileNames(_mediaEncoder);
|
||||
inputFiles = mediaSourceItem.GetPlayableStreamFileNames();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -179,32 +176,27 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
|
||||
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var stream = await GetSubtitleStream(fileInfo.Path, subtitleStream.Language, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
|
||||
var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return (stream, fileInfo.Format);
|
||||
}
|
||||
|
||||
private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
|
||||
private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
|
||||
{
|
||||
if (requiresCharset)
|
||||
{
|
||||
var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
|
||||
_logger.LogDebug("charset {CharSet} detected for {Path}", charset ?? "null", path);
|
||||
|
||||
if (!string.IsNullOrEmpty(charset))
|
||||
using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// Make sure we have all the code pages we can get
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
using (var inputStream = new MemoryStream(bytes))
|
||||
using (var reader = new StreamReader(inputStream, Encoding.GetEncoding(charset)))
|
||||
var result = CharsetDetector.DetectFromStream(stream).Detected;
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
_logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, path);
|
||||
|
||||
using var reader = new StreamReader(stream, result.Encoding);
|
||||
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
|
||||
bytes = Encoding.UTF8.GetBytes(text);
|
||||
|
||||
return new MemoryStream(bytes);
|
||||
return new MemoryStream(Encoding.UTF8.GetBytes(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -323,7 +315,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
|
||||
if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new JsonWriter(_json);
|
||||
return new JsonWriter();
|
||||
}
|
||||
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -544,7 +536,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
{
|
||||
if (!File.Exists(outputPath))
|
||||
{
|
||||
await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false);
|
||||
await ExtractTextSubtitleInternal(
|
||||
_mediaEncoder.GetInputArgument(inputFiles, protocol),
|
||||
subtitleStreamIndex,
|
||||
outputCodec,
|
||||
outputPath,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
@ -572,8 +569,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||
|
||||
var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath,
|
||||
subtitleStreamIndex, outputCodec, outputPath);
|
||||
var processArgs = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"",
|
||||
inputPath,
|
||||
subtitleStreamIndex,
|
||||
outputCodec,
|
||||
outputPath);
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
{
|
||||
|
@ -721,41 +723,38 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
{
|
||||
var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false);
|
||||
using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName;
|
||||
|
||||
var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
|
||||
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
|
||||
|
||||
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
|
||||
|
||||
return charset;
|
||||
return charset;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
private Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
{
|
||||
if (protocol == MediaProtocol.Http)
|
||||
switch (protocol)
|
||||
{
|
||||
var opts = new HttpRequestOptions()
|
||||
{
|
||||
Url = path,
|
||||
CancellationToken = cancellationToken
|
||||
};
|
||||
using (var file = await _httpClient.Get(opts).ConfigureAwait(false))
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await file.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||
memoryStream.Position = 0;
|
||||
case MediaProtocol.Http:
|
||||
var opts = new HttpRequestOptions()
|
||||
{
|
||||
Url = path,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = true
|
||||
};
|
||||
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
if (protocol == MediaProtocol.File)
|
||||
{
|
||||
return File.ReadAllBytes(path);
|
||||
}
|
||||
return _httpClient.Get(opts);
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(protocol));
|
||||
case MediaProtocol.File:
|
||||
return Task.FromResult<Stream>(File.OpenRead(path));
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(protocol));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,12 +49,5 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
writer.WriteLine("</tt>");
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatTime(long ticks)
|
||||
{
|
||||
var time = TimeSpan.FromTicks(ticks);
|
||||
|
||||
return string.Format(@"{0:hh\:mm\:ss\,fff}", time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,7 +233,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
LocalNetworkSubnets = Array.Empty<string>();
|
||||
LocalNetworkAddresses = Array.Empty<string>();
|
||||
CodecsUsed = Array.Empty<string>();
|
||||
ImageExtractionTimeoutMs = 0;
|
||||
PathSubstitutions = Array.Empty<PathSubstitution>();
|
||||
IgnoreVirtualInterfaces = false;
|
||||
EnableSimpleArtistDetection = true;
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Model.MediaInfo
|
||||
{
|
||||
public class SubtitleTrackInfo
|
||||
{
|
||||
public SubtitleTrackEvent[] TrackEvents { get; set; }
|
||||
public IReadOnlyList<SubtitleTrackEvent> TrackEvents { get; set; }
|
||||
|
||||
public SubtitleTrackInfo()
|
||||
{
|
||||
TrackEvents = new SubtitleTrackEvent[] { };
|
||||
TrackEvents = Array.Empty<SubtitleTrackEvent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,11 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
{
|
||||
var protocol = item.PathProtocol ?? MediaProtocol.File;
|
||||
|
||||
var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, null, item.GetPlayableStreamFileNames(_mediaEncoder));
|
||||
var inputPath = MediaEncoderHelpers.GetInputArgument(
|
||||
_fileSystem,
|
||||
item.Path,
|
||||
null,
|
||||
item.GetPlayableStreamFileNames());
|
||||
|
||||
var mediaStreams =
|
||||
item.GetMediaStreams();
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
|
||||
<!-- disable warning CA1031: Do not catch general exception types -->
|
||||
<Rule Id="CA1031" Action="Info" />
|
||||
<!-- disable warning CA1032: Implement standard exception constructors -->
|
||||
<Rule Id="CA1032" Action="Info" />
|
||||
<!-- disable warning CA1062: Validate arguments of public methods -->
|
||||
<Rule Id="CA1062" Action="Info" />
|
||||
<!-- disable warning CA1720: Identifiers should not contain type names -->
|
||||
|
|
Loading…
Reference in New Issue