mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-07-03 12:52:56 +01:00
Merge remote-tracking branch 'upstream/master' into random
This commit is contained in:
@@ -13,7 +13,6 @@ using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -40,14 +39,13 @@ namespace MediaBrowser.Api
|
||||
internal IHttpResultFactory ResultFactory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The application paths
|
||||
/// Gets the configuration manager.
|
||||
/// </summary>
|
||||
private readonly IServerConfigurationManager _config;
|
||||
internal IServerConfigurationManager ConfigurationManager { get; }
|
||||
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
public readonly IProcessFactory ProcessFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The active transcoding jobs
|
||||
@@ -73,15 +71,13 @@ namespace MediaBrowser.Api
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IProcessFactory processFactory,
|
||||
IHttpResultFactory resultFactory)
|
||||
{
|
||||
Logger = logger;
|
||||
_sessionManager = sessionManager;
|
||||
_config = config;
|
||||
ConfigurationManager = config;
|
||||
_fileSystem = fileSystem;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
ProcessFactory = processFactory;
|
||||
ResultFactory = resultFactory;
|
||||
|
||||
_sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
|
||||
@@ -160,17 +156,12 @@ namespace MediaBrowser.Api
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public EncodingOptions GetEncodingOptions()
|
||||
{
|
||||
return ConfigurationManagerExtensions.GetConfiguration<EncodingOptions>(_config, "encoding");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the encoded media cache.
|
||||
/// </summary>
|
||||
private void DeleteEncodedMediaCache()
|
||||
{
|
||||
var path = _config.ApplicationPaths.GetTranscodingTempPath();
|
||||
var path = ConfigurationManager.GetTranscodePath();
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -298,11 +297,26 @@ namespace MediaBrowser.Api
|
||||
var pathInfo = Parse(Request.PathInfo);
|
||||
var first = pathInfo[0];
|
||||
|
||||
string baseUrl = ApiEntryPoint.Instance.ConfigurationManager.Configuration.BaseUrl;
|
||||
|
||||
// backwards compatibility
|
||||
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
|
||||
if (baseUrl.Length == 0)
|
||||
{
|
||||
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(first, baseUrl.Remove(0, 1)))
|
||||
{
|
||||
index++;
|
||||
var second = pathInfo[1];
|
||||
if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return pathInfo[index];
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace MediaBrowser.Api
|
||||
public bool? IsFile { get; set; }
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]
|
||||
public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>>
|
||||
{
|
||||
@@ -192,22 +193,18 @@ namespace MediaBrowser.Api
|
||||
|
||||
var networkPrefix = UncSeparatorString + UncSeparatorString;
|
||||
|
||||
if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) && path.LastIndexOf(UncSeparator) == 1)
|
||||
if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase)
|
||||
&& path.LastIndexOf(UncSeparator) == 1)
|
||||
{
|
||||
return ToOptimizedResult(GetNetworkShares(path).OrderBy(i => i.Path).ToList());
|
||||
return ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
|
||||
}
|
||||
|
||||
return ToOptimizedResult(GetFileSystemEntries(request).ToList());
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public object Get(GetNetworkShares request)
|
||||
{
|
||||
var path = request.Path;
|
||||
|
||||
var shares = GetNetworkShares(path).OrderBy(i => i.Path).ToList();
|
||||
|
||||
return ToOptimizedResult(shares);
|
||||
}
|
||||
=> ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
@@ -241,26 +238,7 @@ namespace MediaBrowser.Api
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public object Get(GetNetworkDevices request)
|
||||
{
|
||||
var result = _networkManager.GetNetworkDevices().ToList();
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network shares.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>IEnumerable{FileSystemEntryInfo}.</returns>
|
||||
private IEnumerable<FileSystemEntryInfo> GetNetworkShares(string path)
|
||||
{
|
||||
return _networkManager.GetNetworkShares(path).Where(s => s.ShareType == NetworkShareType.Disk).Select(c => new FileSystemEntryInfo
|
||||
{
|
||||
Name = c.Name,
|
||||
Path = Path.Combine(path, c.Name),
|
||||
Type = FileSystemEntryType.NetworkShare
|
||||
});
|
||||
}
|
||||
=> ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file system entries.
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
return new ItemFields[] { };
|
||||
return Array.Empty<ItemFields>();
|
||||
}
|
||||
|
||||
return val.Split(',').Select(v =>
|
||||
@@ -41,6 +41,7 @@ namespace MediaBrowser.Api
|
||||
{
|
||||
return (ItemFields?)value;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}).Where(i => i.HasValue).Select(i => i.Value).ToArray();
|
||||
|
||||
@@ -227,15 +227,17 @@ namespace MediaBrowser.Api
|
||||
//item.ProductionYear = request.ProductionYear;
|
||||
//item.Name = request.Name;
|
||||
|
||||
return _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(new DirectoryService(Logger, _fileSystem))
|
||||
{
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ReplaceAllMetadata = true,
|
||||
ReplaceAllImages = request.ReplaceAllImages,
|
||||
SearchResult = request
|
||||
|
||||
}, CancellationToken.None);
|
||||
return _providerManager.RefreshFullItem(
|
||||
item,
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ReplaceAllMetadata = true,
|
||||
ReplaceAllImages = request.ReplaceAllImages,
|
||||
SearchResult = request
|
||||
},
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -294,11 +296,9 @@ namespace MediaBrowser.Api
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||
using (var stream = result.Content)
|
||||
using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
|
||||
{
|
||||
using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
|
||||
{
|
||||
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
||||
}
|
||||
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||
@@ -311,9 +311,6 @@ namespace MediaBrowser.Api
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private string GetFullCachePath(string filename)
|
||||
{
|
||||
return Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename);
|
||||
}
|
||||
|
||||
=> Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
private MetadataRefreshOptions GetRefreshOptions(RefreshItem request)
|
||||
{
|
||||
return new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
return new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
MetadataRefreshMode = request.MetadataRefreshMode,
|
||||
ImageRefreshMode = request.ImageRefreshMode,
|
||||
|
||||
@@ -225,13 +225,15 @@ namespace MediaBrowser.Api
|
||||
|
||||
if (displayOrderChanged)
|
||||
{
|
||||
_providerManager.QueueRefresh(series.Id, new MetadataRefreshOptions(new DirectoryService(Logger, _fileSystem))
|
||||
{
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ReplaceAllMetadata = true
|
||||
|
||||
}, RefreshPriority.High);
|
||||
_providerManager.QueueRefresh(
|
||||
series.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ReplaceAllMetadata = true
|
||||
},
|
||||
RefreshPriority.High);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Api.UserLibrary;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -25,7 +26,6 @@ using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using static MediaBrowser.Common.HexHelper;
|
||||
|
||||
namespace MediaBrowser.Api.LiveTv
|
||||
{
|
||||
@@ -887,8 +887,9 @@ namespace MediaBrowser.Api.LiveTv
|
||||
{
|
||||
// SchedulesDirect requires a SHA1 hash of the user's password
|
||||
// https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
|
||||
using (SHA1 sha = SHA1.Create()) {
|
||||
return ToHexString(
|
||||
using (SHA1 sha = SHA1.Create())
|
||||
{
|
||||
return Hex.Encode(
|
||||
sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Services;
|
||||
@@ -126,12 +126,6 @@ namespace MediaBrowser.Api
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
///
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
@@ -139,7 +133,7 @@ namespace MediaBrowser.Api
|
||||
/// <returns>System.Object.</returns>
|
||||
public object Get(GetPackage request)
|
||||
{
|
||||
var packages = _installationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: typeof(PackageService).Assembly.GetName().Version).Result;
|
||||
var packages = _installationManager.GetAvailablePackages().Result;
|
||||
|
||||
var result = packages.FirstOrDefault(p => string.Equals(p.guid, request.AssemblyGuid ?? "none", StringComparison.OrdinalIgnoreCase))
|
||||
?? packages.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -154,7 +148,7 @@ namespace MediaBrowser.Api
|
||||
/// <returns>System.Object.</returns>
|
||||
public async Task<object> Get(GetPackages request)
|
||||
{
|
||||
IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages(CancellationToken.None, false, request.PackageType, typeof(PackageService).Assembly.GetName().Version).ConfigureAwait(false);
|
||||
IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(request.TargetSystems))
|
||||
{
|
||||
@@ -163,11 +157,6 @@ namespace MediaBrowser.Api
|
||||
packages = packages.Where(p => apps.Contains(p.targetSystem));
|
||||
}
|
||||
|
||||
if (request.IsPremium.HasValue)
|
||||
{
|
||||
packages = packages.Where(p => p.isPremium == request.IsPremium.Value);
|
||||
}
|
||||
|
||||
if (request.IsAdult.HasValue)
|
||||
{
|
||||
packages = packages.Where(p => p.adult == request.IsAdult.Value);
|
||||
@@ -188,13 +177,21 @@ namespace MediaBrowser.Api
|
||||
/// <exception cref="ResourceNotFoundException"></exception>
|
||||
public async Task Post(InstallPackage request)
|
||||
{
|
||||
var package = string.IsNullOrEmpty(request.Version) ?
|
||||
await _installationManager.GetLatestCompatibleVersion(request.Name, request.AssemblyGuid, typeof(PackageService).Assembly.GetName().Version, request.UpdateClass).ConfigureAwait(false) :
|
||||
await _installationManager.GetPackage(request.Name, request.AssemblyGuid, request.UpdateClass, Version.Parse(request.Version)).ConfigureAwait(false);
|
||||
var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
|
||||
var package = _installationManager.GetCompatibleVersions(
|
||||
packages,
|
||||
request.Name,
|
||||
new Guid(request.AssemblyGuid),
|
||||
string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version),
|
||||
request.UpdateClass).FirstOrDefault();
|
||||
|
||||
if (package == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name));
|
||||
throw new ResourceNotFoundException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Package not found: {0}",
|
||||
request.Name));
|
||||
}
|
||||
|
||||
await _installationManager.InstallPackage(package);
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
@@ -141,7 +142,7 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
var ext = outputFileExtension.ToLowerInvariant();
|
||||
var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
|
||||
var folder = ServerConfigurationManager.GetTranscodePath();
|
||||
|
||||
if (EnableOutputInSubFolder)
|
||||
{
|
||||
@@ -215,7 +216,7 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
var encodingOptions = ServerConfigurationManager.GetEncodingOptions();
|
||||
|
||||
var process = new Process()
|
||||
{
|
||||
@@ -289,17 +290,22 @@ namespace MediaBrowser.Api.Playback
|
||||
throw;
|
||||
}
|
||||
|
||||
Logger.LogDebug("Launched ffmpeg process");
|
||||
state.TranscodingJob = transcodingJob;
|
||||
|
||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||
_ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
|
||||
|
||||
// Wait for the file to exist before proceeeding
|
||||
while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
|
||||
var ffmpegTargetFile = state.WaitForPath ?? outputPath;
|
||||
Logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile);
|
||||
while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited)
|
||||
{
|
||||
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile);
|
||||
|
||||
if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited)
|
||||
{
|
||||
await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
@@ -314,6 +320,7 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
StartThrottler(state, transcodingJob);
|
||||
}
|
||||
Logger.LogDebug("StartFfMpeg() finished successfully");
|
||||
|
||||
return transcodingJob;
|
||||
}
|
||||
@@ -582,7 +589,7 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
/// <summary>
|
||||
/// Parses query parameters as StreamOptions
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="request">The stream request.</param>
|
||||
private void ParseStreamOptions(StreamRequest request)
|
||||
{
|
||||
@@ -839,7 +846,7 @@ namespace MediaBrowser.Api.Playback
|
||||
? GetOutputFileExtension(state)
|
||||
: ('.' + state.OutputContainer);
|
||||
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
var encodingOptions = ServerConfigurationManager.GetEncodingOptions();
|
||||
|
||||
state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext);
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
if (File.Exists(segmentPath))
|
||||
{
|
||||
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
||||
Logger.LogDebug("returning {0} [it exists, try 1]", segmentPath);
|
||||
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -207,6 +208,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
||||
transcodingLock.Release();
|
||||
released = true;
|
||||
Logger.LogDebug("returning {0} [it exists, try 2]", segmentPath);
|
||||
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
@@ -243,6 +245,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex);
|
||||
|
||||
state.WaitForPath = segmentPath;
|
||||
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
@@ -277,7 +280,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
// await Task.Delay(50, cancellationToken).ConfigureAwait(false);
|
||||
//}
|
||||
|
||||
Logger.LogInformation("returning {0}", segmentPath);
|
||||
Logger.LogDebug("returning {0} [general case]", segmentPath);
|
||||
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
||||
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -458,56 +461,68 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
TranscodingJob transcodingJob,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var segmentFileExists = File.Exists(segmentPath);
|
||||
|
||||
// If all transcoding has completed, just return immediately
|
||||
if (transcodingJob != null && transcodingJob.HasExited && segmentFileExists)
|
||||
var segmentExists = File.Exists(segmentPath);
|
||||
if (segmentExists)
|
||||
{
|
||||
return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
|
||||
}
|
||||
if (transcodingJob != null && transcodingJob.HasExited)
|
||||
{
|
||||
// Transcoding job is over, so assume all existing files are ready
|
||||
Logger.LogDebug("serving up {0} as transcode is over", segmentPath);
|
||||
return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (segmentFileExists)
|
||||
{
|
||||
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
|
||||
|
||||
// If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready
|
||||
if (segmentIndex < currentTranscodingIndex)
|
||||
{
|
||||
Logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex);
|
||||
return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
var segmentFilename = Path.GetFileName(segmentPath);
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
var nextSegmentPath = GetSegmentPath(state, playlistPath, segmentIndex + 1);
|
||||
if (transcodingJob != null)
|
||||
{
|
||||
try
|
||||
while (!cancellationToken.IsCancellationRequested && !transcodingJob.HasExited)
|
||||
{
|
||||
var text = File.ReadAllText(playlistPath, Encoding.UTF8);
|
||||
|
||||
// If it appears in the playlist, it's done
|
||||
if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
// To be considered ready, the segment file has to exist AND
|
||||
// either the transcoding job should be done or next segment should also exist
|
||||
if (segmentExists)
|
||||
{
|
||||
if (!segmentFileExists)
|
||||
{
|
||||
segmentFileExists = File.Exists(segmentPath);
|
||||
}
|
||||
if (segmentFileExists)
|
||||
if (transcodingJob.HasExited || File.Exists(nextSegmentPath))
|
||||
{
|
||||
Logger.LogDebug("serving up {0} as it deemed ready", segmentPath);
|
||||
return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
|
||||
}
|
||||
//break;
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// May get an error if the file is locked
|
||||
else
|
||||
{
|
||||
segmentExists = File.Exists(segmentPath);
|
||||
if (segmentExists)
|
||||
{
|
||||
continue; // avoid unnecessary waiting if segment just became available
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
if (!File.Exists(segmentPath))
|
||||
{
|
||||
Logger.LogWarning("cannot serve {0} as transcoding quit before we got there", segmentPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath);
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -521,6 +536,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
FileShare = FileShareMode.ReadWrite,
|
||||
OnComplete = () =>
|
||||
{
|
||||
Logger.LogDebug("finished serving {0}", segmentPath);
|
||||
if (transcodingJob != null)
|
||||
{
|
||||
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
|
||||
@@ -909,9 +925,23 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
else
|
||||
{
|
||||
var keyFrameArg = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"",
|
||||
GetStartNumber(state) * state.SegmentLength,
|
||||
state.SegmentLength.ToString(CultureInfo.InvariantCulture));
|
||||
state.SegmentLength);
|
||||
if (state.TargetFramerate.HasValue)
|
||||
{
|
||||
// This is to make sure keyframe interval is limited to our segment,
|
||||
// as forcing keyframes is not enough.
|
||||
// Example: we encoded half of desired length, then codec detected
|
||||
// scene cut and inserted a keyframe; next forced keyframe would
|
||||
// be created outside of segment, which breaks seeking.
|
||||
keyFrameArg += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" -g {0} -keyint_min {0}",
|
||||
(int)(state.SegmentLength * state.TargetFramerate)
|
||||
);
|
||||
}
|
||||
|
||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||
|
||||
@@ -955,6 +985,15 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec);
|
||||
|
||||
if (state.BaseRequest.BreakOnNonKeyFrames)
|
||||
{
|
||||
// FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe
|
||||
// breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable
|
||||
// to produce a missing part of video stream before first keyframe is encountered, which may lead to
|
||||
// awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js
|
||||
Logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request");
|
||||
state.BaseRequest.BreakOnNonKeyFrames = false;
|
||||
}
|
||||
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
|
||||
|
||||
// If isEncoding is true we're actually starting ffmpeg
|
||||
@@ -965,14 +1004,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
|
||||
|
||||
var timeDeltaParam = string.Empty;
|
||||
|
||||
if (isEncoding && state.TargetFramerate > 0)
|
||||
{
|
||||
float startTime = 1 / (state.TargetFramerate.Value * 2);
|
||||
timeDeltaParam = string.Format(CultureInfo.InvariantCulture, "-segment_time_delta {0:F3}", startTime);
|
||||
}
|
||||
|
||||
var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
|
||||
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -980,7 +1011,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
}
|
||||
|
||||
return string.Format(
|
||||
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
|
||||
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f hls -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
|
||||
inputModifier,
|
||||
EncodingHelper.GetInputArgument(state, encodingOptions),
|
||||
threads,
|
||||
@@ -988,11 +1019,10 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
GetVideoArguments(state, encodingOptions),
|
||||
GetAudioArguments(state, encodingOptions),
|
||||
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
|
||||
segmentFormat,
|
||||
startNumberParam,
|
||||
outputPath,
|
||||
outputTsArg,
|
||||
timeDeltaParam,
|
||||
segmentFormat
|
||||
outputPath
|
||||
).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
@@ -83,13 +83,11 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
public class HlsSegmentService : BaseApiService
|
||||
{
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public HlsSegmentService(IServerApplicationPaths appPaths, IServerConfigurationManager config, IFileSystem fileSystem)
|
||||
public HlsSegmentService(IServerConfigurationManager config, IFileSystem fileSystem)
|
||||
{
|
||||
_appPaths = appPaths;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
@@ -97,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
public Task<object> Get(GetHlsPlaylistLegacy request)
|
||||
{
|
||||
var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
|
||||
file = Path.Combine(_appPaths.TranscodingTempPath, file);
|
||||
file = Path.Combine(_config.GetTranscodePath(), file);
|
||||
|
||||
return GetFileResult(file, file);
|
||||
}
|
||||
@@ -115,8 +113,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
public Task<object> Get(GetHlsVideoSegmentLegacy request)
|
||||
{
|
||||
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
|
||||
|
||||
var transcodeFolderPath = _config.ApplicationPaths.TranscodingTempPath;
|
||||
var transcodeFolderPath = _config.GetTranscodePath();
|
||||
file = Path.Combine(transcodeFolderPath, file);
|
||||
|
||||
var normalizedPlaylistId = request.PlaylistId;
|
||||
@@ -136,7 +133,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
{
|
||||
// TODO: Deprecate with new iOS app
|
||||
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
|
||||
file = Path.Combine(_appPaths.TranscodingTempPath, file);
|
||||
file = Path.Combine(_config.GetTranscodePath(), file);
|
||||
|
||||
return ResultFactory.GetStaticFileResult(Request, file, FileShareMode.ReadWrite);
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ namespace MediaBrowser.Api.Session
|
||||
DateCreated = DateTime.UtcNow,
|
||||
DeviceId = _appHost.SystemId,
|
||||
DeviceName = _appHost.FriendlyName,
|
||||
AppVersion = _appHost.ApplicationVersion
|
||||
AppVersion = _appHost.ApplicationVersionString
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace MediaBrowser.Api
|
||||
{
|
||||
[Route("/Startup/Complete", "POST", Summary = "Reports that the startup wizard has been completed", IsHidden = true)]
|
||||
public class ReportStartupWizardComplete : IReturnVoid
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Startup/Configuration", "GET", Summary = "Gets initial server configuration", IsHidden = true)]
|
||||
public class GetStartupConfiguration : IReturn<StartupConfiguration>
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Startup/Configuration", "POST", Summary = "Updates initial server configuration", IsHidden = true)]
|
||||
public class UpdateStartupConfiguration : StartupConfiguration, IReturnVoid
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Startup/RemoteAccess", "POST", Summary = "Updates initial server configuration", IsHidden = true)]
|
||||
public class UpdateRemoteAccessConfiguration : IReturnVoid
|
||||
{
|
||||
public bool EnableRemoteAccess { get; set; }
|
||||
public bool EnableAutomaticPortMapping { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Startup/User", "GET", Summary = "Gets initial user info", IsHidden = true)]
|
||||
public class GetStartupUser : IReturn<StartupUser>
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Startup/User", "POST", Summary = "Updates initial user info", IsHidden = true)]
|
||||
public class UpdateStartupUser : StartupUser
|
||||
{
|
||||
}
|
||||
|
||||
[Authenticated(AllowBeforeStartupWizard = true, Roles = "Admin")]
|
||||
public class StartupWizardService : BaseApiService
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
public StartupWizardService(IServerConfigurationManager config, IHttpClient httpClient, IServerApplicationHost appHost, IUserManager userManager, IMediaEncoder mediaEncoder)
|
||||
{
|
||||
_config = config;
|
||||
_appHost = appHost;
|
||||
_userManager = userManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public void Post(ReportStartupWizardComplete request)
|
||||
{
|
||||
_config.Configuration.IsStartupWizardCompleted = true;
|
||||
_config.SetOptimalValues();
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
public object Get(GetStartupConfiguration request)
|
||||
{
|
||||
var result = new StartupConfiguration
|
||||
{
|
||||
UICulture = _config.Configuration.UICulture,
|
||||
MetadataCountryCode = _config.Configuration.MetadataCountryCode,
|
||||
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Post(UpdateStartupConfiguration request)
|
||||
{
|
||||
_config.Configuration.UICulture = request.UICulture;
|
||||
_config.Configuration.MetadataCountryCode = request.MetadataCountryCode;
|
||||
_config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
public void Post(UpdateRemoteAccessConfiguration request)
|
||||
{
|
||||
_config.Configuration.EnableRemoteAccess = request.EnableRemoteAccess;
|
||||
_config.Configuration.EnableUPnP = request.EnableAutomaticPortMapping;
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
public object Get(GetStartupUser request)
|
||||
{
|
||||
var user = _userManager.Users.First();
|
||||
|
||||
return new StartupUser
|
||||
{
|
||||
Name = user.Name,
|
||||
Password = user.Password
|
||||
};
|
||||
}
|
||||
|
||||
public async Task Post(UpdateStartupUser request)
|
||||
{
|
||||
var user = _userManager.Users.First();
|
||||
|
||||
user.Name = request.Name;
|
||||
|
||||
_userManager.UpdateUser(user);
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Password))
|
||||
{
|
||||
await _userManager.ChangePassword(user, request.Password).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StartupConfiguration
|
||||
{
|
||||
public string UICulture { get; set; }
|
||||
public string MetadataCountryCode { get; set; }
|
||||
public string PreferredMetadataLanguage { get; set; }
|
||||
}
|
||||
|
||||
public class StartupUser
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,13 +279,12 @@ namespace MediaBrowser.Api.Subtitles
|
||||
await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(Logger, _fileSystem)), RefreshPriority.High);
|
||||
_providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error downloading subtitles");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,11 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
NameContains = query.NameContains ?? query.SearchTerm
|
||||
});
|
||||
|
||||
if ((query.IsFavorite ?? false) && query.User != null)
|
||||
{
|
||||
items = items.Where(i => UserDataRepository.GetUserData(query.User, i).IsFavorite).ToList();
|
||||
}
|
||||
|
||||
return new QueryResult<(BaseItem, ItemCounts)>
|
||||
{
|
||||
TotalRecordCount = items.Count,
|
||||
|
||||
@@ -413,7 +413,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
|
||||
if (!hasMetdata)
|
||||
{
|
||||
var options = new MetadataRefreshOptions(new DirectoryService(Logger, _fileSystem))
|
||||
var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
|
||||
@@ -10,6 +10,7 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Library;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Api.UserLibrary
|
||||
{
|
||||
@@ -49,7 +50,12 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
private readonly IAuthorizationContext _authContext;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public UserViewsService(IUserManager userManager, IUserViewManager userViewManager, IDtoService dtoService, IAuthorizationContext authContext, ILibraryManager libraryManager)
|
||||
public UserViewsService(
|
||||
IUserManager userManager,
|
||||
IUserViewManager userViewManager,
|
||||
IDtoService dtoService,
|
||||
IAuthorizationContext authContext,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_userViewManager = userViewManager;
|
||||
|
||||
Reference in New Issue
Block a user