merge branch master into plugin
This commit is contained in:
commit
cb2523e2ef
|
@ -128,6 +128,7 @@
|
||||||
- [xosdy](https://github.com/xosdy)
|
- [xosdy](https://github.com/xosdy)
|
||||||
- [XVicarious](https://github.com/XVicarious)
|
- [XVicarious](https://github.com/XVicarious)
|
||||||
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
||||||
|
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|
|
@ -74,4 +74,4 @@ VOLUME /cache /config /media
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
|
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||||
|
|
|
@ -30,7 +30,6 @@ using Emby.Server.Implementations.Configuration;
|
||||||
using Emby.Server.Implementations.Cryptography;
|
using Emby.Server.Implementations.Cryptography;
|
||||||
using Emby.Server.Implementations.Data;
|
using Emby.Server.Implementations.Data;
|
||||||
using Emby.Server.Implementations.Devices;
|
using Emby.Server.Implementations.Devices;
|
||||||
using Emby.Server.Implementations.Diagnostics;
|
|
||||||
using Emby.Server.Implementations.Dto;
|
using Emby.Server.Implementations.Dto;
|
||||||
using Emby.Server.Implementations.HttpServer;
|
using Emby.Server.Implementations.HttpServer;
|
||||||
using Emby.Server.Implementations.HttpServer.Security;
|
using Emby.Server.Implementations.HttpServer.Security;
|
||||||
|
@ -86,7 +85,6 @@ using MediaBrowser.MediaEncoding.BdInfo;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
|
@ -324,8 +322,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
internal IImageEncoder ImageEncoder { get; private set; }
|
internal IImageEncoder ImageEncoder { get; private set; }
|
||||||
|
|
||||||
protected IProcessFactory ProcessFactory { get; private set; }
|
|
||||||
|
|
||||||
protected readonly IXmlSerializer XmlSerializer;
|
protected readonly IXmlSerializer XmlSerializer;
|
||||||
|
|
||||||
protected ISocketFactory SocketFactory { get; private set; }
|
protected ISocketFactory SocketFactory { get; private set; }
|
||||||
|
@ -667,9 +663,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
serviceCollection.AddSingleton(XmlSerializer);
|
serviceCollection.AddSingleton(XmlSerializer);
|
||||||
|
|
||||||
ProcessFactory = new ProcessFactory();
|
|
||||||
serviceCollection.AddSingleton(ProcessFactory);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
||||||
|
|
||||||
var cryptoProvider = new CryptographyProvider();
|
var cryptoProvider = new CryptographyProvider();
|
||||||
|
@ -730,7 +723,6 @@ namespace Emby.Server.Implementations
|
||||||
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
|
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
|
||||||
ServerConfigurationManager,
|
ServerConfigurationManager,
|
||||||
FileSystemManager,
|
FileSystemManager,
|
||||||
ProcessFactory,
|
|
||||||
LocalizationManager,
|
LocalizationManager,
|
||||||
() => SubtitleEncoder,
|
() => SubtitleEncoder,
|
||||||
startupConfig,
|
startupConfig,
|
||||||
|
@ -844,8 +836,7 @@ namespace Emby.Server.Implementations
|
||||||
FileSystemManager,
|
FileSystemManager,
|
||||||
MediaEncoder,
|
MediaEncoder,
|
||||||
HttpClient,
|
HttpClient,
|
||||||
MediaSourceManager,
|
MediaSourceManager);
|
||||||
ProcessFactory);
|
|
||||||
serviceCollection.AddSingleton(SubtitleEncoder);
|
serviceCollection.AddSingleton(SubtitleEncoder);
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
|
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
|
||||||
|
@ -1664,15 +1655,17 @@ namespace Emby.Server.Implementations
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = ProcessFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
FileName = url,
|
StartInfo = new ProcessStartInfo
|
||||||
EnableRaisingEvents = true,
|
{
|
||||||
UseShellExecute = true,
|
FileName = url,
|
||||||
ErrorDialog = false
|
UseShellExecute = true,
|
||||||
});
|
ErrorDialog = false
|
||||||
|
},
|
||||||
process.Exited += ProcessExited;
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
process.Exited += (sender, args) => ((Process)sender).Dispose();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -1685,11 +1678,6 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessExited(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
((IProcess)sender).Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void EnableLoopback(string appName)
|
public virtual void EnableLoopback(string appName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Diagnostics
|
|
||||||
{
|
|
||||||
public class CommonProcess : IProcess
|
|
||||||
{
|
|
||||||
private readonly Process _process;
|
|
||||||
|
|
||||||
private bool _disposed = false;
|
|
||||||
private bool _hasExited;
|
|
||||||
|
|
||||||
public CommonProcess(ProcessOptions options)
|
|
||||||
{
|
|
||||||
StartInfo = options;
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
Arguments = options.Arguments,
|
|
||||||
FileName = options.FileName,
|
|
||||||
WorkingDirectory = options.WorkingDirectory,
|
|
||||||
UseShellExecute = options.UseShellExecute,
|
|
||||||
CreateNoWindow = options.CreateNoWindow,
|
|
||||||
RedirectStandardError = options.RedirectStandardError,
|
|
||||||
RedirectStandardInput = options.RedirectStandardInput,
|
|
||||||
RedirectStandardOutput = options.RedirectStandardOutput,
|
|
||||||
ErrorDialog = options.ErrorDialog
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (options.IsHidden)
|
|
||||||
{
|
|
||||||
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
_process = new Process
|
|
||||||
{
|
|
||||||
StartInfo = startInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.EnableRaisingEvents)
|
|
||||||
{
|
|
||||||
_process.EnableRaisingEvents = true;
|
|
||||||
_process.Exited += OnProcessExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler Exited;
|
|
||||||
|
|
||||||
public ProcessOptions StartInfo { get; }
|
|
||||||
|
|
||||||
public StreamWriter StandardInput => _process.StandardInput;
|
|
||||||
|
|
||||||
public StreamReader StandardError => _process.StandardError;
|
|
||||||
|
|
||||||
public StreamReader StandardOutput => _process.StandardOutput;
|
|
||||||
|
|
||||||
public int ExitCode => _process.ExitCode;
|
|
||||||
|
|
||||||
private bool HasExited
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_hasExited)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_hasExited = _process.HasExited;
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
_hasExited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _hasExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
_process.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Kill()
|
|
||||||
{
|
|
||||||
_process.Kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool WaitForExit(int timeMs)
|
|
||||||
{
|
|
||||||
return _process.WaitForExit(timeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<bool> WaitForExitAsync(int timeMs)
|
|
||||||
{
|
|
||||||
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
|
||||||
|
|
||||||
if (HasExited)
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
timeMs = Math.Max(0, timeMs);
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
var cancellationToken = new CancellationTokenSource(timeMs).Token;
|
|
||||||
|
|
||||||
_process.Exited += (sender, args) => tcs.TrySetResult(true);
|
|
||||||
|
|
||||||
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
|
|
||||||
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_process?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnProcessExited(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
_hasExited = true;
|
|
||||||
Exited?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Diagnostics
|
|
||||||
{
|
|
||||||
public class ProcessFactory : IProcessFactory
|
|
||||||
{
|
|
||||||
public IProcess Create(ProcessOptions options)
|
|
||||||
{
|
|
||||||
return new CommonProcess(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -239,7 +239,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace)
|
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, string urlToLog)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -247,11 +247,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
if (logExceptionStackTrace)
|
if (logExceptionStackTrace)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error processing request");
|
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogError("Error processing request: {Message}", ex.Message);
|
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpRes = httpReq.Response;
|
var httpRes = httpReq.Response;
|
||||||
|
@ -271,7 +271,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
catch (Exception errorEx)
|
catch (Exception errorEx)
|
||||||
{
|
{
|
||||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
|
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response). URL: {Url}", urlToLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,7 +456,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
var stopWatch = new Stopwatch();
|
var stopWatch = new Stopwatch();
|
||||||
stopWatch.Start();
|
stopWatch.Start();
|
||||||
var httpRes = httpReq.Response;
|
var httpRes = httpReq.Response;
|
||||||
string urlToLog = null;
|
string urlToLog = GetUrlToLog(urlString);
|
||||||
string remoteIp = httpReq.RemoteIp;
|
string remoteIp = httpReq.RemoteIp;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -502,8 +502,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
urlToLog = GetUrlToLog(urlString);
|
|
||||||
|
|
||||||
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|
||||||
|
@ -553,7 +551,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|| ex is OperationCanceledException
|
|| ex is OperationCanceledException
|
||||||
|| ex is SecurityException
|
|| ex is SecurityException
|
||||||
|| ex is FileNotFoundException;
|
|| ex is FileNotFoundException;
|
||||||
await ErrorHandler(ex, httpReq, ignoreStackTrace).ConfigureAwait(false);
|
await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
|
@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IStreamHelper _streamHelper;
|
private readonly IStreamHelper _streamHelper;
|
||||||
|
|
||||||
|
@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILibraryMonitor libraryMonitor,
|
ILibraryMonitor libraryMonitor,
|
||||||
IProviderManager providerManager,
|
IProviderManager providerManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder)
|
||||||
IProcessFactory processFactory)
|
|
||||||
{
|
{
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
|
@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
_libraryMonitor = libraryMonitor;
|
_libraryMonitor = libraryMonitor;
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_processFactory = processFactory;
|
|
||||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
@ -1662,7 +1659,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||||
{
|
{
|
||||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
||||||
|
@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
StartInfo = new ProcessStartInfo
|
||||||
CreateNoWindow = true,
|
{
|
||||||
EnableRaisingEvents = true,
|
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
||||||
ErrorDialog = false,
|
CreateNoWindow = true,
|
||||||
FileName = options.RecordingPostProcessor,
|
ErrorDialog = false,
|
||||||
IsHidden = true,
|
FileName = options.RecordingPostProcessor,
|
||||||
UseShellExecute = false
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
});
|
UseShellExecute = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
|
@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
private void Process_Exited(object sender, EventArgs e)
|
private void Process_Exited(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (var process = (IProcess)sender)
|
using (var process = (Process)sender)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
|
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -13,7 +14,6 @@ using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
private bool _hasExited;
|
private bool _hasExited;
|
||||||
private Stream _logFileStream;
|
private Stream _logFileStream;
|
||||||
private string _targetPath;
|
private string _targetPath;
|
||||||
private IProcess _process;
|
private Process _process;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IJsonSerializer json,
|
IJsonSerializer json,
|
||||||
IProcessFactory processFactory,
|
|
||||||
IServerConfigurationManager config)
|
IServerConfigurationManager config)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_json = json;
|
_json = json;
|
||||||
_processFactory = processFactory;
|
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
_targetPath = targetFile;
|
_targetPath = targetFile;
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var processStartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
|
@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
||||||
|
|
||||||
IsHidden = true,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false
|
||||||
EnableRaisingEvents = true
|
};
|
||||||
});
|
|
||||||
|
|
||||||
_process = process;
|
var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
|
||||||
|
|
||||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
|
||||||
_logger.LogInformation(commandLineLogMessage);
|
_logger.LogInformation(commandLineLogMessage);
|
||||||
|
|
||||||
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
||||||
|
@ -109,16 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||||
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
||||||
|
|
||||||
process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
|
_process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = processStartInfo,
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
|
||||||
|
|
||||||
process.Start();
|
_process.Start();
|
||||||
|
|
||||||
cancellationToken.Register(Stop);
|
cancellationToken.Register(Stop);
|
||||||
|
|
||||||
onStarted();
|
onStarted();
|
||||||
|
|
||||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||||
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
|
StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||||
|
|
||||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
||||||
|
|
||||||
|
@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the exited.
|
/// Processes the exited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnFfMpegProcessExited(IProcess process, string inputFile)
|
private void OnFfMpegProcessExited(Process process, string inputFile)
|
||||||
{
|
{
|
||||||
_hasExited = true;
|
using (process)
|
||||||
|
|
||||||
_logFileStream?.Dispose();
|
|
||||||
_logFileStream = null;
|
|
||||||
|
|
||||||
var exitCode = process.ExitCode;
|
|
||||||
|
|
||||||
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
|
||||||
|
|
||||||
if (exitCode == 0)
|
|
||||||
{
|
{
|
||||||
_taskCompletionSource.TrySetResult(true);
|
_hasExited = true;
|
||||||
}
|
|
||||||
else
|
_logFileStream?.Dispose();
|
||||||
{
|
_logFileStream = null;
|
||||||
_taskCompletionSource.TrySetException(
|
|
||||||
new Exception(
|
var exitCode = process.ExitCode;
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
||||||
"Recording for {0} failed. Exit code {1}",
|
|
||||||
_targetPath,
|
if (exitCode == 0)
|
||||||
exitCode)));
|
{
|
||||||
|
_taskCompletionSource.TrySetResult(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_taskCompletionSource.TrySetException(
|
||||||
|
new Exception(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"Recording for {0} failed. Exit code {1}",
|
||||||
|
_targetPath,
|
||||||
|
exitCode)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"Albums": "Album",
|
"Albums": "Albums",
|
||||||
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
||||||
"Application": "Applikation",
|
"Application": "Applikation",
|
||||||
"Artists": "Kunstnere",
|
"Artists": "Kunstnere",
|
||||||
|
@ -106,5 +106,7 @@
|
||||||
"TasksChannelsCategory": "Internet Kanaler",
|
"TasksChannelsCategory": "Internet Kanaler",
|
||||||
"TasksApplicationCategory": "Applikation",
|
"TasksApplicationCategory": "Applikation",
|
||||||
"TasksLibraryCategory": "Bibliotek",
|
"TasksLibraryCategory": "Bibliotek",
|
||||||
"TasksMaintenanceCategory": "Vedligeholdelse"
|
"TasksMaintenanceCategory": "Vedligeholdelse",
|
||||||
|
"TaskRefreshChapterImages": "Udtræk Kapitel billeder",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler."
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,5 +92,27 @@
|
||||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||||
"ValueSpecialEpisodeName": "Special - {0}",
|
"ValueSpecialEpisodeName": "Special - {0}",
|
||||||
"VersionNumber": "Version {0}"
|
"VersionNumber": "Version {0}",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Download missing subtitles",
|
||||||
|
"TaskRefreshChannelsDescription": "Refreshes internet channel information.",
|
||||||
|
"TaskRefreshChannels": "Refresh Channels",
|
||||||
|
"TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
|
||||||
|
"TaskCleanTranscode": "Clean Transcode Directory",
|
||||||
|
"TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
|
||||||
|
"TaskUpdatePlugins": "Update Plugins",
|
||||||
|
"TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
|
||||||
|
"TaskRefreshPeople": "Refresh People",
|
||||||
|
"TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
|
||||||
|
"TaskCleanLogs": "Clean Log Directory",
|
||||||
|
"TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
|
||||||
|
"TaskRefreshLibrary": "Scan Media Library",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
|
||||||
|
"TaskRefreshChapterImages": "Extract Chapter Images",
|
||||||
|
"TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
|
||||||
|
"TaskCleanCache": "Clean Cache Directory",
|
||||||
|
"TasksChannelsCategory": "Internet Channels",
|
||||||
|
"TasksApplicationCategory": "Application",
|
||||||
|
"TasksLibraryCategory": "Library",
|
||||||
|
"TasksMaintenanceCategory": "Maintenance"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"Collections": "Colecciones",
|
"Collections": "Colecciones",
|
||||||
"Artists": "Artistas",
|
"Artists": "Artistas",
|
||||||
"DeviceOnlineWithName": "{0} está conectado",
|
"DeviceOnlineWithName": "{0} está conectado",
|
||||||
"DeviceOfflineWithName": "{0} ha desconectado",
|
"DeviceOfflineWithName": "{0} se ha desconectado",
|
||||||
"ChapterNameValue": "Capítulo {0}",
|
"ChapterNameValue": "Capítulo {0}",
|
||||||
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
||||||
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
|
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"HeaderFavoriteEpisodes": "قسمتهای مورد علاقه",
|
"HeaderFavoriteEpisodes": "قسمتهای مورد علاقه",
|
||||||
"HeaderFavoriteShows": "سریالهای مورد علاقه",
|
"HeaderFavoriteShows": "سریالهای مورد علاقه",
|
||||||
"HeaderFavoriteSongs": "آهنگهای مورد علاقه",
|
"HeaderFavoriteSongs": "آهنگهای مورد علاقه",
|
||||||
"HeaderLiveTV": "پخش زنده تلویزیون",
|
"HeaderLiveTV": "تلویزیون زنده",
|
||||||
"HeaderNextUp": "قسمت بعدی",
|
"HeaderNextUp": "قسمت بعدی",
|
||||||
"HeaderRecordingGroups": "گروههای ضبط",
|
"HeaderRecordingGroups": "گروههای ضبط",
|
||||||
"HomeVideos": "ویدیوهای خانگی",
|
"HomeVideos": "ویدیوهای خانگی",
|
||||||
|
@ -92,5 +92,27 @@
|
||||||
"UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
|
"UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} به کتابخانهی رسانهی شما افزوده شد",
|
"ValueHasBeenAddedToLibrary": "{0} به کتابخانهی رسانهی شما افزوده شد",
|
||||||
"ValueSpecialEpisodeName": "ویژه - {0}",
|
"ValueSpecialEpisodeName": "ویژه - {0}",
|
||||||
"VersionNumber": "نسخه {0}"
|
"VersionNumber": "نسخه {0}",
|
||||||
|
"TaskCleanTranscodeDescription": "فایلهای کدگذاری که قدیمیتر از یک روز هستند را حذف میکند.",
|
||||||
|
"TaskCleanTranscode": "پاکسازی مسیر کد گذاری",
|
||||||
|
"TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونههایی که برای به روز رسانی خودکار پیکربندی شدهاند.",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویسهای ناموجود در اینترنت بر اساس پیکربندی ابردادهها.",
|
||||||
|
"TaskDownloadMissingSubtitles": "دانلود زیرنویسهای ناموجود",
|
||||||
|
"TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی میکند.",
|
||||||
|
"TaskRefreshChannels": "تازه سازی کانالها",
|
||||||
|
"TaskUpdatePlugins": "به روز رسانی افزونهها",
|
||||||
|
"TaskRefreshPeopleDescription": "ابردادهها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.",
|
||||||
|
"TaskRefreshPeople": "تازه سازی افراد",
|
||||||
|
"TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.",
|
||||||
|
"TaskCleanLogs": "پاکسازی مسیر واقعه نگار",
|
||||||
|
"TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن میکند و ابردادهها را تازه سازی میکند.",
|
||||||
|
"TaskRefreshLibrary": "اسکن کتابخانه رسانه",
|
||||||
|
"TaskRefreshChapterImagesDescription": "عکسهای کوچک برای ویدیوهایی که سکانس دارند ایجاد میکند.",
|
||||||
|
"TaskRefreshChapterImages": "استخراج عکسهای سکانس",
|
||||||
|
"TaskCleanCacheDescription": "فایلهای حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف میشوند.",
|
||||||
|
"TaskCleanCache": "پاکسازی مسیر حافظه موقت",
|
||||||
|
"TasksChannelsCategory": "کانالهای داخلی",
|
||||||
|
"TasksApplicationCategory": "برنامه",
|
||||||
|
"TasksLibraryCategory": "کتابخانه",
|
||||||
|
"TasksMaintenanceCategory": "تعمیر"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"HeaderLiveTV": "TV-lähetykset",
|
"HeaderLiveTV": "Suorat lähetykset",
|
||||||
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
||||||
"NameSeasonUnknown": "Tuntematon Kausi",
|
"NameSeasonUnknown": "Tuntematon Kausi",
|
||||||
"NameSeasonNumber": "Kausi {0}",
|
"NameSeasonNumber": "Kausi {0}",
|
||||||
|
@ -19,12 +19,12 @@
|
||||||
"ItemAddedWithName": "{0} lisättiin kirjastoon",
|
"ItemAddedWithName": "{0} lisättiin kirjastoon",
|
||||||
"Inherit": "Periytyä",
|
"Inherit": "Periytyä",
|
||||||
"HomeVideos": "Kotivideot",
|
"HomeVideos": "Kotivideot",
|
||||||
"HeaderRecordingGroups": "Nauhoitusryhmät",
|
"HeaderRecordingGroups": "Nauhoiteryhmät",
|
||||||
"HeaderNextUp": "Seuraavaksi",
|
"HeaderNextUp": "Seuraavaksi",
|
||||||
"HeaderFavoriteSongs": "Lempikappaleet",
|
"HeaderFavoriteSongs": "Lempikappaleet",
|
||||||
"HeaderFavoriteShows": "Lempisarjat",
|
"HeaderFavoriteShows": "Lempisarjat",
|
||||||
"HeaderFavoriteEpisodes": "Lempijaksot",
|
"HeaderFavoriteEpisodes": "Lempijaksot",
|
||||||
"HeaderCameraUploads": "Kameralataukset",
|
"HeaderCameraUploads": "Kamerasta Lähetetyt",
|
||||||
"HeaderFavoriteArtists": "Lempiartistit",
|
"HeaderFavoriteArtists": "Lempiartistit",
|
||||||
"HeaderFavoriteAlbums": "Lempialbumit",
|
"HeaderFavoriteAlbums": "Lempialbumit",
|
||||||
"HeaderContinueWatching": "Jatka katsomista",
|
"HeaderContinueWatching": "Jatka katsomista",
|
||||||
|
@ -63,10 +63,10 @@
|
||||||
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
|
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
|
||||||
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
|
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
|
||||||
"UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
|
"UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
|
||||||
"UserLockedOutWithName": "Käyttäjä {0} kirjautui ulos",
|
"UserLockedOutWithName": "Käyttäjä {0} lukittu",
|
||||||
"UserDownloadingItemWithValues": "{0} latautumassa {1}",
|
"UserDownloadingItemWithValues": "{0} lataa {1}",
|
||||||
"UserDeletedWithName": "Poistettiin käyttäjä {0}",
|
"UserDeletedWithName": "Käyttäjä {0} poistettu",
|
||||||
"UserCreatedWithName": "Luotiin käyttäjä {0}",
|
"UserCreatedWithName": "Käyttäjä {0} luotu",
|
||||||
"TvShows": "TV-Ohjelmat",
|
"TvShows": "TV-Ohjelmat",
|
||||||
"Sync": "Synkronoi",
|
"Sync": "Synkronoi",
|
||||||
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
|
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
|
||||||
|
@ -74,22 +74,44 @@
|
||||||
"Songs": "Kappaleet",
|
"Songs": "Kappaleet",
|
||||||
"Shows": "Ohjelmat",
|
"Shows": "Ohjelmat",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
|
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
|
||||||
"ProviderValue": "Palveluntarjoaja: {0}",
|
"ProviderValue": "Tarjoaja: {0}",
|
||||||
"Plugin": "Liitännäinen",
|
"Plugin": "Liitännäinen",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty",
|
"NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
|
||||||
"NotificationOptionVideoPlayback": "Videon toistaminen aloitettu",
|
"NotificationOptionVideoPlayback": "Videon toisto aloitettu",
|
||||||
"NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
|
"NotificationOptionUserLockedOut": "Käyttäjä lukittu",
|
||||||
"NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma",
|
"NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
|
||||||
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
|
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
|
"NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu",
|
||||||
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
||||||
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
||||||
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
||||||
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
|
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
|
||||||
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
|
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
|
||||||
"NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta",
|
"NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty",
|
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
|
||||||
"NotificationOptionAudioPlayback": "Audion toisto aloitettu",
|
"NotificationOptionAudioPlayback": "Toistetaan ääntä",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu",
|
"NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla"
|
"NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla",
|
||||||
|
"TasksMaintenanceCategory": "Ylläpito",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
|
||||||
|
"TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
|
||||||
|
"TaskRefreshChannels": "Päivitä kanavat",
|
||||||
|
"TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
|
||||||
|
"TaskCleanTranscode": "Puhdista transkoodaushakemisto",
|
||||||
|
"TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
|
||||||
|
"TaskUpdatePlugins": "Päivitä liitännäiset",
|
||||||
|
"TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
|
||||||
|
"TaskRefreshPeople": "Päivitä henkilöt",
|
||||||
|
"TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
|
||||||
|
"TaskCleanLogs": "Puhdista lokihakemisto",
|
||||||
|
"TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
|
||||||
|
"TaskRefreshLibrary": "Skannaa mediakirjasto",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
|
||||||
|
"TaskRefreshChapterImages": "Eristä lukujen kuvat",
|
||||||
|
"TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
|
||||||
|
"TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
|
||||||
|
"TasksChannelsCategory": "Internet kanavat",
|
||||||
|
"TasksApplicationCategory": "Sovellus",
|
||||||
|
"TasksLibraryCategory": "Kirjasto"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
||||||
"Books": "Livres",
|
"Books": "Livres",
|
||||||
"CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}",
|
"CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}",
|
||||||
"Channels": "Chaînes",
|
"Channels": "Chaînes",
|
||||||
"ChapterNameValue": "Chapitre {0}",
|
"ChapterNameValue": "Chapitre {0}",
|
||||||
"Collections": "Collections",
|
"Collections": "Collections",
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
"ScheduledTaskFailedWithName": "{0} sikertelen",
|
"ScheduledTaskFailedWithName": "{0} sikertelen",
|
||||||
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
||||||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||||
"Shows": "Műsorok",
|
"Shows": "Sorozatok",
|
||||||
"Songs": "Dalok",
|
"Songs": "Dalok",
|
||||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
|
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
|
||||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||||
|
|
|
@ -91,5 +91,9 @@
|
||||||
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
|
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
|
||||||
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
||||||
"Application": "Aplicação",
|
"Application": "Aplicação",
|
||||||
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}"
|
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}",
|
||||||
|
"TaskCleanCache": "Limpar Diretório de Cache",
|
||||||
|
"TasksApplicationCategory": "Aplicação",
|
||||||
|
"TasksLibraryCategory": "Biblioteca",
|
||||||
|
"TasksMaintenanceCategory": "Manutenção"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,117 @@
|
||||||
{}
|
{
|
||||||
|
"HeaderFavoriteAlbums": "پسندیدہ البمز",
|
||||||
|
"HeaderNextUp": "اگلا",
|
||||||
|
"HeaderFavoriteArtists": "پسندیدہ فنکار",
|
||||||
|
"HeaderAlbumArtists": "البم کے فنکار",
|
||||||
|
"Movies": "فلمیں",
|
||||||
|
"HeaderFavoriteEpisodes": "پسندیدہ اقساط",
|
||||||
|
"Collections": "مجموعہ",
|
||||||
|
"Folders": "فولڈرز",
|
||||||
|
"HeaderLiveTV": "براہ راست ٹی وی",
|
||||||
|
"Channels": "چینل",
|
||||||
|
"HeaderContinueWatching": "دیکھنا جاری رکھیں",
|
||||||
|
"Playlists": "پلے لسٹس",
|
||||||
|
"ValueSpecialEpisodeName": "خاص - {0}",
|
||||||
|
"Shows": "شوز",
|
||||||
|
"Genres": "انواع",
|
||||||
|
"Artists": "فنکار",
|
||||||
|
"Sync": "مطابقت",
|
||||||
|
"Photos": "تصوریں",
|
||||||
|
"Albums": "البم",
|
||||||
|
"Favorites": "پسندیدہ",
|
||||||
|
"Songs": "گانے",
|
||||||
|
"Books": "کتابیں",
|
||||||
|
"HeaderFavoriteSongs": "پسندیدہ گانے",
|
||||||
|
"HeaderFavoriteShows": "پسندیدہ شوز",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔",
|
||||||
|
"TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں",
|
||||||
|
"TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔",
|
||||||
|
"TaskRefreshChannels": "چینلز ریفریش کریں",
|
||||||
|
"TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔",
|
||||||
|
"TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں",
|
||||||
|
"TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔",
|
||||||
|
"TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں",
|
||||||
|
"TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔",
|
||||||
|
"TaskRefreshPeople": "لوگوں کو تروتازہ کریں",
|
||||||
|
"TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔",
|
||||||
|
"TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں",
|
||||||
|
"TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.",
|
||||||
|
"TaskRefreshLibrary": "اسکین میڈیا لائبریری",
|
||||||
|
"TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔",
|
||||||
|
"TaskRefreshChapterImages": "باب کی تصاویر نکالیں",
|
||||||
|
"TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔",
|
||||||
|
"TaskCleanCache": "کیش ڈائرکٹری کلیر کریں",
|
||||||
|
"TasksChannelsCategory": "انٹرنیٹ چینلز",
|
||||||
|
"TasksApplicationCategory": "پروگرام",
|
||||||
|
"TasksLibraryCategory": "لآیبریری",
|
||||||
|
"TasksMaintenanceCategory": "مرمت",
|
||||||
|
"VersionNumber": "ورژن {0}",
|
||||||
|
"ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے",
|
||||||
|
"UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے",
|
||||||
|
"UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے",
|
||||||
|
"UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے",
|
||||||
|
"UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے",
|
||||||
|
"UserOnlineFromDevice": "{0} آن لائن ہے {1} سے",
|
||||||
|
"UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}",
|
||||||
|
"UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے",
|
||||||
|
"UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}",
|
||||||
|
"UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے",
|
||||||
|
"UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے",
|
||||||
|
"User": "صارف",
|
||||||
|
"TvShows": "ٹی وی کے پروگرام",
|
||||||
|
"System": "نظام",
|
||||||
|
"SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے",
|
||||||
|
"StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔",
|
||||||
|
"ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے",
|
||||||
|
"ScheduledTaskStartedWithName": "{0} شروع",
|
||||||
|
"ScheduledTaskFailedWithName": "{0} ناکام",
|
||||||
|
"ProviderValue": "فراہم کرنے والا: {0}",
|
||||||
|
"PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی",
|
||||||
|
"PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا",
|
||||||
|
"PluginInstalledWithName": "{0} انسٹال کیا گیا تھا",
|
||||||
|
"Plugin": "پلگن",
|
||||||
|
"NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا",
|
||||||
|
"NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا",
|
||||||
|
"NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا",
|
||||||
|
"NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی",
|
||||||
|
"NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے",
|
||||||
|
"NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال",
|
||||||
|
"NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا",
|
||||||
|
"NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا",
|
||||||
|
"NotificationOptionPluginError": "پلگ ان کی ناکامی",
|
||||||
|
"NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا",
|
||||||
|
"NotificationOptionInstallationFailed": "تنصیب کی ناکامی",
|
||||||
|
"NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی",
|
||||||
|
"NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا",
|
||||||
|
"NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا",
|
||||||
|
"NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے",
|
||||||
|
"NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے",
|
||||||
|
"NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔",
|
||||||
|
"NameSeasonUnknown": "نامعلوم باب",
|
||||||
|
"NameSeasonNumber": "باب {0}",
|
||||||
|
"NameInstallFailed": "{0} تنصیب ناکام ہوگئی",
|
||||||
|
"MusicVideos": "موسیقی ویڈیو",
|
||||||
|
"Music": "موسیقی",
|
||||||
|
"MixedContent": "مخلوط مواد",
|
||||||
|
"MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے",
|
||||||
|
"MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے",
|
||||||
|
"MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}",
|
||||||
|
"MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے",
|
||||||
|
"Latest": "تازہ ترین",
|
||||||
|
"LabelRunningTimeValue": "چلانے کی مدت",
|
||||||
|
"LabelIpAddressValue": "ای پی پتے {0}",
|
||||||
|
"ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے",
|
||||||
|
"ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے",
|
||||||
|
"Inherit": "وراثت میں",
|
||||||
|
"HomeVideos": "ہوم ویڈیو",
|
||||||
|
"HeaderRecordingGroups": "ریکارڈنگ گروپس",
|
||||||
|
"HeaderCameraUploads": "کیمرہ اپلوڈز",
|
||||||
|
"FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
|
||||||
|
"DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
|
||||||
|
"DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",
|
||||||
|
"ChapterNameValue": "باب",
|
||||||
|
"AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے",
|
||||||
|
"CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
|
||||||
|
"Application": "پروگرام",
|
||||||
|
"AppDeviceValues": "پروگرام:{0}, آلہ:{1}"
|
||||||
|
}
|
||||||
|
|
|
@ -86,12 +86,9 @@ namespace MediaBrowser.Api
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeEmpty)
|
return removeEmpty
|
||||||
{
|
? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
|
: value.Split(separator);
|
||||||
}
|
|
||||||
|
|
||||||
return value.Split(separator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SemaphoreSlim GetTranscodingLock(string outputPath)
|
public SemaphoreSlim GetTranscodingLock(string outputPath)
|
||||||
|
@ -258,7 +255,7 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
|
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
|
||||||
{
|
{
|
||||||
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
|
var ticks = transcodingPosition?.Ticks;
|
||||||
|
|
||||||
if (job != null)
|
if (job != null)
|
||||||
{
|
{
|
||||||
|
@ -487,16 +484,9 @@ namespace MediaBrowser.Api
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
|
internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
|
||||||
{
|
{
|
||||||
return KillTranscodingJobs(j =>
|
return KillTranscodingJobs(j => string.IsNullOrWhiteSpace(playSessionId)
|
||||||
{
|
? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
|
||||||
if (!string.IsNullOrWhiteSpace(playSessionId))
|
: string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
|
||||||
{
|
|
||||||
return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
}, deleteFiles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -561,10 +551,7 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
lock (job.ProcessLock)
|
lock (job.ProcessLock)
|
||||||
{
|
{
|
||||||
if (job.TranscodingThrottler != null)
|
job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
|
||||||
{
|
|
||||||
job.TranscodingThrottler.Stop().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
var process = job.Process;
|
var process = job.Process;
|
||||||
|
|
||||||
|
|
|
@ -58,12 +58,9 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
public static string[] SplitValue(string value, char delim)
|
public static string[] SplitValue(string value, char delim)
|
||||||
{
|
{
|
||||||
if (value == null)
|
return value == null
|
||||||
{
|
? Array.Empty<string>()
|
||||||
return Array.Empty<string>();
|
: value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
}
|
|
||||||
|
|
||||||
return value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Guid[] GetGuids(string value)
|
public static Guid[] GetGuids(string value)
|
||||||
|
@ -97,19 +94,10 @@ namespace MediaBrowser.Api
|
||||||
var authenticatedUser = auth.User;
|
var authenticatedUser = auth.User;
|
||||||
|
|
||||||
// If they're going to update the record of another user, they must be an administrator
|
// If they're going to update the record of another user, they must be an administrator
|
||||||
if (!userId.Equals(auth.UserId))
|
if ((!userId.Equals(auth.UserId) && !authenticatedUser.Policy.IsAdministrator)
|
||||||
|
|| (restrictUserPreferences && !authenticatedUser.Policy.EnableUserPreferenceAccess))
|
||||||
{
|
{
|
||||||
if (!authenticatedUser.Policy.IsAdministrator)
|
throw new SecurityException("Unauthorized access.");
|
||||||
{
|
|
||||||
throw new SecurityException("Unauthorized access.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (restrictUserPreferences)
|
|
||||||
{
|
|
||||||
if (!authenticatedUser.Policy.EnableUserPreferenceAccess)
|
|
||||||
{
|
|
||||||
throw new SecurityException("Unauthorized access.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,8 +126,8 @@ namespace MediaBrowser.Api
|
||||||
options.Fields = hasFields.GetItemFields();
|
options.Fields = hasFields.GetItemFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.ContainsField(Model.Querying.ItemFields.RecursiveItemCount)
|
if (!options.ContainsField(ItemFields.RecursiveItemCount)
|
||||||
|| !options.ContainsField(Model.Querying.ItemFields.ChildCount))
|
|| !options.ContainsField(ItemFields.ChildCount))
|
||||||
{
|
{
|
||||||
var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
|
var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
|
||||||
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
|
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||||
|
@ -150,7 +138,7 @@ namespace MediaBrowser.Api
|
||||||
int oldLen = options.Fields.Length;
|
int oldLen = options.Fields.Length;
|
||||||
var arr = new ItemFields[oldLen + 1];
|
var arr = new ItemFields[oldLen + 1];
|
||||||
options.Fields.CopyTo(arr, 0);
|
options.Fields.CopyTo(arr, 0);
|
||||||
arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
|
arr[oldLen] = ItemFields.RecursiveItemCount;
|
||||||
options.Fields = arr;
|
options.Fields = arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +154,7 @@ namespace MediaBrowser.Api
|
||||||
int oldLen = options.Fields.Length;
|
int oldLen = options.Fields.Length;
|
||||||
var arr = new ItemFields[oldLen + 1];
|
var arr = new ItemFields[oldLen + 1];
|
||||||
options.Fields.CopyTo(arr, 0);
|
options.Fields.CopyTo(arr, 0);
|
||||||
arr[oldLen] = Model.Querying.ItemFields.ChildCount;
|
arr[oldLen] = ItemFields.ChildCount;
|
||||||
options.Fields = arr;
|
options.Fields = arr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,27 +270,21 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
}).OfType<T>().FirstOrDefault();
|
}).OfType<T>().FirstOrDefault();
|
||||||
|
|
||||||
if (result == null)
|
result ??= libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
result = libraryManager.GetItemList(new InternalItemsQuery
|
Name = name.Replace(BaseItem.SlugChar, '/'),
|
||||||
{
|
IncludeItemTypes = new[] { typeof(T).Name },
|
||||||
Name = name.Replace(BaseItem.SlugChar, '/'),
|
DtoOptions = dtoOptions
|
||||||
IncludeItemTypes = new[] { typeof(T).Name },
|
|
||||||
DtoOptions = dtoOptions
|
|
||||||
|
|
||||||
}).OfType<T>().FirstOrDefault();
|
}).OfType<T>().FirstOrDefault();
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null)
|
result ??= libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
result = libraryManager.GetItemList(new InternalItemsQuery
|
Name = name.Replace(BaseItem.SlugChar, '?'),
|
||||||
{
|
IncludeItemTypes = new[] { typeof(T).Name },
|
||||||
Name = name.Replace(BaseItem.SlugChar, '?'),
|
DtoOptions = dtoOptions
|
||||||
IncludeItemTypes = new[] { typeof(T).Name },
|
|
||||||
DtoOptions = dtoOptions
|
|
||||||
|
|
||||||
}).OfType<T>().FirstOrDefault();
|
}).OfType<T>().FirstOrDefault();
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,12 +116,9 @@ namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
var val = Filters;
|
var val = Filters;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(val))
|
return string.IsNullOrEmpty(val)
|
||||||
{
|
? Array.Empty<ItemFilter>()
|
||||||
return new ItemFilter[] { };
|
: val.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
|
||||||
}
|
|
||||||
|
|
||||||
return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -173,14 +170,9 @@ namespace MediaBrowser.Api
|
||||||
/// <returns>IEnumerable{ItemFilter}.</returns>
|
/// <returns>IEnumerable{ItemFilter}.</returns>
|
||||||
public IEnumerable<ItemFilter> GetFilters()
|
public IEnumerable<ItemFilter> GetFilters()
|
||||||
{
|
{
|
||||||
var val = Filters;
|
return string.IsNullOrEmpty(Filters)
|
||||||
|
? Array.Empty<ItemFilter>()
|
||||||
if (string.IsNullOrEmpty(val))
|
: Filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
|
||||||
{
|
|
||||||
return new ItemFilter[] { };
|
|
||||||
}
|
|
||||||
|
|
||||||
return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +233,7 @@ namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
Limit = request.Limit,
|
Limit = request.Limit,
|
||||||
StartIndex = request.StartIndex,
|
StartIndex = request.StartIndex,
|
||||||
ChannelIds = new Guid[] { new Guid(request.Id) },
|
ChannelIds = new[] { new Guid(request.Id) },
|
||||||
ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId),
|
ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId),
|
||||||
OrderBy = request.GetOrderBy(),
|
OrderBy = request.GetOrderBy(),
|
||||||
DtoOptions = new Controller.Dto.DtoOptions
|
DtoOptions = new Controller.Dto.DtoOptions
|
||||||
|
|
|
@ -155,16 +155,14 @@ namespace MediaBrowser.Api.Devices
|
||||||
Id = id
|
Id = id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
|
||||||
{
|
{
|
||||||
return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
|
MimeType = Request.ContentType,
|
||||||
{
|
Album = album,
|
||||||
MimeType = Request.ContentType,
|
Name = name,
|
||||||
Album = album,
|
Id = id
|
||||||
Name = name,
|
});
|
||||||
Id = id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,12 +258,7 @@ namespace MediaBrowser.Api
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.IncludeDirectories && isDirectory)
|
return request.IncludeDirectories || !isDirectory;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return entries.Select(f => new FileSystemEntryInfo
|
return entries.Select(f => new FileSystemEntryInfo
|
||||||
|
|
|
@ -133,7 +133,7 @@ namespace MediaBrowser.Api
|
||||||
// Non recursive not yet supported for library folders
|
// Non recursive not yet supported for library folders
|
||||||
if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
|
if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
|
||||||
{
|
{
|
||||||
genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new Guid[] { parentItem.Id };
|
genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new[] { parentItem.Id };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -231,7 +231,7 @@ namespace MediaBrowser.Api
|
||||||
EnableTotalRecordCount = false,
|
EnableTotalRecordCount = false,
|
||||||
DtoOptions = new Controller.Dto.DtoOptions
|
DtoOptions = new Controller.Dto.DtoOptions
|
||||||
{
|
{
|
||||||
Fields = new ItemFields[] { ItemFields.Genres, ItemFields.Tags },
|
Fields = new[] { ItemFields.Genres, ItemFields.Tags },
|
||||||
EnableImages = false,
|
EnableImages = false,
|
||||||
EnableUserData = false
|
EnableUserData = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -650,7 +650,7 @@ namespace MediaBrowser.Api.Images
|
||||||
if (!string.IsNullOrWhiteSpace(request.Format)
|
if (!string.IsNullOrWhiteSpace(request.Format)
|
||||||
&& Enum.TryParse(request.Format, true, out ImageFormat format))
|
&& Enum.TryParse(request.Format, true, out ImageFormat format))
|
||||||
{
|
{
|
||||||
return new ImageFormat[] { format };
|
return new[] { format };
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetClientSupportedFormats();
|
return GetClientSupportedFormats();
|
||||||
|
@ -743,24 +743,22 @@ namespace MediaBrowser.Api.Images
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
|
public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
|
||||||
{
|
{
|
||||||
using (var reader = new StreamReader(inputStream))
|
using var reader = new StreamReader(inputStream);
|
||||||
|
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var bytes = Convert.FromBase64String(text);
|
||||||
|
|
||||||
|
var memoryStream = new MemoryStream(bytes)
|
||||||
{
|
{
|
||||||
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
Position = 0
|
||||||
|
};
|
||||||
|
|
||||||
var bytes = Convert.FromBase64String(text);
|
// Handle image/png; charset=utf-8
|
||||||
|
mimeType = mimeType.Split(';').FirstOrDefault();
|
||||||
|
|
||||||
var memoryStream = new MemoryStream(bytes)
|
await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
|
||||||
{
|
|
||||||
Position = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle image/png; charset=utf-8
|
entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
|
||||||
mimeType = mimeType.Split(';').FirstOrDefault();
|
|
||||||
|
|
||||||
await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
|
|
||||||
|
|
||||||
entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -261,27 +261,25 @@ namespace MediaBrowser.Api.Images
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
|
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
|
||||||
{
|
{
|
||||||
using (var result = await _httpClient.GetResponse(new HttpRequestOptions
|
using var result = await _httpClient.GetResponse(new HttpRequestOptions
|
||||||
{
|
{
|
||||||
Url = url,
|
Url = url,
|
||||||
BufferContent = false
|
BufferContent = false
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
}).ConfigureAwait(false);
|
||||||
|
var ext = result.ContentType.Split('/').Last();
|
||||||
|
|
||||||
|
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||||
|
using (var stream = result.Content)
|
||||||
{
|
{
|
||||||
var ext = result.ContentType.Split('/').Last();
|
using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||||
|
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
||||||
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
|
||||||
using (var stream = result.Content)
|
|
||||||
using (var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
|
|
||||||
{
|
|
||||||
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
|
||||||
File.WriteAllText(pointerCachePath, fullCachePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||||
|
File.WriteAllText(pointerCachePath, fullCachePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -305,9 +305,16 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||||
using (var stream = result.Content)
|
using (var stream = result.Content)
|
||||||
using (var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
|
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
using var fileStream = new FileStream(
|
||||||
|
fullCachePath,
|
||||||
|
FileMode.Create,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.Read,
|
||||||
|
IODefaults.FileStreamBufferSize,
|
||||||
|
true);
|
||||||
|
|
||||||
|
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||||
|
|
|
@ -263,8 +263,7 @@ namespace MediaBrowser.Api
|
||||||
item.Overview = request.Overview;
|
item.Overview = request.Overview;
|
||||||
item.Genres = request.Genres;
|
item.Genres = request.Genres;
|
||||||
|
|
||||||
var episode = item as Episode;
|
if (item is Episode episode)
|
||||||
if (episode != null)
|
|
||||||
{
|
{
|
||||||
episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
|
episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
|
||||||
episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
|
episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
|
||||||
|
@ -302,14 +301,12 @@ namespace MediaBrowser.Api
|
||||||
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
|
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
|
||||||
item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
|
item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
|
||||||
|
|
||||||
var hasDisplayOrder = item as IHasDisplayOrder;
|
if (item is IHasDisplayOrder hasDisplayOrder)
|
||||||
if (hasDisplayOrder != null)
|
|
||||||
{
|
{
|
||||||
hasDisplayOrder.DisplayOrder = request.DisplayOrder;
|
hasDisplayOrder.DisplayOrder = request.DisplayOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasAspectRatio = item as IHasAspectRatio;
|
if (item is IHasAspectRatio hasAspectRatio)
|
||||||
if (hasAspectRatio != null)
|
|
||||||
{
|
{
|
||||||
hasAspectRatio.AspectRatio = request.AspectRatio;
|
hasAspectRatio.AspectRatio = request.AspectRatio;
|
||||||
}
|
}
|
||||||
|
@ -337,16 +334,14 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
item.ProviderIds = request.ProviderIds;
|
item.ProviderIds = request.ProviderIds;
|
||||||
|
|
||||||
var video = item as Video;
|
if (item is Video video)
|
||||||
if (video != null)
|
|
||||||
{
|
{
|
||||||
video.Video3DFormat = request.Video3DFormat;
|
video.Video3DFormat = request.Video3DFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.AlbumArtists != null)
|
if (request.AlbumArtists != null)
|
||||||
{
|
{
|
||||||
var hasAlbumArtists = item as IHasAlbumArtist;
|
if (item is IHasAlbumArtist hasAlbumArtists)
|
||||||
if (hasAlbumArtists != null)
|
|
||||||
{
|
{
|
||||||
hasAlbumArtists.AlbumArtists = request
|
hasAlbumArtists.AlbumArtists = request
|
||||||
.AlbumArtists
|
.AlbumArtists
|
||||||
|
@ -357,8 +352,7 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
if (request.ArtistItems != null)
|
if (request.ArtistItems != null)
|
||||||
{
|
{
|
||||||
var hasArtists = item as IHasArtist;
|
if (item is IHasArtist hasArtists)
|
||||||
if (hasArtists != null)
|
|
||||||
{
|
{
|
||||||
hasArtists.Artists = request
|
hasArtists.Artists = request
|
||||||
.ArtistItems
|
.ArtistItems
|
||||||
|
@ -367,20 +361,17 @@ namespace MediaBrowser.Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var song = item as Audio;
|
if (item is Audio song)
|
||||||
if (song != null)
|
|
||||||
{
|
{
|
||||||
song.Album = request.Album;
|
song.Album = request.Album;
|
||||||
}
|
}
|
||||||
|
|
||||||
var musicVideo = item as MusicVideo;
|
if (item is MusicVideo musicVideo)
|
||||||
if (musicVideo != null)
|
|
||||||
{
|
{
|
||||||
musicVideo.Album = request.Album;
|
musicVideo.Album = request.Album;
|
||||||
}
|
}
|
||||||
|
|
||||||
var series = item as Series;
|
if (item is Series series)
|
||||||
if (series != null)
|
|
||||||
{
|
{
|
||||||
series.Status = GetSeriesStatus(request);
|
series.Status = GetSeriesStatus(request);
|
||||||
|
|
||||||
|
@ -400,7 +391,6 @@ namespace MediaBrowser.Api
|
||||||
}
|
}
|
||||||
|
|
||||||
return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true);
|
return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -348,28 +348,19 @@ namespace MediaBrowser.Api.Library
|
||||||
|
|
||||||
private string[] GetRepresentativeItemTypes(string contentType)
|
private string[] GetRepresentativeItemTypes(string contentType)
|
||||||
{
|
{
|
||||||
switch (contentType)
|
return contentType switch
|
||||||
{
|
{
|
||||||
case CollectionType.BoxSets:
|
CollectionType.BoxSets => new[] {"BoxSet"},
|
||||||
return new string[] { "BoxSet" };
|
CollectionType.Playlists => new[] {"Playlist"},
|
||||||
case CollectionType.Playlists:
|
CollectionType.Movies => new[] {"Movie"},
|
||||||
return new string[] { "Playlist" };
|
CollectionType.TvShows => new[] {"Series", "Season", "Episode"},
|
||||||
case CollectionType.Movies:
|
CollectionType.Books => new[] {"Book"},
|
||||||
return new string[] { "Movie" };
|
CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"},
|
||||||
case CollectionType.TvShows:
|
CollectionType.HomeVideos => new[] {"Video", "Photo"},
|
||||||
return new string[] { "Series", "Season", "Episode" };
|
CollectionType.Photos => new[] {"Video", "Photo"},
|
||||||
case CollectionType.Books:
|
CollectionType.MusicVideos => new[] {"MusicVideo"},
|
||||||
return new string[] { "Book" };
|
_ => new[] {"Series", "Season", "Episode", "Movie"}
|
||||||
case CollectionType.Music:
|
};
|
||||||
return new string[] { "MusicAlbum", "MusicArtist", "Audio", "MusicVideo" };
|
|
||||||
case CollectionType.HomeVideos:
|
|
||||||
case CollectionType.Photos:
|
|
||||||
return new string[] { "Video", "Photo" };
|
|
||||||
case CollectionType.MusicVideos:
|
|
||||||
return new string[] { "MusicVideo" };
|
|
||||||
default:
|
|
||||||
return new string[] { "Series", "Season", "Episode", "Movie" };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
|
private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
|
||||||
|
@ -397,54 +388,22 @@ namespace MediaBrowser.Api.Library
|
||||||
{
|
{
|
||||||
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
|
return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
|
||||||
{
|
|| string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
|
||||||
return true;
|
|| string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
|
||||||
if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
|
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
|
||||||
.Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
|
.Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
if (metadataOptions.Length == 0)
|
return metadataOptions.Length == 0
|
||||||
{
|
|| metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
|
private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
|
||||||
|
@ -453,50 +412,17 @@ namespace MediaBrowser.Api.Library
|
||||||
{
|
{
|
||||||
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
|
return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)
|
||||||
{
|
&& !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
|
||||||
return false;
|
&& !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
|
||||||
}
|
&& !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase);
|
||||||
if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
|
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
|
||||||
|
@ -561,8 +487,7 @@ namespace MediaBrowser.Api.Library
|
||||||
|
|
||||||
foreach (var type in types)
|
foreach (var type in types)
|
||||||
{
|
{
|
||||||
ImageOption[] defaultImageOptions = null;
|
TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions);
|
||||||
TypeOptions.DefaultImageOptions.TryGetValue(type, out defaultImageOptions);
|
|
||||||
|
|
||||||
typeOptions.Add(new LibraryTypeOptions
|
typeOptions.Add(new LibraryTypeOptions
|
||||||
{
|
{
|
||||||
|
@ -609,8 +534,6 @@ namespace MediaBrowser.Api.Library
|
||||||
|
|
||||||
public object Get(GetSimilarItems request)
|
public object Get(GetSimilarItems request)
|
||||||
{
|
{
|
||||||
var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
|
|
||||||
|
|
||||||
var item = string.IsNullOrEmpty(request.Id) ?
|
var item = string.IsNullOrEmpty(request.Id) ?
|
||||||
(!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
|
(!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
|
||||||
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
|
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
|
||||||
|
@ -668,7 +591,7 @@ namespace MediaBrowser.Api.Library
|
||||||
// ExcludeArtistIds
|
// ExcludeArtistIds
|
||||||
if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
|
if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
|
||||||
{
|
{
|
||||||
query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds);
|
query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BaseItem> itemsResult;
|
List<BaseItem> itemsResult;
|
||||||
|
@ -689,7 +612,6 @@ namespace MediaBrowser.Api.Library
|
||||||
var result = new QueryResult<BaseItemDto>
|
var result = new QueryResult<BaseItemDto>
|
||||||
{
|
{
|
||||||
Items = returnList,
|
Items = returnList,
|
||||||
|
|
||||||
TotalRecordCount = itemsResult.Count
|
TotalRecordCount = itemsResult.Count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -919,12 +841,10 @@ namespace MediaBrowser.Api.Library
|
||||||
|
|
||||||
private BaseItem TranslateParentItem(BaseItem item, User user)
|
private BaseItem TranslateParentItem(BaseItem item, User user)
|
||||||
{
|
{
|
||||||
if (item.GetParent() is AggregateFolder)
|
return item.GetParent() is AggregateFolder
|
||||||
{
|
? _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||||
return _libraryManager.GetUserRootFolder().GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
|
.FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path))
|
||||||
}
|
: item;
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1086,7 +1006,7 @@ namespace MediaBrowser.Api.Library
|
||||||
var item = string.IsNullOrEmpty(request.Id)
|
var item = string.IsNullOrEmpty(request.Id)
|
||||||
? (!request.UserId.Equals(Guid.Empty)
|
? (!request.UserId.Equals(Guid.Empty)
|
||||||
? _libraryManager.GetUserRootFolder()
|
? _libraryManager.GetUserRootFolder()
|
||||||
: (Folder)_libraryManager.RootFolder)
|
: _libraryManager.RootFolder)
|
||||||
: _libraryManager.GetItemById(request.Id);
|
: _libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
if (item == null)
|
if (item == null)
|
||||||
|
@ -1094,18 +1014,13 @@ namespace MediaBrowser.Api.Library
|
||||||
throw new ResourceNotFoundException("Item not found.");
|
throw new ResourceNotFoundException("Item not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseItem[] themeItems = Array.Empty<BaseItem>();
|
IEnumerable<BaseItem> themeItems;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
themeItems = item.GetThemeSongs().ToArray();
|
themeItems = item.GetThemeSongs();
|
||||||
|
|
||||||
if (themeItems.Length > 0)
|
if (themeItems.Any() || !request.InheritFromParent)
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.InheritFromParent)
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1119,11 +1034,9 @@ namespace MediaBrowser.Api.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = GetDtoOptions(_authContext, request);
|
var dtoOptions = GetDtoOptions(_authContext, request);
|
||||||
|
var items = themeItems
|
||||||
var dtos = themeItems
|
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
|
.ToArray();
|
||||||
|
|
||||||
var items = dtos.ToArray();
|
|
||||||
|
|
||||||
return new ThemeMediaResult
|
return new ThemeMediaResult
|
||||||
{
|
{
|
||||||
|
@ -1140,9 +1053,7 @@ namespace MediaBrowser.Api.Library
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
public object Get(GetThemeVideos request)
|
public object Get(GetThemeVideos request)
|
||||||
{
|
{
|
||||||
var result = GetThemeVideos(request);
|
return ToOptimizedResult(GetThemeVideos(request));
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
|
public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
|
||||||
|
@ -1152,7 +1063,7 @@ namespace MediaBrowser.Api.Library
|
||||||
var item = string.IsNullOrEmpty(request.Id)
|
var item = string.IsNullOrEmpty(request.Id)
|
||||||
? (!request.UserId.Equals(Guid.Empty)
|
? (!request.UserId.Equals(Guid.Empty)
|
||||||
? _libraryManager.GetUserRootFolder()
|
? _libraryManager.GetUserRootFolder()
|
||||||
: (Folder)_libraryManager.RootFolder)
|
: _libraryManager.RootFolder)
|
||||||
: _libraryManager.GetItemById(request.Id);
|
: _libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
if (item == null)
|
if (item == null)
|
||||||
|
@ -1160,18 +1071,13 @@ namespace MediaBrowser.Api.Library
|
||||||
throw new ResourceNotFoundException("Item not found.");
|
throw new ResourceNotFoundException("Item not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseItem[] themeItems = Array.Empty<BaseItem>();
|
IEnumerable<BaseItem> themeItems;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
themeItems = item.GetThemeVideos().ToArray();
|
themeItems = item.GetThemeVideos();
|
||||||
|
|
||||||
if (themeItems.Length > 0)
|
if (themeItems.Any() || !request.InheritFromParent)
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.InheritFromParent)
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1186,10 +1092,9 @@ namespace MediaBrowser.Api.Library
|
||||||
|
|
||||||
var dtoOptions = GetDtoOptions(_authContext, request);
|
var dtoOptions = GetDtoOptions(_authContext, request);
|
||||||
|
|
||||||
var dtos = themeItems
|
var items = themeItems
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
|
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
|
||||||
|
.ToArray();
|
||||||
var items = dtos.ToArray();
|
|
||||||
|
|
||||||
return new ThemeMediaResult
|
return new ThemeMediaResult
|
||||||
{
|
{
|
||||||
|
|
|
@ -327,15 +327,11 @@ namespace MediaBrowser.Api.Library
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var mediaPath = request.PathInfo;
|
var mediaPath = request.PathInfo ?? new MediaPathInfo
|
||||||
|
|
||||||
if (mediaPath == null)
|
|
||||||
{
|
{
|
||||||
mediaPath = new MediaPathInfo
|
Path = request.Path
|
||||||
{
|
};
|
||||||
Path = request.Path
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_libraryManager.AddMediaPath(request.Name, mediaPath);
|
_libraryManager.AddMediaPath(request.Name, mediaPath);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
@ -885,11 +885,10 @@ namespace MediaBrowser.Api.LiveTv
|
||||||
{
|
{
|
||||||
// SchedulesDirect requires a SHA1 hash of the user's password
|
// SchedulesDirect requires a SHA1 hash of the user's password
|
||||||
// https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
|
// https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
|
||||||
using (SHA1 sha = SHA1.Create())
|
using SHA1 sha = SHA1.Create();
|
||||||
{
|
|
||||||
return Hex.Encode(
|
return Hex.Encode(
|
||||||
sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
|
sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete(DeleteListingProvider request)
|
public void Delete(DeleteListingProvider request)
|
||||||
|
@ -1050,8 +1049,7 @@ namespace MediaBrowser.Api.LiveTv
|
||||||
{
|
{
|
||||||
query.IsSeries = true;
|
query.IsSeries = true;
|
||||||
|
|
||||||
var series = _libraryManager.GetItemById(request.LibrarySeriesId) as Series;
|
if (_libraryManager.GetItemById(request.LibrarySeriesId) is Series series)
|
||||||
if (series != null)
|
|
||||||
{
|
{
|
||||||
query.Name = series.Name;
|
query.Name = series.Name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,7 +394,7 @@ namespace MediaBrowser.Api.Movies
|
||||||
{
|
{
|
||||||
var people = _libraryManager.GetPeople(new InternalPeopleQuery
|
var people = _libraryManager.GetPeople(new InternalPeopleQuery
|
||||||
{
|
{
|
||||||
PersonTypes = new string[]
|
PersonTypes = new[]
|
||||||
{
|
{
|
||||||
PersonType.Director
|
PersonType.Director
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,12 +137,9 @@ namespace MediaBrowser.Api.Playback
|
||||||
var ext = outputFileExtension.ToLowerInvariant();
|
var ext = outputFileExtension.ToLowerInvariant();
|
||||||
var folder = ServerConfigurationManager.GetTranscodePath();
|
var folder = ServerConfigurationManager.GetTranscodePath();
|
||||||
|
|
||||||
if (EnableOutputInSubFolder)
|
return EnableOutputInSubFolder
|
||||||
{
|
? Path.Combine(folder, filename, filename + ext)
|
||||||
return Path.Combine(folder, filename, filename + ext);
|
: Path.Combine(folder, filename + ext);
|
||||||
}
|
|
||||||
|
|
||||||
return Path.Combine(folder, filename + ext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string GetDefaultEncoderPreset()
|
protected virtual string GetDefaultEncoderPreset()
|
||||||
|
@ -248,14 +245,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
if (state.VideoRequest != null
|
if (state.VideoRequest != null
|
||||||
&& string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
logFilePrefix = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)
|
||||||
{
|
? "ffmpeg-remux" : "ffmpeg-directstream";
|
||||||
logFilePrefix = "ffmpeg-remux";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logFilePrefix = "ffmpeg-directstream";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
|
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
|
||||||
|
@ -389,195 +380,181 @@ namespace MediaBrowser.Api.Playback
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0)
|
switch (i)
|
||||||
{
|
{
|
||||||
request.DeviceProfileId = val;
|
case 0:
|
||||||
}
|
request.DeviceProfileId = val;
|
||||||
else if (i == 1)
|
break;
|
||||||
{
|
case 1:
|
||||||
request.DeviceId = val;
|
request.DeviceId = val;
|
||||||
}
|
break;
|
||||||
else if (i == 2)
|
case 2:
|
||||||
{
|
request.MediaSourceId = val;
|
||||||
request.MediaSourceId = val;
|
break;
|
||||||
}
|
case 3:
|
||||||
else if (i == 3)
|
request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
{
|
break;
|
||||||
request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
case 4:
|
||||||
}
|
if (videoRequest != null)
|
||||||
else if (i == 4)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.VideoCodec = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 5)
|
|
||||||
{
|
|
||||||
request.AudioCodec = val;
|
|
||||||
}
|
|
||||||
else if (i == 6)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 7)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 8)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 9)
|
|
||||||
{
|
|
||||||
request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
else if (i == 10)
|
|
||||||
{
|
|
||||||
request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
else if (i == 11)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 12)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 13)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 14)
|
|
||||||
{
|
|
||||||
request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
else if (i == 15)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.Level = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 16)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 17)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 18)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.Profile = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 19)
|
|
||||||
{
|
|
||||||
// cabac no longer used
|
|
||||||
}
|
|
||||||
else if (i == 20)
|
|
||||||
{
|
|
||||||
request.PlaySessionId = val;
|
|
||||||
}
|
|
||||||
else if (i == 21)
|
|
||||||
{
|
|
||||||
// api_key
|
|
||||||
}
|
|
||||||
else if (i == 22)
|
|
||||||
{
|
|
||||||
request.LiveStreamId = val;
|
|
||||||
}
|
|
||||||
else if (i == 23)
|
|
||||||
{
|
|
||||||
// Duplicating ItemId because of MediaMonkey
|
|
||||||
}
|
|
||||||
else if (i == 24)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
|
||||||
videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i == 25)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
|
||||||
{
|
|
||||||
if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
|
|
||||||
{
|
{
|
||||||
videoRequest.SubtitleMethod = method;
|
videoRequest.VideoCodec = val;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
break;
|
||||||
else if (i == 26)
|
case 5:
|
||||||
{
|
request.AudioCodec = val;
|
||||||
request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
|
break;
|
||||||
}
|
case 6:
|
||||||
else if (i == 27)
|
if (videoRequest != null)
|
||||||
{
|
{
|
||||||
if (videoRequest != null)
|
videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
{
|
}
|
||||||
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
break;
|
||||||
}
|
case 7:
|
||||||
else if (i == 28)
|
if (videoRequest != null)
|
||||||
{
|
{
|
||||||
request.Tag = val;
|
videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
else if (i == 29)
|
|
||||||
{
|
break;
|
||||||
if (videoRequest != null)
|
case 8:
|
||||||
{
|
if (videoRequest != null)
|
||||||
videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
{
|
||||||
}
|
videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
else if (i == 30)
|
|
||||||
{
|
break;
|
||||||
request.SubtitleCodec = val;
|
case 9:
|
||||||
}
|
request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
else if (i == 31)
|
break;
|
||||||
{
|
case 10:
|
||||||
if (videoRequest != null)
|
request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
{
|
break;
|
||||||
videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
case 11:
|
||||||
}
|
if (videoRequest != null)
|
||||||
}
|
{
|
||||||
else if (i == 32)
|
videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
|
||||||
{
|
}
|
||||||
if (videoRequest != null)
|
|
||||||
{
|
break;
|
||||||
videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
case 12:
|
||||||
}
|
if (videoRequest != null)
|
||||||
}
|
{
|
||||||
else if (i == 33)
|
videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
{
|
}
|
||||||
request.TranscodeReasons = val;
|
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
|
||||||
|
break;
|
||||||
|
case 15:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.Level = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 17:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 18:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.Profile = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 19:
|
||||||
|
// cabac no longer used
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
request.PlaySessionId = val;
|
||||||
|
break;
|
||||||
|
case 21:
|
||||||
|
// api_key
|
||||||
|
break;
|
||||||
|
case 22:
|
||||||
|
request.LiveStreamId = val;
|
||||||
|
break;
|
||||||
|
case 23:
|
||||||
|
// Duplicating ItemId because of MediaMonkey
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 25:
|
||||||
|
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
||||||
|
{
|
||||||
|
if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
|
||||||
|
{
|
||||||
|
videoRequest.SubtitleMethod = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 26:
|
||||||
|
request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
|
||||||
|
break;
|
||||||
|
case 27:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 28:
|
||||||
|
request.Tag = val;
|
||||||
|
break;
|
||||||
|
case 29:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 30:
|
||||||
|
request.SubtitleCodec = val;
|
||||||
|
break;
|
||||||
|
case 31:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 33:
|
||||||
|
request.TranscodeReasons = val;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -630,14 +607,9 @@ namespace MediaBrowser.Api.Playback
|
||||||
throw new ArgumentException("Invalid timeseek header");
|
throw new ArgumentException("Invalid timeseek header");
|
||||||
}
|
}
|
||||||
int index = value.IndexOf('-');
|
int index = value.IndexOf('-');
|
||||||
if (index == -1)
|
value = index == -1
|
||||||
{
|
? value.Substring(Npt.Length)
|
||||||
value = value.Substring(Npt.Length);
|
: value.Substring(Npt.Length, index - Npt.Length);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = value.Substring(Npt.Length, index - Npt.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.IndexOf(':') == -1)
|
if (value.IndexOf(':') == -1)
|
||||||
{
|
{
|
||||||
|
@ -856,21 +828,11 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
|
state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
|
||||||
}
|
}
|
||||||
else
|
else if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
|
var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
|
||||||
{
|
|
||||||
var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
|
|
||||||
|
|
||||||
if (caps != null)
|
state.DeviceProfile = caps == null ? DlnaManager.GetProfile(headers) : caps.DeviceProfile;
|
||||||
{
|
|
||||||
state.DeviceProfile = caps.DeviceProfile;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state.DeviceProfile = DlnaManager.GetProfile(headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var profile = state.DeviceProfile;
|
var profile = state.DeviceProfile;
|
||||||
|
|
|
@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
if (isLive)
|
if (isLive)
|
||||||
{
|
{
|
||||||
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
|
job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
|
||||||
|
|
||||||
if (job != null)
|
if (job != null)
|
||||||
{
|
{
|
||||||
|
@ -156,7 +156,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
|
var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
|
||||||
|
|
||||||
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
|
job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
|
||||||
|
|
||||||
if (job != null)
|
if (job != null)
|
||||||
{
|
{
|
||||||
|
@ -168,22 +168,19 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
private string GetLivePlaylistText(string path, int segmentLength)
|
private string GetLivePlaylistText(string path, int segmentLength)
|
||||||
{
|
{
|
||||||
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
{
|
using var reader = new StreamReader(stream);
|
||||||
using (var reader = new StreamReader(stream))
|
|
||||||
{
|
|
||||||
var text = reader.ReadToEnd();
|
|
||||||
|
|
||||||
text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
|
var text = reader.ReadToEnd();
|
||||||
|
|
||||||
var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
|
text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
|
||||||
|
|
||||||
text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
|
var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
|
||||||
//text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
return text;
|
text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
//text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
|
||||||
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
|
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
|
||||||
|
@ -212,29 +209,25 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
||||||
using (var fileStream = GetPlaylistFileStream(playlist))
|
using var fileStream = GetPlaylistFileStream(playlist);
|
||||||
|
using var reader = new StreamReader(fileStream);
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
while (!reader.EndOfStream)
|
||||||
{
|
{
|
||||||
using (var reader = new StreamReader(fileStream))
|
var line = reader.ReadLine();
|
||||||
|
|
||||||
|
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
var count = 0;
|
count++;
|
||||||
|
if (count >= segmentCount)
|
||||||
while (!reader.EndOfStream)
|
|
||||||
{
|
{
|
||||||
var line = reader.ReadLine();
|
Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
|
||||||
|
return;
|
||||||
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
if (count >= segmentCount)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
@ -247,17 +240,13 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
protected Stream GetPlaylistFileStream(string path)
|
protected Stream GetPlaylistFileStream(string path)
|
||||||
{
|
{
|
||||||
var tmpPath = path + ".tmp";
|
return new FileStream(
|
||||||
tmpPath = path;
|
path,
|
||||||
|
FileMode.Open,
|
||||||
try
|
FileAccess.Read,
|
||||||
{
|
FileShare.ReadWrite,
|
||||||
return new FileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
|
IODefaults.FileStreamBufferSize,
|
||||||
}
|
FileOptions.SequentialScan);
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
|
protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
|
||||||
|
|
|
@ -284,7 +284,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
//}
|
//}
|
||||||
|
|
||||||
Logger.LogDebug("returning {0} [general case]", segmentPath);
|
Logger.LogDebug("returning {0} [general case]", segmentPath);
|
||||||
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
||||||
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,8 +438,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
var segmentId = "0";
|
var segmentId = "0";
|
||||||
|
|
||||||
var segmentRequest = request as GetHlsVideoSegment;
|
if (request is GetHlsVideoSegment segmentRequest)
|
||||||
if (segmentRequest != null)
|
|
||||||
{
|
{
|
||||||
segmentId = segmentRequest.SegmentId;
|
segmentId = segmentRequest.SegmentId;
|
||||||
}
|
}
|
||||||
|
@ -690,8 +689,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = state.Request as IMasterHlsRequest;
|
if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming)
|
||||||
if (request != null && !request.EnableAdaptiveBitrateStreaming)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -936,7 +934,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
var framerate = state.VideoStream?.RealFrameRate;
|
var framerate = state.VideoStream?.RealFrameRate;
|
||||||
|
|
||||||
if (framerate != null && framerate.HasValue)
|
if (framerate.HasValue)
|
||||||
{
|
{
|
||||||
// This is to make sure keyframe interval is limited to our segment,
|
// This is to make sure keyframe interval is limited to our segment,
|
||||||
// as forcing keyframes is not enough.
|
// as forcing keyframes is not enough.
|
||||||
|
|
|
@ -234,7 +234,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
OpenToken = mediaSource.OpenToken
|
OpenToken = mediaSource.OpenToken
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
info.MediaSources = new MediaSourceInfo[] { openStreamResult.MediaSource };
|
info.MediaSources = new[] { openStreamResult.MediaSource };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +289,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
|
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
mediaSources = new MediaSourceInfo[] { mediaSource };
|
mediaSources = new[] { mediaSource };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaSources.Length == 0)
|
if (mediaSources.Length == 0)
|
||||||
|
@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
var options = new VideoOptions
|
var options = new VideoOptions
|
||||||
{
|
{
|
||||||
MediaSources = new MediaSourceInfo[] { mediaSource },
|
MediaSources = new[] { mediaSource },
|
||||||
Context = EncodingContext.Streaming,
|
Context = EncodingContext.Streaming,
|
||||||
DeviceId = auth.DeviceId,
|
DeviceId = auth.DeviceId,
|
||||||
ItemId = item.Id,
|
ItemId = item.Id,
|
||||||
|
@ -572,8 +572,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
attachment.DeliveryUrl = string.Format(
|
attachment.DeliveryUrl = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"{0}/Videos/{1}/{2}/Attachments/{3}",
|
"/Videos/{0}/{1}/Attachments/{2}",
|
||||||
ServerConfigurationManager.Configuration.BaseUrl,
|
|
||||||
item.Id,
|
item.Id,
|
||||||
mediaSource.Id,
|
mediaSource.Id,
|
||||||
attachment.Index);
|
attachment.Index);
|
||||||
|
@ -583,7 +582,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
private long? GetMaxBitrate(long? clientMaxBitrate, User user)
|
private long? GetMaxBitrate(long? clientMaxBitrate, User user)
|
||||||
{
|
{
|
||||||
var maxBitrate = clientMaxBitrate;
|
var maxBitrate = clientMaxBitrate;
|
||||||
var remoteClientMaxBitrate = user == null ? 0 : user.Policy.RemoteClientBitrateLimit;
|
var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0;
|
||||||
|
|
||||||
if (remoteClientMaxBitrate <= 0)
|
if (remoteClientMaxBitrate <= 0)
|
||||||
{
|
{
|
||||||
|
@ -662,17 +661,9 @@ namespace MediaBrowser.Api.Playback
|
||||||
};
|
};
|
||||||
}).ThenBy(i =>
|
}).ThenBy(i =>
|
||||||
{
|
{
|
||||||
if (maxBitrate.HasValue)
|
if (maxBitrate.HasValue && i.Bitrate.HasValue)
|
||||||
{
|
{
|
||||||
if (i.Bitrate.HasValue)
|
return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
|
||||||
{
|
|
||||||
if (i.Bitrate.Value <= maxBitrate.Value)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -167,7 +167,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
AudioCodec = request.AudioCodec,
|
AudioCodec = request.AudioCodec,
|
||||||
Protocol = request.TranscodingProtocol,
|
Protocol = request.TranscodingProtocol,
|
||||||
BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
|
BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
|
||||||
MaxAudioChannels = request.TranscodingAudioChannels.HasValue ? request.TranscodingAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : null
|
MaxAudioChannels = request.TranscodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -300,7 +300,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
|
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
|
||||||
// TODO: remove this when we switch back to the segment muxer
|
// TODO: remove this when we switch back to the segment muxer
|
||||||
var supportedHLSContainers = new string[] { "mpegts", "fmp4" };
|
var supportedHLSContainers = new[] { "mpegts", "fmp4" };
|
||||||
|
|
||||||
var newRequest = new GetMasterHlsAudioPlaylist
|
var newRequest = new GetMasterHlsAudioPlaylist
|
||||||
{
|
{
|
||||||
|
|
|
@ -243,9 +243,7 @@ namespace MediaBrowser.Api
|
||||||
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
|
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
|
||||||
var id = Guid.Parse(GetPathValue(1));
|
var id = Guid.Parse(GetPathValue(1));
|
||||||
|
|
||||||
var plugin = _appHost.Plugins.First(p => p.Id == id) as IHasPluginConfiguration;
|
if (!(_appHost.Plugins.First(p => p.Id == id) is IHasPluginConfiguration plugin))
|
||||||
|
|
||||||
if (plugin == null)
|
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,9 +123,7 @@ namespace MediaBrowser.Api.ScheduledTasks
|
||||||
{
|
{
|
||||||
var isHidden = false;
|
var isHidden = false;
|
||||||
|
|
||||||
var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
|
if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
|
||||||
|
|
||||||
if (configurableTask != null)
|
|
||||||
{
|
{
|
||||||
isHidden = configurableTask.IsHidden;
|
isHidden = configurableTask.IsHidden;
|
||||||
}
|
}
|
||||||
|
@ -142,9 +140,7 @@ namespace MediaBrowser.Api.ScheduledTasks
|
||||||
{
|
{
|
||||||
var isEnabled = true;
|
var isEnabled = true;
|
||||||
|
|
||||||
var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
|
if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
|
||||||
|
|
||||||
if (configurableTask != null)
|
|
||||||
{
|
{
|
||||||
isEnabled = configurableTask.IsEnabled;
|
isEnabled = configurableTask.IsEnabled;
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,59 +234,48 @@ namespace MediaBrowser.Api
|
||||||
SetThumbImageInfo(result, item);
|
SetThumbImageInfo(result, item);
|
||||||
SetBackdropImageInfo(result, item);
|
SetBackdropImageInfo(result, item);
|
||||||
|
|
||||||
var program = item as LiveTvProgram;
|
switch (item)
|
||||||
if (program != null)
|
|
||||||
{
|
{
|
||||||
result.StartDate = program.StartDate;
|
case IHasSeries hasSeries:
|
||||||
}
|
result.Series = hasSeries.SeriesName;
|
||||||
|
break;
|
||||||
|
case LiveTvProgram program:
|
||||||
|
result.StartDate = program.StartDate;
|
||||||
|
break;
|
||||||
|
case Series series:
|
||||||
|
if (series.Status.HasValue)
|
||||||
|
{
|
||||||
|
result.Status = series.Status.Value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
var hasSeries = item as IHasSeries;
|
break;
|
||||||
if (hasSeries != null)
|
case MusicAlbum album:
|
||||||
{
|
result.Artists = album.Artists;
|
||||||
result.Series = hasSeries.SeriesName;
|
result.AlbumArtist = album.AlbumArtist;
|
||||||
}
|
break;
|
||||||
|
case Audio song:
|
||||||
|
result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
|
||||||
|
result.Artists = song.Artists;
|
||||||
|
|
||||||
var series = item as Series;
|
MusicAlbum musicAlbum = song.AlbumEntity;
|
||||||
if (series != null)
|
|
||||||
{
|
|
||||||
if (series.Status.HasValue)
|
|
||||||
{
|
|
||||||
result.Status = series.Status.Value.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var album = item as MusicAlbum;
|
if (musicAlbum != null)
|
||||||
|
{
|
||||||
|
result.Album = musicAlbum.Name;
|
||||||
|
result.AlbumId = musicAlbum.Id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Album = song.Album;
|
||||||
|
}
|
||||||
|
|
||||||
if (album != null)
|
break;
|
||||||
{
|
|
||||||
result.Artists = album.Artists;
|
|
||||||
result.AlbumArtist = album.AlbumArtist;
|
|
||||||
}
|
|
||||||
|
|
||||||
var song = item as Audio;
|
|
||||||
|
|
||||||
if (song != null)
|
|
||||||
{
|
|
||||||
result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
|
|
||||||
result.Artists = song.Artists;
|
|
||||||
|
|
||||||
album = song.AlbumEntity;
|
|
||||||
|
|
||||||
if (album != null)
|
|
||||||
{
|
|
||||||
result.Album = album.Name;
|
|
||||||
result.AlbumId = album.Id;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.Album = song.Album;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.ChannelId.Equals(Guid.Empty))
|
if (!item.ChannelId.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
var channel = _libraryManager.GetItemById(item.ChannelId);
|
var channel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
result.ChannelName = channel == null ? null : channel.Name;
|
result.ChannelName = channel?.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -296,12 +285,9 @@ namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null;
|
var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null;
|
||||||
|
|
||||||
if (itemWithImage == null)
|
if (itemWithImage == null && item is Episode)
|
||||||
{
|
{
|
||||||
if (item is Episode)
|
itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
|
||||||
{
|
|
||||||
itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemWithImage == null)
|
if (itemWithImage == null)
|
||||||
|
@ -323,12 +309,8 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
private void SetBackdropImageInfo(SearchHint hint, BaseItem item)
|
private void SetBackdropImageInfo(SearchHint hint, BaseItem item)
|
||||||
{
|
{
|
||||||
var itemWithImage = item.HasImage(ImageType.Backdrop) ? item : null;
|
var itemWithImage = (item.HasImage(ImageType.Backdrop) ? item : null)
|
||||||
|
?? GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
|
||||||
if (itemWithImage == null)
|
|
||||||
{
|
|
||||||
itemWithImage = GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemWithImage != null)
|
if (itemWithImage != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -230,17 +230,14 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
|
|
||||||
if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
|
if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
|
||||||
{
|
{
|
||||||
using (var stream = await GetSubtitles(request).ConfigureAwait(false))
|
using var stream = await GetSubtitles(request).ConfigureAwait(false);
|
||||||
{
|
using var reader = new StreamReader(stream);
|
||||||
using (var reader = new StreamReader(stream))
|
|
||||||
{
|
|
||||||
var text = reader.ReadToEnd();
|
|
||||||
|
|
||||||
text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
|
var text = reader.ReadToEnd();
|
||||||
|
|
||||||
return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
|
text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
|
||||||
}
|
|
||||||
}
|
return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format));
|
return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format));
|
||||||
|
|
|
@ -168,12 +168,9 @@ namespace MediaBrowser.Api.System
|
||||||
.First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
|
.First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
// For older files, assume fully static
|
// For older files, assume fully static
|
||||||
if (file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1))
|
var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite;
|
||||||
{
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.Read);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);
|
return ResultFactory.GetStaticFileResult(Request, file.FullName, fileShare);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -92,10 +92,7 @@ namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
{
|
{
|
||||||
if (KillTimer != null)
|
KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||||
{
|
|
||||||
KillTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -424,9 +424,7 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.SeasonId))
|
if (!string.IsNullOrWhiteSpace(request.SeasonId))
|
||||||
{
|
{
|
||||||
var season = _libraryManager.GetItemById(new Guid(request.SeasonId)) as Season;
|
if (!(_libraryManager.GetItemById(new Guid(request.SeasonId)) is Season season))
|
||||||
|
|
||||||
if (season == null)
|
|
||||||
{
|
{
|
||||||
throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
|
throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
|
||||||
}
|
}
|
||||||
|
@ -444,14 +442,7 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
|
var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
|
||||||
|
|
||||||
if (season == null)
|
episodes = season == null ? new List<BaseItem>() : ((Season)season).GetEpisodes(user, dtoOptions);
|
||||||
{
|
|
||||||
episodes = new List<BaseItem>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
episodes = ((Season)season).GetEpisodes(user, dtoOptions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -126,12 +126,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
|
|
||||||
protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
|
protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (request is GetAlbumArtists)
|
return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query);
|
||||||
{
|
|
||||||
return LibraryManager.GetAlbumArtists(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
return LibraryManager.GetArtists(query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -82,8 +82,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
{
|
{
|
||||||
var parent = GetParentItem(request);
|
var parent = GetParentItem(request);
|
||||||
|
|
||||||
var collectionFolder = parent as IHasCollectionType;
|
if (parent is IHasCollectionType collectionFolder)
|
||||||
if (collectionFolder != null)
|
|
||||||
{
|
{
|
||||||
return collectionFolder.CollectionType;
|
return collectionFolder.CollectionType;
|
||||||
}
|
}
|
||||||
|
@ -274,7 +273,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
DtoOptions = dtoOptions
|
DtoOptions = dtoOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
|
bool Filter(BaseItem i) => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
|
||||||
|
|
||||||
if (parentItem.IsFolder)
|
if (parentItem.IsFolder)
|
||||||
{
|
{
|
||||||
|
@ -284,18 +283,18 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
{
|
{
|
||||||
items = request.Recursive ?
|
items = request.Recursive ?
|
||||||
folder.GetRecursiveChildren(user, query).ToList() :
|
folder.GetRecursiveChildren(user, query).ToList() :
|
||||||
folder.GetChildren(user, true).Where(filter).ToList();
|
folder.GetChildren(user, true).Where(Filter).ToList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
items = request.Recursive ?
|
items = request.Recursive ?
|
||||||
folder.GetRecursiveChildren(filter) :
|
folder.GetRecursiveChildren(Filter) :
|
||||||
folder.Children.Where(filter).ToList();
|
folder.Children.Where(Filter).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
items = new[] { parentItem }.Where(filter).ToList();
|
items = new[] { parentItem }.Where(Filter).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var extractedItems = GetAllItems(request, items);
|
var extractedItems = GetAllItems(request, items);
|
||||||
|
@ -346,30 +345,21 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
|
private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
|
||||||
{
|
{
|
||||||
// Exclude item types
|
// Exclude item types
|
||||||
if (excludeItemTypes.Length > 0)
|
if (excludeItemTypes.Length > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include item types
|
// Include item types
|
||||||
if (includeItemTypes.Length > 0)
|
if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (!includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include MediaTypes
|
// Include MediaTypes
|
||||||
if (mediaTypes.Length > 0)
|
if (mediaTypes.Length > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (!mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -396,12 +396,10 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
|
|
||||||
public VideoType[] GetVideoTypes()
|
public VideoType[] GetVideoTypes()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(VideoTypes))
|
return string.IsNullOrEmpty(VideoTypes)
|
||||||
{
|
? Array.Empty<VideoType>()
|
||||||
return Array.Empty<VideoType>();
|
: VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
}
|
.Select(v => Enum.Parse<VideoType>(v, true)).ToArray();
|
||||||
|
|
||||||
return VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -412,12 +410,10 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
{
|
{
|
||||||
var val = Filters;
|
var val = Filters;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(val))
|
return string.IsNullOrEmpty(val)
|
||||||
{
|
? Array.Empty<ItemFilter>()
|
||||||
return new ItemFilter[] { };
|
: val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
}
|
.Select(v => Enum.Parse<ItemFilter>(v, true)).ToArray();
|
||||||
|
|
||||||
return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -428,12 +424,9 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
{
|
{
|
||||||
var val = ImageTypes;
|
var val = ImageTypes;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(val))
|
return string.IsNullOrEmpty(val)
|
||||||
{
|
? Array.Empty<ImageType>()
|
||||||
return new ImageType[] { };
|
: val.Split(',').Select(v => Enum.Parse<ImageType>(v, true)).ToArray();
|
||||||
}
|
|
||||||
|
|
||||||
return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -469,7 +462,9 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
var sortOrderIndex = sortOrders.Length > i ? i : 0;
|
var sortOrderIndex = sortOrders.Length > i ? i : 0;
|
||||||
|
|
||||||
var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
|
var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
|
||||||
var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending;
|
var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? MediaBrowser.Model.Entities.SortOrder.Descending
|
||||||
|
: MediaBrowser.Model.Entities.SortOrder.Ascending;
|
||||||
|
|
||||||
result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
|
result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,14 +199,12 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
item = _libraryManager.GetUserRootFolder();
|
item = _libraryManager.GetUserRootFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
Folder folder = item as Folder;
|
if (!(item is Folder folder))
|
||||||
if (folder == null)
|
|
||||||
{
|
{
|
||||||
folder = _libraryManager.GetUserRootFolder();
|
folder = _libraryManager.GetUserRootFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasCollectionType = folder as IHasCollectionType;
|
if (folder is IHasCollectionType hasCollectionType
|
||||||
if (hasCollectionType != null
|
|
||||||
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
request.Recursive = true;
|
request.Recursive = true;
|
||||||
|
|
|
@ -139,17 +139,11 @@ namespace MediaBrowser.Api
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var primaryVersion = videosWithVersions.FirstOrDefault();
|
var primaryVersion = videosWithVersions.FirstOrDefault();
|
||||||
|
|
||||||
if (primaryVersion == null)
|
if (primaryVersion == null)
|
||||||
{
|
{
|
||||||
primaryVersion = items.OrderBy(i =>
|
primaryVersion = items.OrderBy(i =>
|
||||||
{
|
{
|
||||||
if (i.Video3DFormat.HasValue)
|
if (i.Video3DFormat.HasValue || i.VideoType != Model.Entities.VideoType.VideoFile)
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i.VideoType != Model.Entities.VideoType.VideoFile)
|
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -158,10 +152,7 @@ namespace MediaBrowser.Api
|
||||||
})
|
})
|
||||||
.ThenByDescending(i =>
|
.ThenByDescending(i =>
|
||||||
{
|
{
|
||||||
var stream = i.GetDefaultVideoStream();
|
return i.GetDefaultVideoStream()?.Width ?? 0;
|
||||||
|
|
||||||
return stream == null || stream.Width == null ? 0 : stream.Width.Value;
|
|
||||||
|
|
||||||
}).First();
|
}).First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
80
MediaBrowser.Common/Extensions/ProcessExtensions.cs
Normal file
80
MediaBrowser.Common/Extensions/ProcessExtensions.cs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="Process"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ProcessExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously wait for the process to exit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process to wait for.</param>
|
||||||
|
/// <param name="timeout">The duration to wait before cancelling waiting for the task.</param>
|
||||||
|
/// <returns>True if the task exited normally, false if the timeout elapsed before the process exited.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">If <see cref="Process.EnableRaisingEvents"/> is not set to true for the process.</exception>
|
||||||
|
public static async Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
using (var cancelTokenSource = new CancellationTokenSource(timeout))
|
||||||
|
{
|
||||||
|
return await WaitForExitAsync(process, cancelTokenSource.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously wait for the process to exit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process to wait for.</param>
|
||||||
|
/// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the process to exit.</param>
|
||||||
|
/// <returns>True if the task exited normally, false if cancelled before the process exited.</returns>
|
||||||
|
public static async Task<bool> WaitForExitAsync(this Process process, CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
if (!process.EnableRaisingEvents)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("EnableRisingEvents must be enabled to async wait for a task to exit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an event handler for the process exit event
|
||||||
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
process.Exited += (sender, args) => tcs.TrySetResult(true);
|
||||||
|
|
||||||
|
// Return immediately if the process has already exited
|
||||||
|
if (process.HasExitedSafe())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register with the cancellation token then await
|
||||||
|
using (var cancelRegistration = cancelToken.Register(() => tcs.TrySetResult(process.HasExitedSafe())))
|
||||||
|
{
|
||||||
|
return await tcs.Task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the associated process has been terminated using
|
||||||
|
/// <see cref="Process.HasExited"/>. This is safe to call even if there is no operating system process
|
||||||
|
/// associated with the <see cref="Process"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process to check the exit status for.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the operating system process referenced by the <see cref="Process"/> component has
|
||||||
|
/// terminated, or if there is no associated operating system process; otherwise, false.
|
||||||
|
/// </returns>
|
||||||
|
private static bool HasExitedSafe(this Process process)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return process.HasExited;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,8 +78,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(hwType)
|
if (!string.IsNullOrEmpty(hwType)
|
||||||
&& encodingOptions.EnableHardwareEncoding
|
&& encodingOptions.EnableHardwareEncoding
|
||||||
&& codecMap.ContainsKey(hwType)
|
&& codecMap.ContainsKey(hwType))
|
||||||
&& CheckVaapi(state, hwType, encodingOptions))
|
|
||||||
{
|
{
|
||||||
var preferredEncoder = codecMap[hwType];
|
var preferredEncoder = codecMap[hwType];
|
||||||
|
|
||||||
|
@ -93,23 +92,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return defaultEncoder;
|
return defaultEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckVaapi(EncodingJobInfo state, string hwType, EncodingOptions encodingOptions)
|
|
||||||
{
|
|
||||||
if (!string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// No vaapi requested, return OK.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(encodingOptions.VaapiDevice))
|
|
||||||
{
|
|
||||||
// No device specified, return OK.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return IsVaapiSupported(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsVaapiSupported(EncodingJobInfo state)
|
private bool IsVaapiSupported(EncodingJobInfo state)
|
||||||
{
|
{
|
||||||
var videoStream = state.VideoStream;
|
var videoStream = state.VideoStream;
|
||||||
|
@ -424,7 +406,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "aac -strict experimental";
|
// Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
|
||||||
|
if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
|
||||||
|
{
|
||||||
|
return "libfdk_aac";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "aac";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -1605,7 +1593,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
// For VAAPI and CUVID decoder
|
// For VAAPI and CUVID decoder
|
||||||
// these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
|
// these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
|
||||||
// thus needs to be manually adjusted.
|
// thus needs to be manually adjusted.
|
||||||
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
if ((IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
||||||
|| (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
|
|| (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
var videoStream = state.VideoStream;
|
var videoStream = state.VideoStream;
|
||||||
|
@ -1648,7 +1636,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||||
else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
||||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -2014,19 +2002,29 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||||
else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
||||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var codec = videoStream.Codec.ToLowerInvariant();
|
var codec = videoStream.Codec.ToLowerInvariant();
|
||||||
var pixelFormat = videoStream.PixelFormat.ToLowerInvariant();
|
var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
// Assert hardware VAAPI decodable (Except h264 10-bit and higher color depth)
|
// Assert 10-bit hardware VAAPI decodable
|
||||||
// TODO: a propery way to detect hardware capabilities and falling back when transcoding is failed
|
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) == -1
|
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||||
|| ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
|
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
|
||||||
&& (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
{
|
||||||
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
/*
|
||||||
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))))
|
Download data from GPU to CPU as p010le format.
|
||||||
|
Colorspace conversion is unnecessary here as libx264 will handle it.
|
||||||
|
If this step is missing, it will fail on AMD but not on intel.
|
||||||
|
*/
|
||||||
|
filters.Add("hwdownload");
|
||||||
|
filters.Add("format=p010le");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert 8-bit hardware VAAPI decodable
|
||||||
|
else if (!isColorDepth10)
|
||||||
{
|
{
|
||||||
filters.Add("hwdownload");
|
filters.Add("hwdownload");
|
||||||
filters.Add("format=nv12");
|
filters.Add("format=nv12");
|
||||||
|
|
|
@ -155,48 +155,45 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||||
inputPath,
|
inputPath,
|
||||||
attachmentStreamIndex,
|
attachmentStreamIndex,
|
||||||
outputPath);
|
outputPath);
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
Arguments = processArgs,
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
|
||||||
ErrorDialog = false
|
|
||||||
};
|
|
||||||
var process = new Process
|
|
||||||
{
|
|
||||||
StartInfo = startInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
int exitCode;
|
||||||
|
|
||||||
process.Start();
|
using (var process = new Process
|
||||||
|
|
||||||
var processTcs = new TaskCompletionSource<bool>();
|
|
||||||
process.EnableRaisingEvents = true;
|
|
||||||
process.Exited += (sender, args) => processTcs.TrySetResult(true);
|
|
||||||
var unregister = cancellationToken.Register(() => processTcs.TrySetResult(process.HasExited));
|
|
||||||
var ranToCompletion = await processTcs.Task.ConfigureAwait(false);
|
|
||||||
unregister.Dispose();
|
|
||||||
|
|
||||||
if (!ranToCompletion)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Killing ffmpeg attachment extraction process");
|
StartInfo = new ProcessStartInfo
|
||||||
process.Kill();
|
{
|
||||||
}
|
Arguments = processArgs,
|
||||||
catch (Exception ex)
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
var ranToCompletion = await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!ranToCompletion)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error killing attachment extraction process");
|
try
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Killing ffmpeg attachment extraction process");
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error killing attachment extraction process");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
|
|
||||||
var failed = false;
|
var failed = false;
|
||||||
|
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
|
|
|
@ -42,6 +42,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
"libvpx",
|
"libvpx",
|
||||||
"libvpx-vp9",
|
"libvpx-vp9",
|
||||||
"aac",
|
"aac",
|
||||||
|
"libfdk_aac",
|
||||||
"libmp3lame",
|
"libmp3lame",
|
||||||
"libopus",
|
"libopus",
|
||||||
"libvorbis",
|
"libvorbis",
|
||||||
|
|
|
@ -13,7 +13,6 @@ using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.MediaEncoding.Probing;
|
using MediaBrowser.MediaEncoding.Probing;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
|
@ -22,6 +21,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Encoder
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
{
|
{
|
||||||
|
@ -38,7 +38,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly Func<ISubtitleEncoder> _subtitleEncoder;
|
private readonly Func<ISubtitleEncoder> _subtitleEncoder;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
@ -58,7 +57,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
ILogger<MediaEncoder> logger,
|
ILogger<MediaEncoder> logger,
|
||||||
IServerConfigurationManager configurationManager,
|
IServerConfigurationManager configurationManager,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IProcessFactory processFactory,
|
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
Func<ISubtitleEncoder> subtitleEncoder,
|
Func<ISubtitleEncoder> subtitleEncoder,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
|
@ -67,7 +65,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_processFactory = processFactory;
|
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
|
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
|
||||||
_subtitleEncoder = subtitleEncoder;
|
_subtitleEncoder = subtitleEncoder;
|
||||||
|
@ -362,30 +359,33 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
: "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
|
: "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
|
||||||
args = string.Format(args, probeSizeArgument, inputPath).Trim();
|
args = string.Format(args, probeSizeArgument, inputPath).Trim();
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
StartInfo = new ProcessStartInfo
|
||||||
UseShellExecute = false,
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
|
||||||
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
|
|
||||||
FileName = _ffprobePath,
|
FileName = _ffprobePath,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
|
|
||||||
|
|
||||||
IsHidden = true,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false,
|
||||||
|
},
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
});
|
};
|
||||||
|
|
||||||
if (forceEnableLogging)
|
if (forceEnableLogging)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogInformation("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogDebug("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var processWrapper = new ProcessWrapper(process, this))
|
using (var processWrapper = new ProcessWrapper(process, this))
|
||||||
|
@ -571,18 +571,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
StartInfo = new ProcessStartInfo
|
||||||
UseShellExecute = false,
|
{
|
||||||
FileName = _ffmpegPath,
|
CreateNoWindow = true,
|
||||||
Arguments = args,
|
UseShellExecute = false,
|
||||||
IsHidden = true,
|
FileName = _ffmpegPath,
|
||||||
ErrorDialog = false,
|
Arguments = args,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false,
|
||||||
|
},
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
});
|
};
|
||||||
|
|
||||||
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogDebug("{ProcessFileName} {ProcessArguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
using (var processWrapper = new ProcessWrapper(process, this))
|
using (var processWrapper = new ProcessWrapper(process, this))
|
||||||
{
|
{
|
||||||
|
@ -599,7 +602,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
timeoutMs = DefaultImageExtractionTimeout;
|
timeoutMs = DefaultImageExtractionTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
|
ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMilliseconds(timeoutMs)).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!ranToCompletion)
|
if (!ranToCompletion)
|
||||||
{
|
{
|
||||||
|
@ -700,23 +703,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var processStartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
FileName = _ffmpegPath,
|
FileName = _ffmpegPath,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
IsHidden = true,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false
|
||||||
EnableRaisingEvents = true
|
};
|
||||||
});
|
|
||||||
|
|
||||||
_logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
|
_logger.LogInformation(processStartInfo.FileName + " " + processStartInfo.Arguments);
|
||||||
|
|
||||||
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
bool ranToCompletion = false;
|
bool ranToCompletion = false;
|
||||||
|
|
||||||
|
var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = processStartInfo,
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
using (var processWrapper = new ProcessWrapper(process, this))
|
using (var processWrapper = new ProcessWrapper(process, this))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -732,7 +739,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
while (isResponsive)
|
while (isResponsive)
|
||||||
{
|
{
|
||||||
if (await process.WaitForExitAsync(30000).ConfigureAwait(false))
|
if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
ranToCompletion = true;
|
ranToCompletion = true;
|
||||||
break;
|
break;
|
||||||
|
@ -949,14 +956,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
|
public ProcessWrapper(Process process, MediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
Process = process;
|
Process = process;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
Process.Exited += OnProcessExited;
|
Process.Exited += OnProcessExited;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IProcess Process { get; }
|
public Process Process { get; }
|
||||||
|
|
||||||
public bool HasExited { get; private set; }
|
public bool HasExited { get; private set; }
|
||||||
|
|
||||||
|
@ -964,7 +971,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
void OnProcessExited(object sender, EventArgs e)
|
void OnProcessExited(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var process = (IProcess)sender;
|
var process = (Process)sender;
|
||||||
|
|
||||||
HasExited = true;
|
HasExited = true;
|
||||||
|
|
||||||
|
@ -979,7 +986,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
DisposeProcess(process);
|
DisposeProcess(process);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeProcess(IProcess process)
|
private void DisposeProcess(Process process)
|
||||||
{
|
{
|
||||||
lock (_mediaEncoder._runningProcessesLock)
|
lock (_mediaEncoder._runningProcessesLock)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -12,7 +13,6 @@ using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
@ -31,7 +31,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
|
|
||||||
public SubtitleEncoder(
|
public SubtitleEncoder(
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
|
@ -40,8 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager)
|
||||||
IProcessFactory processFactory)
|
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_processFactory = processFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
|
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
|
||||||
|
@ -429,50 +426,54 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
encodingParam = " -sub_charenc " + encodingParam;
|
encodingParam = " -sub_charenc " + encodingParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
int exitCode;
|
||||||
|
|
||||||
|
using (var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
|
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
})
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
UseShellExecute = false,
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
|
||||||
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
|
||||||
EnableRaisingEvents = true,
|
|
||||||
IsHidden = true,
|
|
||||||
ErrorDialog = false
|
|
||||||
});
|
|
||||||
|
|
||||||
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error starting ffmpeg");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!ranToCompletion)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Killing ffmpeg subtitle conversion process");
|
process.Start();
|
||||||
|
|
||||||
process.Kill();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error killing subtitle conversion process");
|
_logger.LogError(ex, "Error starting ffmpeg");
|
||||||
|
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!ranToCompletion)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Killing ffmpeg subtitle conversion process");
|
||||||
|
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error killing subtitle conversion process");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
|
|
||||||
var failed = false;
|
var failed = false;
|
||||||
|
|
||||||
if (exitCode == -1)
|
if (exitCode == -1)
|
||||||
|
@ -578,50 +579,54 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
outputCodec,
|
outputCodec,
|
||||||
outputPath);
|
outputPath);
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
int exitCode;
|
||||||
|
|
||||||
|
using (var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
|
Arguments = processArgs,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
})
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
UseShellExecute = false,
|
|
||||||
EnableRaisingEvents = true,
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
|
||||||
Arguments = processArgs,
|
|
||||||
IsHidden = true,
|
|
||||||
ErrorDialog = false
|
|
||||||
});
|
|
||||||
|
|
||||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error starting ffmpeg");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!ranToCompletion)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Killing ffmpeg subtitle extraction process");
|
process.Start();
|
||||||
|
|
||||||
process.Kill();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error killing subtitle extraction process");
|
_logger.LogError(ex, "Error starting ffmpeg");
|
||||||
|
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!ranToCompletion)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Killing ffmpeg subtitle extraction process");
|
||||||
|
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error killing subtitle extraction process");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
|
|
||||||
var failed = false;
|
var failed = false;
|
||||||
|
|
||||||
if (exitCode == -1)
|
if (exitCode == -1)
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Diagnostics
|
|
||||||
{
|
|
||||||
public interface IProcess : IDisposable
|
|
||||||
{
|
|
||||||
event EventHandler Exited;
|
|
||||||
|
|
||||||
void Kill();
|
|
||||||
bool WaitForExit(int timeMs);
|
|
||||||
Task<bool> WaitForExitAsync(int timeMs);
|
|
||||||
int ExitCode { get; }
|
|
||||||
void Start();
|
|
||||||
StreamWriter StandardInput { get; }
|
|
||||||
StreamReader StandardError { get; }
|
|
||||||
StreamReader StandardOutput { get; }
|
|
||||||
ProcessOptions StartInfo { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Diagnostics
|
|
||||||
{
|
|
||||||
public interface IProcessFactory
|
|
||||||
{
|
|
||||||
IProcess Create(ProcessOptions options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ProcessOptions
|
|
||||||
{
|
|
||||||
public string FileName { get; set; }
|
|
||||||
public string Arguments { get; set; }
|
|
||||||
public string WorkingDirectory { get; set; }
|
|
||||||
public bool CreateNoWindow { get; set; }
|
|
||||||
public bool UseShellExecute { get; set; }
|
|
||||||
public bool EnableRaisingEvents { get; set; }
|
|
||||||
public bool ErrorDialog { get; set; }
|
|
||||||
public bool RedirectStandardError { get; set; }
|
|
||||||
public bool RedirectStandardInput { get; set; }
|
|
||||||
public bool RedirectStandardOutput { get; set; }
|
|
||||||
public bool IsHidden { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
6
nuget.config
Normal file
6
nuget.config
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
|
@ -7,6 +7,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||||
{
|
{
|
||||||
public class ExtraTests : BaseVideoTest
|
public class ExtraTests : BaseVideoTest
|
||||||
{
|
{
|
||||||
|
private readonly NamingOptions _videoOptions = new NamingOptions();
|
||||||
|
|
||||||
// Requirements
|
// Requirements
|
||||||
// movie-deleted = ExtraType deletedscene
|
// movie-deleted = ExtraType deletedscene
|
||||||
|
|
||||||
|
@ -15,42 +17,64 @@ namespace Jellyfin.Naming.Tests.Video
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestKodiExtras()
|
public void TestKodiExtras()
|
||||||
{
|
{
|
||||||
var videoOptions = new NamingOptions();
|
Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
|
||||||
|
Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
|
||||||
|
|
||||||
Test("trailer.mp4", ExtraType.Trailer, videoOptions);
|
Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
|
||||||
Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
|
|
||||||
|
|
||||||
Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestExpandedExtras()
|
public void TestExpandedExtras()
|
||||||
{
|
{
|
||||||
var videoOptions = new NamingOptions();
|
Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
|
||||||
|
Test("trailer.mp3", null, _videoOptions);
|
||||||
|
Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
|
||||||
|
|
||||||
Test("trailer.mp4", ExtraType.Trailer, videoOptions);
|
Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
|
||||||
Test("trailer.mp3", null, videoOptions);
|
Test("theme.mkv", null, _videoOptions);
|
||||||
Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
|
|
||||||
|
|
||||||
Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
|
Test("300-scene.mp4", ExtraType.Scene, _videoOptions);
|
||||||
Test("theme.mkv", null, videoOptions);
|
Test("300-scene2.mp4", ExtraType.Scene, _videoOptions);
|
||||||
|
Test("300-clip.mp4", ExtraType.Clip, _videoOptions);
|
||||||
|
|
||||||
Test("300-scene.mp4", ExtraType.Scene, videoOptions);
|
Test("300-deleted.mp4", ExtraType.DeletedScene, _videoOptions);
|
||||||
Test("300-scene2.mp4", ExtraType.Scene, videoOptions);
|
Test("300-deletedscene.mp4", ExtraType.DeletedScene, _videoOptions);
|
||||||
Test("300-clip.mp4", ExtraType.Clip, videoOptions);
|
Test("300-interview.mp4", ExtraType.Interview, _videoOptions);
|
||||||
|
Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, _videoOptions);
|
||||||
|
}
|
||||||
|
|
||||||
Test("300-deleted.mp4", ExtraType.DeletedScene, videoOptions);
|
[Theory]
|
||||||
Test("300-deletedscene.mp4", ExtraType.DeletedScene, videoOptions);
|
[InlineData(ExtraType.BehindTheScenes, "behind the scenes" )]
|
||||||
Test("300-interview.mp4", ExtraType.Interview, videoOptions);
|
[InlineData(ExtraType.DeletedScene, "deleted scenes" )]
|
||||||
Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, videoOptions);
|
[InlineData(ExtraType.Interview, "interviews" )]
|
||||||
|
[InlineData(ExtraType.Scene, "scenes" )]
|
||||||
|
[InlineData(ExtraType.Sample, "samples" )]
|
||||||
|
[InlineData(ExtraType.Clip, "shorts" )]
|
||||||
|
[InlineData(ExtraType.Clip, "featurettes" )]
|
||||||
|
[InlineData(ExtraType.Unknown, "extras" )]
|
||||||
|
public void TestDirectories(ExtraType type, string dirName)
|
||||||
|
{
|
||||||
|
Test(dirName + "/300.mp4", type, _videoOptions);
|
||||||
|
Test("300/" + dirName + "/something.mkv", type, _videoOptions);
|
||||||
|
Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", type, _videoOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("gibberish")]
|
||||||
|
[InlineData("not a scene")]
|
||||||
|
[InlineData("The Big Short")]
|
||||||
|
public void TestNonExtraDirectories(string dirName)
|
||||||
|
{
|
||||||
|
Test(dirName + "/300.mp4", null, _videoOptions);
|
||||||
|
Test("300/" + dirName + "/something.mkv", null, _videoOptions);
|
||||||
|
Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", null, _videoOptions);
|
||||||
|
Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null, _videoOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestSample()
|
public void TestSample()
|
||||||
{
|
{
|
||||||
var videoOptions = new NamingOptions();
|
Test("300-sample.mp4", ExtraType.Sample, _videoOptions);
|
||||||
|
|
||||||
Test("300-sample.mp4", ExtraType.Sample, videoOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions)
|
private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user