mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-06 03:26:07 +02:00
Merge remote-tracking branch 'upstream/master' into api-stream-return
This commit is contained in:
commit
eb2dcbddc0
57 changed files with 310 additions and 330 deletions
|
@ -78,6 +78,7 @@
|
||||||
- [nvllsvm](https://github.com/nvllsvm)
|
- [nvllsvm](https://github.com/nvllsvm)
|
||||||
- [nyanmisaka](https://github.com/nyanmisaka)
|
- [nyanmisaka](https://github.com/nyanmisaka)
|
||||||
- [oddstr13](https://github.com/oddstr13)
|
- [oddstr13](https://github.com/oddstr13)
|
||||||
|
- [orryverducci](https://github.com/orryverducci)
|
||||||
- [petermcneil](https://github.com/petermcneil)
|
- [petermcneil](https://github.com/petermcneil)
|
||||||
- [Phlogi](https://github.com/Phlogi)
|
- [Phlogi](https://github.com/Phlogi)
|
||||||
- [pjeanjean](https://github.com/pjeanjean)
|
- [pjeanjean](https://github.com/pjeanjean)
|
||||||
|
|
|
@ -14,7 +14,7 @@ COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM debian:buster-slim
|
FROM debian:buster-slim
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||||
|
|
|
@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||||
FROM arm64v8/debian:buster-slim
|
FROM arm64v8/debian:buster-slim
|
||||||
|
|
|
@ -1363,7 +1363,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id);
|
||||||
|
|
||||||
return new ServerItem(_libraryManager.GetUserRootFolder());
|
return new ServerItem(_libraryManager.GetUserRootFolder());
|
||||||
}
|
}
|
||||||
|
|
|
@ -948,7 +948,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error adding xml value: {value}", name);
|
_logger.LogError(ex, "Error adding xml value: {Value}", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -960,7 +960,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error adding xml value: {value}", value);
|
_logger.LogError(ex, "Error adding xml value: {Value}", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error loading configuration file: {path}", path);
|
Logger.LogError(ex, "Error loading configuration file: {Path}", path);
|
||||||
|
|
||||||
return Activator.CreateInstance(configurationType);
|
return Activator.CreateInstance(configurationType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,6 +279,10 @@ namespace Emby.Server.Implementations
|
||||||
Password = ServerConfigurationManager.Configuration.CertificatePassword
|
Password = ServerConfigurationManager.Configuration.CertificatePassword
|
||||||
};
|
};
|
||||||
Certificate = GetCertificate(CertificateInfo);
|
Certificate = GetCertificate(CertificateInfo);
|
||||||
|
|
||||||
|
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
||||||
|
ApplicationVersionString = ApplicationVersion.ToString(3);
|
||||||
|
ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ExpandVirtualPath(string path)
|
public string ExpandVirtualPath(string path)
|
||||||
|
@ -308,16 +312,16 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
|
public Version ApplicationVersion { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
public string ApplicationVersionString { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current application user agent.
|
/// Gets the current application user agent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application user agent.</value>
|
/// <value>The application user agent.</value>
|
||||||
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
|
public string ApplicationUserAgent { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the email address for use within a comment section of a user agent field.
|
/// Gets the email address for use within a comment section of a user agent field.
|
||||||
|
@ -1401,7 +1405,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
foreach (var assembly in assemblies)
|
foreach (var assembly in assemblies)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Found API endpoints in plugin {name}", assembly.FullName);
|
Logger.LogDebug("Found API endpoints in plugin {Name}", assembly.FullName);
|
||||||
yield return assembly;
|
yield return assembly;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -890,7 +890,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error writing to channel cache file: {path}", path);
|
_logger.LogError(ex, "Error writing to channel cache file: {Path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
|
// Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
|
||||||
_logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {itemName}", item.Name);
|
_logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
|
_logger.LogInformation("{Name} ({Path}) will be refreshed.", item.Name, item.Path);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -160,11 +160,11 @@ namespace Emby.Server.Implementations.IO
|
||||||
// For now swallow and log.
|
// For now swallow and log.
|
||||||
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
|
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
|
||||||
// Should we remove it from it's parent?
|
// Should we remove it from it's parent?
|
||||||
_logger.LogError(ex, "Error refreshing {name}", item.Name);
|
_logger.LogError(ex, "Error refreshing {Name}", item.Name);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error refreshing {name}", item.Name);
|
_logger.LogError(ex, "Error refreshing {Name}", item.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,6 +214,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
|
@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
|
_logger.LogError(ex, "Error in ReportFileSystemChanged for {Path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -398,30 +398,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void SetReadOnly(string path, bool isReadOnly)
|
|
||||||
{
|
|
||||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = GetExtendedFileSystemInfo(path);
|
|
||||||
|
|
||||||
if (info.Exists && info.IsReadOnly != isReadOnly)
|
|
||||||
{
|
|
||||||
if (isReadOnly)
|
|
||||||
{
|
|
||||||
File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.ReadOnly);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var attributes = File.GetAttributes(path);
|
|
||||||
attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
|
|
||||||
File.SetAttributes(path, attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
|
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
|
||||||
{
|
{
|
||||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
||||||
|
@ -707,14 +683,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
|
return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void SetExecutable(string path)
|
|
||||||
{
|
|
||||||
if (OperatingSystem.Id == OperatingSystemId.Darwin)
|
|
||||||
{
|
|
||||||
RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RunProcess(string path, string args, string workingDirectory)
|
private static void RunProcess(string path, string args, string workingDirectory)
|
||||||
{
|
{
|
||||||
using (var process = Process.Start(new ProcessStartInfo
|
using (var process = Process.Start(new ProcessStartInfo
|
||||||
|
|
|
@ -11,8 +11,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
public class StreamHelper : IStreamHelper
|
public class StreamHelper : IStreamHelper
|
||||||
{
|
{
|
||||||
private const int StreamCopyToBufferSize = 81920;
|
|
||||||
|
|
||||||
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
|
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||||
|
@ -83,37 +81,9 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int totalBytesRead = 0;
|
|
||||||
|
|
||||||
int bytesRead;
|
|
||||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
|
||||||
{
|
|
||||||
var bytesToWrite = bytesRead;
|
|
||||||
|
|
||||||
if (bytesToWrite > 0)
|
|
||||||
{
|
|
||||||
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
totalBytesRead += bytesRead;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalBytesRead;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(IODefaults.CopyToBufferSize);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
|
|
|
@ -52,10 +52,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
|
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
|
||||||
|
|
||||||
// The media source is infinite so we need to handle stopping ourselves
|
// The media source is infinite so we need to handle stopping ourselves
|
||||||
var durationToken = new CancellationTokenSource(duration);
|
using var durationToken = new CancellationTokenSource(duration);
|
||||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
||||||
|
|
||||||
await directStreamProvider.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
|
await directStreamProvider.CopyToAsync(output, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||||
|
@ -72,7 +72,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
UserAgent = "Emby/3.0",
|
UserAgent = "Emby/3.0",
|
||||||
|
|
||||||
// Shouldn't matter but may cause issues
|
// Shouldn't matter but may cause issues
|
||||||
DecompressionMethod = CompressionMethods.None
|
DecompressionMethod = CompressionMethods.None,
|
||||||
|
CancellationToken = cancellationToken
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
|
using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
|
||||||
|
@ -88,10 +89,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
|
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
|
||||||
|
|
||||||
// The media source if infinite so we need to handle stopping ourselves
|
// The media source if infinite so we need to handle stopping ourselves
|
||||||
var durationToken = new CancellationTokenSource(duration);
|
using var durationToken = new CancellationTokenSource(duration);
|
||||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
||||||
|
|
||||||
await _streamHelper.CopyUntilCancelled(response.Content, output, 81920, cancellationToken).ConfigureAwait(false);
|
await _streamHelper.CopyUntilCancelled(
|
||||||
|
response.Content,
|
||||||
|
output,
|
||||||
|
IODefaults.CopyToBufferSize,
|
||||||
|
cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -604,11 +604,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
@ -808,11 +803,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ActiveRecordingInfo> GetAllActiveRecordings()
|
|
||||||
{
|
|
||||||
return _activeRecordings.Values.Where(i => i.Timer.Status == RecordingStatus.InProgress && !i.CancellationTokenSource.IsCancellationRequested);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
|
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(path))
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
@ -1015,16 +1005,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
throw new Exception("Tuner not found.");
|
throw new Exception("Tuner not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing)
|
|
||||||
{
|
|
||||||
var json = _jsonSerializer.SerializeToString(mediaSource);
|
|
||||||
mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
|
||||||
|
|
||||||
mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id;
|
|
||||||
|
|
||||||
return mediaSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
|
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(channelId))
|
if (string.IsNullOrWhiteSpace(channelId))
|
||||||
|
@ -1654,7 +1634,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, _config);
|
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
||||||
|
|
|
@ -8,12 +8,9 @@ using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
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.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
@ -26,26 +23,24 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
|
private readonly IJsonSerializer _json;
|
||||||
|
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
private bool _hasExited;
|
private bool _hasExited;
|
||||||
private Stream _logFileStream;
|
private Stream _logFileStream;
|
||||||
private string _targetPath;
|
private string _targetPath;
|
||||||
private Process _process;
|
private Process _process;
|
||||||
private readonly IJsonSerializer _json;
|
|
||||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
|
|
||||||
public EncodedRecorder(
|
public EncodedRecorder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IJsonSerializer json,
|
IJsonSerializer json)
|
||||||
IServerConfigurationManager config)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_json = json;
|
_json = json;
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CopySubtitles => false;
|
private static bool CopySubtitles => false;
|
||||||
|
@ -58,19 +53,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// The media source is infinite so we need to handle stopping ourselves
|
// The media source is infinite so we need to handle stopping ourselves
|
||||||
var durationToken = new CancellationTokenSource(duration);
|
using var durationToken = new CancellationTokenSource(duration);
|
||||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
||||||
|
|
||||||
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false);
|
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncodingOptions GetEncodingOptions()
|
|
||||||
{
|
|
||||||
return _config.GetConfiguration<EncodingOptions>("encoding");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_targetPath = targetFile;
|
_targetPath = targetFile;
|
||||||
|
@ -108,7 +98,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
StartInfo = processStartInfo,
|
StartInfo = processStartInfo,
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
};
|
};
|
||||||
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
|
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process);
|
||||||
|
|
||||||
_process.Start();
|
_process.Start();
|
||||||
|
|
||||||
|
@ -221,20 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string GetOutputSizeParam()
|
protected string GetOutputSizeParam()
|
||||||
{
|
=> "-vf \"yadif=0:-1:0\"";
|
||||||
var filters = new List<string>();
|
|
||||||
|
|
||||||
filters.Add("yadif=0:-1:0");
|
|
||||||
|
|
||||||
var output = string.Empty;
|
|
||||||
|
|
||||||
if (filters.Count > 0)
|
|
||||||
{
|
|
||||||
output += string.Format(CultureInfo.InvariantCulture, " -vf \"{0}\"", string.Join(",", filters.ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Stop()
|
private void Stop()
|
||||||
{
|
{
|
||||||
|
@ -291,7 +268,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the exited.
|
/// Processes the exited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnFfMpegProcessExited(Process process, string inputFile)
|
private void OnFfMpegProcessExited(Process process)
|
||||||
{
|
{
|
||||||
using (process)
|
using (process)
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,14 +24,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
{
|
{
|
||||||
public class SchedulesDirect : IListingsProvider
|
public class SchedulesDirect : IListingsProvider
|
||||||
{
|
{
|
||||||
|
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
|
||||||
|
|
||||||
private readonly ILogger<SchedulesDirect> _logger;
|
private readonly ILogger<SchedulesDirect> _logger;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
|
||||||
private readonly IApplicationHost _appHost;
|
private readonly IApplicationHost _appHost;
|
||||||
|
|
||||||
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
|
|
||||||
|
|
||||||
public SchedulesDirect(
|
public SchedulesDirect(
|
||||||
ILogger<SchedulesDirect> logger,
|
ILogger<SchedulesDirect> logger,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
|
@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
while (start <= end)
|
while (start <= end)
|
||||||
{
|
{
|
||||||
dates.Add(start.ToString("yyyy-MM-dd"));
|
dates.Add(start.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
|
||||||
start = start.AddDays(1);
|
start = start.AddDays(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,13 +367,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(details.originalAirDate))
|
if (!string.IsNullOrWhiteSpace(details.originalAirDate))
|
||||||
{
|
{
|
||||||
info.OriginalAirDate = DateTime.Parse(details.originalAirDate);
|
info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture);
|
||||||
info.ProductionYear = info.OriginalAirDate.Value.Year;
|
info.ProductionYear = info.OriginalAirDate.Value.Year;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.movie != null)
|
if (details.movie != null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(details.movie.year) && int.TryParse(details.movie.year, out int year))
|
if (!string.IsNullOrEmpty(details.movie.year)
|
||||||
|
&& int.TryParse(details.movie.year, out int year))
|
||||||
{
|
{
|
||||||
info.ProductionYear = year;
|
info.ProductionYear = year;
|
||||||
}
|
}
|
||||||
|
@ -587,7 +588,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
NameValuePair savedToken = null;
|
NameValuePair savedToken;
|
||||||
if (!_tokens.TryGetValue(username, out savedToken))
|
if (!_tokens.TryGetValue(username, out savedToken))
|
||||||
{
|
{
|
||||||
savedToken = new NameValuePair();
|
savedToken = new NameValuePair();
|
||||||
|
@ -633,7 +634,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseInfo> Post(HttpRequestOptions options,
|
private async Task<HttpResponseInfo> Post(
|
||||||
|
HttpRequestOptions options,
|
||||||
bool enableRetry,
|
bool enableRetry,
|
||||||
ListingsProviderInfo providerInfo)
|
ListingsProviderInfo providerInfo)
|
||||||
{
|
{
|
||||||
|
@ -663,7 +665,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
return await Post(options, false, providerInfo).ConfigureAwait(false);
|
return await Post(options, false, providerInfo).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseInfo> Get(HttpRequestOptions options,
|
private async Task<HttpResponseInfo> Get(
|
||||||
|
HttpRequestOptions options,
|
||||||
bool enableRetry,
|
bool enableRetry,
|
||||||
ListingsProviderInfo providerInfo)
|
ListingsProviderInfo providerInfo)
|
||||||
{
|
{
|
||||||
|
@ -693,7 +696,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
return await Get(options, false, providerInfo).ConfigureAwait(false);
|
return await Get(options, false, providerInfo).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetTokenInternal(string username, string password,
|
private async Task<string> GetTokenInternal(
|
||||||
|
string username,
|
||||||
|
string password,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var httpOptions = new HttpRequestOptions()
|
var httpOptions = new HttpRequestOptions()
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
"ScheduledTaskFailedWithName": "{0} mislykkes",
|
"ScheduledTaskFailedWithName": "{0} mislykkes",
|
||||||
"ScheduledTaskStartedWithName": "{0} startet",
|
"ScheduledTaskStartedWithName": "{0} startet",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
|
"ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
|
||||||
"Shows": "Programmer",
|
"Shows": "Program",
|
||||||
"Songs": "Sanger",
|
"Songs": "Sanger",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
|
"StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
|
||||||
"SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}",
|
"SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}",
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
"UserOnlineFromDevice": "{0} er tilkoblet fra {1}",
|
"UserOnlineFromDevice": "{0} er tilkoblet fra {1}",
|
||||||
"UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
|
"UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
|
||||||
"UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}",
|
"UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} har startet avspilling {1}",
|
"UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}",
|
"UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
|
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
|
||||||
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
|
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
"AuthenticationSucceededWithUserName": "{0} Har logga inn",
|
"AuthenticationSucceededWithUserName": "{0} Har logga inn",
|
||||||
"Artists": "Artistar",
|
"Artists": "Artistar",
|
||||||
"Application": "Program",
|
"Application": "Program",
|
||||||
"AppDeviceValues": "App: {0}, Einheit: {1}",
|
"AppDeviceValues": "App: {0}, Eining: {1}",
|
||||||
"Albums": "Album",
|
"Albums": "Album",
|
||||||
"NotificationOptionServerRestartRequired": "Tenaren krev omstart",
|
"NotificationOptionServerRestartRequired": "Tenaren krev omstart",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert",
|
"NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert",
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
"NotificationOptionPluginInstalled": "Tilleggsprogram installert",
|
"NotificationOptionPluginInstalled": "Tilleggsprogram installert",
|
||||||
"NotificationOptionPluginError": "Tilleggsprogram feila",
|
"NotificationOptionPluginError": "Tilleggsprogram feila",
|
||||||
"NotificationOptionNewLibraryContent": "Nytt innhald er lagt til",
|
"NotificationOptionNewLibraryContent": "Nytt innhald er lagt til",
|
||||||
"NotificationOptionInstallationFailed": "Installasjonen feila",
|
"NotificationOptionInstallationFailed": "Installasjonsfeil",
|
||||||
"NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp",
|
"NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa",
|
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa",
|
||||||
"NotificationOptionAudioPlayback": "Lydavspilling påbyrja",
|
"NotificationOptionAudioPlayback": "Lydavspilling påbyrja",
|
||||||
|
@ -56,5 +56,62 @@
|
||||||
"MusicVideos": "Musikkvideoar",
|
"MusicVideos": "Musikkvideoar",
|
||||||
"Music": "Musikk",
|
"Music": "Musikk",
|
||||||
"Movies": "Filmar",
|
"Movies": "Filmar",
|
||||||
"MixedContent": "Blanda innhald"
|
"MixedContent": "Blanda innhald",
|
||||||
|
"Sync": "Synkronisera",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Last ned manglande undertekstar",
|
||||||
|
"TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.",
|
||||||
|
"TaskRefreshChannels": "Oppdater kanalar",
|
||||||
|
"TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gamal.",
|
||||||
|
"TaskCleanTranscode": "Reins transkodemappe",
|
||||||
|
"TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.",
|
||||||
|
"TaskUpdatePlugins": "Oppdaterer programtillegg",
|
||||||
|
"TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.",
|
||||||
|
"TaskRefreshPeople": "Oppdater personar",
|
||||||
|
"TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.",
|
||||||
|
"TaskCleanLogs": "Reins loggmappe",
|
||||||
|
"TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.",
|
||||||
|
"TaskRefreshLibrary": "Skann mediebibliotek",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.",
|
||||||
|
"TaskRefreshChapterImages": "Trekk ut kapittelbilete",
|
||||||
|
"TaskCleanCacheDescription": "Slettar mellomlagra filer som ikkje lengre trengst av systemet.",
|
||||||
|
"TaskCleanCache": "Rens mappe for hurtiglager",
|
||||||
|
"TasksChannelsCategory": "Internettkanalar",
|
||||||
|
"TasksApplicationCategory": "Applikasjon",
|
||||||
|
"TasksLibraryCategory": "Bibliotek",
|
||||||
|
"TasksMaintenanceCategory": "Vedlikehald",
|
||||||
|
"VersionNumber": "Versjon {0}",
|
||||||
|
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
|
||||||
|
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
|
||||||
|
"UserStoppedPlayingItemWithValues": "{0} har fullført avspeling {1} på {2}",
|
||||||
|
"UserStartedPlayingItemWithValues": "{0} spelar {1} på {2}",
|
||||||
|
"UserPolicyUpdatedWithName": "Brukarreglar har blitt oppdatert for {0}",
|
||||||
|
"UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
|
||||||
|
"UserOnlineFromDevice": "{0} er direktekopla frå {1}",
|
||||||
|
"UserOfflineFromDevice": "{0} har kopla frå {1}",
|
||||||
|
"UserLockedOutWithName": "Brukar {0} har blitt utestengd",
|
||||||
|
"UserDownloadingItemWithValues": "{0} lastar ned {1}",
|
||||||
|
"UserDeletedWithName": "Brukar {0} er sletta",
|
||||||
|
"UserCreatedWithName": "Brukar {0} er oppretta",
|
||||||
|
"User": "Brukar",
|
||||||
|
"TvShows": "TV-seriar",
|
||||||
|
"System": "System",
|
||||||
|
"SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}",
|
||||||
|
"StartupEmbyServerIsLoading": "Jellyfintenaren laster. Prøv igjen om litt.",
|
||||||
|
"Songs": "Songar",
|
||||||
|
"Shows": "Program",
|
||||||
|
"ServerNameNeedsToBeRestarted": "{0} må omstartast",
|
||||||
|
"ScheduledTaskStartedWithName": "{0} starta",
|
||||||
|
"ScheduledTaskFailedWithName": "{0} feila",
|
||||||
|
"ProviderValue": "Leverandør: {0}",
|
||||||
|
"PluginUpdatedWithName": "{0} blei oppdatert",
|
||||||
|
"PluginUninstalledWithName": "{0} blei avinstallert",
|
||||||
|
"PluginInstalledWithName": "{0} blei installert",
|
||||||
|
"Plugin": "Programtillegg",
|
||||||
|
"Playlists": "Speleliste",
|
||||||
|
"Photos": "Foto",
|
||||||
|
"NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa",
|
||||||
|
"NotificationOptionVideoPlayback": "Videoavspeling starta",
|
||||||
|
"NotificationOptionUserLockedOut": "Brukar er utestengd",
|
||||||
|
"NotificationOptionTaskFailed": "Planlagt oppgåve feila"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
{
|
{
|
||||||
|
@ -21,10 +21,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
/// Gets or sets the application paths.
|
/// Gets or sets the application paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
private IApplicationPaths ApplicationPaths { get; set; }
|
private readonly IApplicationPaths _applicationPaths;
|
||||||
|
|
||||||
private readonly ILogger<DeleteCacheFileTask> _logger;
|
private readonly ILogger<DeleteCacheFileTask> _logger;
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
|
||||||
|
@ -37,20 +35,41 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
ILocalizationManager localization)
|
ILocalizationManager localization)
|
||||||
{
|
{
|
||||||
ApplicationPaths = appPaths;
|
_applicationPaths = appPaths;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => _localization.GetLocalizedString("TaskCleanCache");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Key => "DeleteCacheFiles";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsHidden => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsLogged => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the triggers that define when the task will run.
|
/// Creates the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return new[] {
|
return new[]
|
||||||
|
{
|
||||||
// Every so often
|
// Every so often
|
||||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||||
};
|
};
|
||||||
|
@ -68,7 +87,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.CachePath, minDateModified, progress);
|
DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.CachePath, minDateModified, progress);
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -81,7 +100,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress);
|
DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.TempDirectory, minDateModified, progress);
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -91,7 +110,6 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the cache files from directory with a last write time less than a given date.
|
/// Deletes the cache files from directory with a last write time less than a given date.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -164,26 +182,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
_logger.LogError(ex, "Error deleting file {path}", path);
|
_logger.LogError(ex, "Error deleting file {path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => _localization.GetLocalizedString("TaskCleanCache");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Key => "DeleteCacheFiles";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsEnabled => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsLogged => true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,27 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Key => "PluginUpdates";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsHidden => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsLogged => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the triggers that define when the task will run.
|
/// Creates the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -98,26 +119,5 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Key => "PluginUpdates";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsEnabled => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsLogged => true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
public class DailyTrigger : ITaskTrigger
|
public class DailyTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the time of day to trigger the task to run.
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the time of day to trigger the task to run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The time of day.</value>
|
/// <value>The time of day.</value>
|
||||||
public TimeSpan TimeOfDay { get; set; }
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
|
@ -69,11 +74,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [triggered].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EventArgs> Triggered;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [triggered].
|
/// Called when [triggered].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -11,6 +11,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IntervalTrigger : ITaskTrigger
|
public class IntervalTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
|
private DateTime _lastStartDate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> Triggered;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the interval.
|
/// Gets or sets the interval.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -28,8 +35,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
/// <value>The timer.</value>
|
/// <value>The timer.</value>
|
||||||
private Timer Timer { get; set; }
|
private Timer Timer { get; set; }
|
||||||
|
|
||||||
private DateTime _lastStartDate;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stars waiting for the trigger action.
|
/// Stars waiting for the trigger action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -88,11 +93,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [triggered].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EventArgs> Triggered;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [triggered].
|
/// Called when [triggered].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -12,6 +12,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StartupTrigger : ITaskTrigger
|
public class StartupTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> Triggered;
|
||||||
|
|
||||||
public int DelayMs { get; set; }
|
public int DelayMs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -48,20 +53,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [triggered].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EventArgs> Triggered;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [triggered].
|
/// Called when [triggered].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnTriggered()
|
private void OnTriggered()
|
||||||
{
|
{
|
||||||
if (Triggered != null)
|
Triggered?.Invoke(this, EventArgs.Empty);
|
||||||
{
|
|
||||||
Triggered(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
public class WeeklyTrigger : ITaskTrigger
|
public class WeeklyTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the time of day to trigger the task to run.
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the time of day to trigger the task to run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The time of day.</value>
|
/// <value>The time of day.</value>
|
||||||
public TimeSpan TimeOfDay { get; set; }
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
|
@ -95,20 +100,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [triggered].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EventArgs> Triggered;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [triggered].
|
/// Called when [triggered].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnTriggered()
|
private void OnTriggered()
|
||||||
{
|
{
|
||||||
if (Triggered != null)
|
Triggered?.Invoke(this, EventArgs.Empty);
|
||||||
{
|
|
||||||
Triggered(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
|
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
|
||||||
itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
|
itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
|
||||||
_displayPreferencesManager.SaveChanges(itemPreferences);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
|
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
|
||||||
|
@ -167,8 +166,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
itemPrefs.ViewType = viewType;
|
itemPrefs.ViewType = viewType;
|
||||||
}
|
}
|
||||||
|
|
||||||
_displayPreferencesManager.SaveChanges(existingDisplayPreferences);
|
_displayPreferencesManager.SaveChanges();
|
||||||
_displayPreferencesManager.SaveChanges(itemPrefs);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,8 @@ namespace Jellyfin.Api.Controllers
|
||||||
[HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")]
|
[HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")]
|
||||||
[Produces(MediaTypeNames.Text.Xml)]
|
[Produces(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||||
|
[HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
|
||||||
|
[HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||||
public ActionResult GetContentDirectory([FromRoute] string serverId)
|
public ActionResult GetContentDirectory([FromRoute] string serverId)
|
||||||
|
@ -77,8 +79,8 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serverId">Server UUID.</param>
|
/// <param name="serverId">Server UUID.</param>
|
||||||
/// <returns>Dlna media receiver registrar xml.</returns>
|
/// <returns>Dlna media receiver registrar xml.</returns>
|
||||||
[HttpGet("{serverId}/MediaReceiverRegistrar")]
|
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
|
||||||
[HttpGet("{serverId}/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
|
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
|
||||||
[Produces(MediaTypeNames.Text.Xml)]
|
[Produces(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
@ -94,7 +96,8 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="serverId">Server UUID.</param>
|
/// <param name="serverId">Server UUID.</param>
|
||||||
/// <returns>Dlna media receiver registrar xml.</returns>
|
/// <returns>Dlna media receiver registrar xml.</returns>
|
||||||
[HttpGet("{serverId}/ConnectionManager")]
|
[HttpGet("{serverId}/ConnectionManager")]
|
||||||
[HttpGet("{serverId}/ConnectionManager.xml", Name = "GetConnectionManager_2")]
|
[HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
|
||||||
|
[HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
|
||||||
[Produces(MediaTypeNames.Text.Xml)]
|
[Produces(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
|
|
@ -1361,15 +1361,20 @@ namespace Jellyfin.Api.Controllers
|
||||||
segmentFormat = "mpegts";
|
segmentFormat = "mpegts";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var maxMuxingQueueSize = encodingOptions.MaxMuxingQueueSize > 128
|
||||||
|
? encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
|
||||||
|
: "128";
|
||||||
|
|
||||||
return string.Format(
|
return string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -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}\"",
|
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -individual_header_trailer 0 -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
_encodingHelper.GetInputArgument(state, encodingOptions),
|
_encodingHelper.GetInputArgument(state, encodingOptions),
|
||||||
threads,
|
threads,
|
||||||
mapArgs,
|
mapArgs,
|
||||||
GetVideoArguments(state, encodingOptions, startNumber),
|
GetVideoArguments(state, encodingOptions, startNumber),
|
||||||
GetAudioArguments(state, encodingOptions),
|
GetAudioArguments(state, encodingOptions),
|
||||||
|
maxMuxingQueueSize,
|
||||||
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
|
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
|
||||||
segmentFormat,
|
segmentFormat,
|
||||||
startNumberParam,
|
startNumberParam,
|
||||||
|
|
|
@ -14,22 +14,21 @@ namespace Jellyfin.Server.Implementations.Users
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DisplayPreferencesManager : IDisplayPreferencesManager
|
public class DisplayPreferencesManager : IDisplayPreferencesManager
|
||||||
{
|
{
|
||||||
private readonly JellyfinDbProvider _dbProvider;
|
private readonly JellyfinDb _dbContext;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
|
/// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dbProvider">The Jellyfin db provider.</param>
|
/// <param name="dbContext">The database context.</param>
|
||||||
public DisplayPreferencesManager(JellyfinDbProvider dbProvider)
|
public DisplayPreferencesManager(JellyfinDb dbContext)
|
||||||
{
|
{
|
||||||
_dbProvider = dbProvider;
|
_dbContext = dbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public DisplayPreferences GetDisplayPreferences(Guid userId, string client)
|
public DisplayPreferences GetDisplayPreferences(Guid userId, string client)
|
||||||
{
|
{
|
||||||
using var dbContext = _dbProvider.CreateContext();
|
var prefs = _dbContext.DisplayPreferences
|
||||||
var prefs = dbContext.DisplayPreferences
|
|
||||||
.Include(pref => pref.HomeSections)
|
.Include(pref => pref.HomeSections)
|
||||||
.FirstOrDefault(pref =>
|
.FirstOrDefault(pref =>
|
||||||
pref.UserId == userId && string.Equals(pref.Client, client));
|
pref.UserId == userId && string.Equals(pref.Client, client));
|
||||||
|
@ -37,7 +36,7 @@ namespace Jellyfin.Server.Implementations.Users
|
||||||
if (prefs == null)
|
if (prefs == null)
|
||||||
{
|
{
|
||||||
prefs = new DisplayPreferences(userId, client);
|
prefs = new DisplayPreferences(userId, client);
|
||||||
dbContext.DisplayPreferences.Add(prefs);
|
_dbContext.DisplayPreferences.Add(prefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return prefs;
|
return prefs;
|
||||||
|
@ -46,14 +45,13 @@ namespace Jellyfin.Server.Implementations.Users
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client)
|
public ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client)
|
||||||
{
|
{
|
||||||
using var dbContext = _dbProvider.CreateContext();
|
var prefs = _dbContext.ItemDisplayPreferences
|
||||||
var prefs = dbContext.ItemDisplayPreferences
|
|
||||||
.FirstOrDefault(pref => pref.UserId == userId && pref.ItemId == itemId && string.Equals(pref.Client, client));
|
.FirstOrDefault(pref => pref.UserId == userId && pref.ItemId == itemId && string.Equals(pref.Client, client));
|
||||||
|
|
||||||
if (prefs == null)
|
if (prefs == null)
|
||||||
{
|
{
|
||||||
prefs = new ItemDisplayPreferences(userId, Guid.Empty, client);
|
prefs = new ItemDisplayPreferences(userId, Guid.Empty, client);
|
||||||
dbContext.ItemDisplayPreferences.Add(prefs);
|
_dbContext.ItemDisplayPreferences.Add(prefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return prefs;
|
return prefs;
|
||||||
|
@ -62,27 +60,15 @@ namespace Jellyfin.Server.Implementations.Users
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client)
|
public IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client)
|
||||||
{
|
{
|
||||||
using var dbContext = _dbProvider.CreateContext();
|
return _dbContext.ItemDisplayPreferences
|
||||||
|
|
||||||
return dbContext.ItemDisplayPreferences
|
|
||||||
.Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client))
|
.Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void SaveChanges(DisplayPreferences preferences)
|
public void SaveChanges()
|
||||||
{
|
{
|
||||||
using var dbContext = _dbProvider.CreateContext();
|
_dbContext.SaveChanges();
|
||||||
dbContext.Update(preferences);
|
|
||||||
dbContext.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void SaveChanges(ItemDisplayPreferences preferences)
|
|
||||||
{
|
|
||||||
using var dbContext = _dbProvider.CreateContext();
|
|
||||||
dbContext.Update(preferences);
|
|
||||||
dbContext.SaveChanges();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Emby.Drawing;
|
using Emby.Drawing;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
|
@ -15,6 +16,7 @@ using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -67,12 +69,8 @@ namespace Jellyfin.Server
|
||||||
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
|
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Set up scoping and use AddDbContextPool,
|
ServiceCollection.AddDbContextPool<JellyfinDb>(
|
||||||
// can't register as Transient since tracking transient in GC is funky
|
options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
|
||||||
// serviceCollection.AddDbContext<JellyfinDb>(
|
|
||||||
// options => options
|
|
||||||
// .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
|
|
||||||
// ServiceLifetime.Transient);
|
|
||||||
|
|
||||||
ServiceCollection.AddEventServices();
|
ServiceCollection.AddEventServices();
|
||||||
ServiceCollection.AddSingleton<IEventManager, EventManager>();
|
ServiceCollection.AddSingleton<IEventManager, EventManager>();
|
||||||
|
|
|
@ -39,12 +39,14 @@ namespace Jellyfin.Server.Extensions
|
||||||
c.DocumentTitle = "Jellyfin API";
|
c.DocumentTitle = "Jellyfin API";
|
||||||
c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
|
c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
|
||||||
c.RoutePrefix = $"{baseUrl}api-docs/swagger";
|
c.RoutePrefix = $"{baseUrl}api-docs/swagger";
|
||||||
|
c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css");
|
||||||
})
|
})
|
||||||
.UseReDoc(c =>
|
.UseReDoc(c =>
|
||||||
{
|
{
|
||||||
c.DocumentTitle = "Jellyfin API";
|
c.DocumentTitle = "Jellyfin API";
|
||||||
c.SpecUrl($"/{baseUrl}api-docs/openapi.json");
|
c.SpecUrl($"/{baseUrl}api-docs/openapi.json");
|
||||||
c.RoutePrefix = $"{baseUrl}api-docs/redoc";
|
c.RoutePrefix = $"{baseUrl}api-docs/redoc";
|
||||||
|
c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,4 +64,13 @@
|
||||||
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
|
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="wwwroot\api-docs\swagger\custom.css">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="wwwroot\api-docs\redoc\custom.css">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -112,6 +112,7 @@ namespace Jellyfin.Server
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.UseStaticFiles();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseJellyfinApiSwagger(_serverConfigurationManager);
|
app.UseJellyfinApiSwagger(_serverConfigurationManager);
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
0
Jellyfin.Server/wwwroot/api-docs/redoc/custom.css
Normal file
0
Jellyfin.Server/wwwroot/api-docs/redoc/custom.css
Normal file
0
Jellyfin.Server/wwwroot/api-docs/swagger/custom.css
Normal file
0
Jellyfin.Server/wwwroot/api-docs/swagger/custom.css
Normal file
|
@ -35,15 +35,8 @@ namespace MediaBrowser.Controller
|
||||||
IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client);
|
IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves changes to the provided display preferences.
|
/// Saves changes made to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="preferences">The display preferences to save.</param>
|
void SaveChanges();
|
||||||
void SaveChanges(DisplayPreferences preferences);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Saves changes to the provided item display preferences.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="preferences">The item display preferences to save.</param>
|
|
||||||
void SaveChanges(ItemDisplayPreferences preferences);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
|
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
|
||||||
public bool? HasImage { get; set; }
|
public bool? HasImage { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is favorite.
|
/// Gets or sets a value indicating whether this instance is favorite.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -2090,6 +2090,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
|
|
||||||
|
// If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices
|
||||||
|
var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30;
|
||||||
|
|
||||||
// When the input may or may not be hardware VAAPI decodable
|
// When the input may or may not be hardware VAAPI decodable
|
||||||
if (isVaapiH264Encoder)
|
if (isVaapiH264Encoder)
|
||||||
{
|
{
|
||||||
|
@ -2136,35 +2139,38 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
if (isVaapiH264Encoder)
|
if (isVaapiH264Encoder)
|
||||||
{
|
{
|
||||||
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
|
filters.Add(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"deinterlace_vaapi=rate={0}",
|
||||||
|
doubleRateDeinterlace ? "field" : "frame"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add software deinterlace filter before scaling filter
|
// Add software deinterlace filter before scaling filter
|
||||||
if (state.DeInterlace("h264", true)
|
if ((state.DeInterlace("h264", true)
|
||||||
|| state.DeInterlace("avc", true)
|
|| state.DeInterlace("avc", true)
|
||||||
|| state.DeInterlace("h265", true)
|
|| state.DeInterlace("h265", true)
|
||||||
|| state.DeInterlace("hevc", true))
|
|| state.DeInterlace("hevc", true))
|
||||||
|
&& !isVaapiH264Encoder
|
||||||
|
&& !isQsvH264Encoder
|
||||||
|
&& !isNvdecH264Decoder)
|
||||||
{
|
{
|
||||||
string deintParam;
|
if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase))
|
||||||
var inputFramerate = videoStream?.RealFrameRate;
|
|
||||||
|
|
||||||
// If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
|
|
||||||
if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
|
|
||||||
{
|
{
|
||||||
deintParam = "yadif=1:-1:0";
|
filters.Add(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"bwdif={0}:-1:0",
|
||||||
|
doubleRateDeinterlace ? "1" : "0"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
deintParam = "yadif=0:-1:0";
|
filters.Add(
|
||||||
}
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
if (!string.IsNullOrEmpty(deintParam))
|
"yadif={0}:-1:0",
|
||||||
{
|
doubleRateDeinterlace ? "1" : "0"));
|
||||||
if (!isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder)
|
|
||||||
{
|
|
||||||
filters.Add(deintParam);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2397,6 +2403,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
if (state.DeInterlace("h264", true))
|
if (state.DeInterlace("h264", true))
|
||||||
{
|
{
|
||||||
inputModifier += " -deint 1";
|
inputModifier += " -deint 1";
|
||||||
|
|
||||||
|
if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.RealFrameRate ?? 60) > 30)
|
||||||
|
{
|
||||||
|
inputModifier += " -drop_second_field 1";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration
|
||||||
|
|
||||||
public double DownMixAudioBoost { get; set; }
|
public double DownMixAudioBoost { get; set; }
|
||||||
|
|
||||||
|
public int MaxMuxingQueueSize { get; set; }
|
||||||
|
|
||||||
public bool EnableThrottling { get; set; }
|
public bool EnableThrottling { get; set; }
|
||||||
|
|
||||||
public int ThrottleDelaySeconds { get; set; }
|
public int ThrottleDelaySeconds { get; set; }
|
||||||
|
@ -35,6 +37,8 @@ namespace MediaBrowser.Model.Configuration
|
||||||
|
|
||||||
public string EncoderPreset { get; set; }
|
public string EncoderPreset { get; set; }
|
||||||
|
|
||||||
|
public bool DeinterlaceDoubleRate { get; set; }
|
||||||
|
|
||||||
public string DeinterlaceMethod { get; set; }
|
public string DeinterlaceMethod { get; set; }
|
||||||
|
|
||||||
public bool EnableDecodingColorDepth10Hevc { get; set; }
|
public bool EnableDecodingColorDepth10Hevc { get; set; }
|
||||||
|
@ -50,6 +54,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public EncodingOptions()
|
public EncodingOptions()
|
||||||
{
|
{
|
||||||
DownMixAudioBoost = 2;
|
DownMixAudioBoost = 2;
|
||||||
|
MaxMuxingQueueSize = 2048;
|
||||||
EnableThrottling = false;
|
EnableThrottling = false;
|
||||||
ThrottleDelaySeconds = 180;
|
ThrottleDelaySeconds = 180;
|
||||||
EncodingThreadCount = -1;
|
EncodingThreadCount = -1;
|
||||||
|
@ -57,6 +62,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
VaapiDevice = "/dev/dri/renderD128";
|
VaapiDevice = "/dev/dri/renderD128";
|
||||||
H264Crf = 23;
|
H264Crf = 23;
|
||||||
H265Crf = 28;
|
H265Crf = 28;
|
||||||
|
DeinterlaceDoubleRate = false;
|
||||||
DeinterlaceMethod = "yadif";
|
DeinterlaceMethod = "yadif";
|
||||||
EnableDecodingColorDepth10Hevc = true;
|
EnableDecodingColorDepth10Hevc = true;
|
||||||
EnableDecodingColorDepth10Vp9 = true;
|
EnableDecodingColorDepth10Vp9 = true;
|
||||||
|
|
|
@ -56,7 +56,7 @@ namespace MediaBrowser.Model.IO
|
||||||
public DateTime CreationTimeUtc { get; set; }
|
public DateTime CreationTimeUtc { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance is directory.
|
/// Gets or sets a value indicating whether this instance is directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance is directory; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is directory; otherwise, <c>false</c>.</value>
|
||||||
public bool IsDirectory { get; set; }
|
public bool IsDirectory { get; set; }
|
||||||
|
|
|
@ -201,9 +201,9 @@ namespace MediaBrowser.Model.IO
|
||||||
IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false);
|
IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false);
|
||||||
|
|
||||||
void SetHidden(string path, bool isHidden);
|
void SetHidden(string path, bool isHidden);
|
||||||
void SetReadOnly(string path, bool readOnly);
|
|
||||||
void SetAttributes(string path, bool isHidden, bool readOnly);
|
void SetAttributes(string path, bool isHidden, bool readOnly);
|
||||||
|
|
||||||
List<FileSystemMetadata> GetDrives();
|
List<FileSystemMetadata> GetDrives();
|
||||||
void SetExecutable(string path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ namespace MediaBrowser.Model.IO
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shortcutPath">The shortcut path.</param>
|
/// <param name="shortcutPath">The shortcut path.</param>
|
||||||
/// <param name="targetPath">The target path.</param>
|
/// <param name="targetPath">The target path.</param>
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
void Create(string shortcutPath, string targetPath);
|
void Create(string shortcutPath, string targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,6 @@ namespace MediaBrowser.Model.IO
|
||||||
|
|
||||||
Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken);
|
Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<int> CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken);
|
Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken);
|
Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken);
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace MediaBrowser.Model.IO
|
||||||
void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles);
|
void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles);
|
||||||
|
|
||||||
void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles);
|
void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles);
|
||||||
|
|
||||||
void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName);
|
void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -31,9 +31,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ProviderName => TmdbUtils.ProviderName;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Name => ProviderName;
|
public string Name => ProviderName;
|
||||||
|
|
||||||
public static string ProviderName => TmdbUtils.ProviderName;
|
/// <inheritdoc />
|
||||||
|
public int Order => 0;
|
||||||
|
|
||||||
public bool Supports(BaseItem item)
|
public bool Supports(BaseItem item)
|
||||||
{
|
{
|
||||||
|
@ -125,8 +129,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||||
return profile.Iso_639_1?.ToString();
|
return profile.Iso_639_1?.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Order => 0;
|
|
||||||
|
|
||||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
|
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||||
{
|
{
|
||||||
const string DataFileName = "info.json";
|
const string DataFileName = "info.json";
|
||||||
|
|
||||||
internal static TmdbPersonProvider Current { get; private set; }
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
@ -55,6 +55,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static TmdbPersonProvider Current { get; private set; }
|
||||||
|
|
||||||
public string Name => TmdbUtils.ProviderName;
|
public string Name => TmdbUtils.ProviderName;
|
||||||
|
|
||||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
|
||||||
|
@ -95,7 +97,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||||
return new List<RemoteSearchResult>();
|
return new List<RemoteSearchResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), TmdbUtils.ApiKey);
|
var url = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}",
|
||||||
|
WebUtility.UrlEncode(searchInfo.Name),
|
||||||
|
TmdbUtils.ApiKey);
|
||||||
|
|
||||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
|
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
foreach (var header in TmdbUtils.AcceptHeaders)
|
foreach (var header in TmdbUtils.AcceptHeaders)
|
||||||
|
@ -200,8 +206,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the TMDB id.
|
/// Gets the TMDB id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -226,7 +230,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", TmdbUtils.ApiKey, id);
|
var url = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
TmdbUtils.BaseTmdbApiUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids",
|
||||||
|
TmdbUtils.ApiKey,
|
||||||
|
id);
|
||||||
|
|
||||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
|
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
foreach (var header in TmdbUtils.AcceptHeaders)
|
foreach (var header in TmdbUtils.AcceptHeaders)
|
||||||
|
|
2
debian/rules
vendored
2
debian/rules
vendored
|
@ -40,7 +40,7 @@ override_dh_clistrip:
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
|
dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
|
||||||
"-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
|
"-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server
|
||||||
|
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
|
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
|
||||||
|
|
|
@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
|
||||||
|
|
|
@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
|
||||||
|
|
|
@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
|
||||||
|
|
|
@ -16,7 +16,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
|
||||||
tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
|
||||||
tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
|
||||||
tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ fi
|
||||||
output_dir="dist/jellyfin-server_${version}"
|
output_dir="dist/jellyfin-server_${version}"
|
||||||
|
|
||||||
# Build binary
|
# Build binary
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
|
||||||
|
|
||||||
# Prepare addins
|
# Prepare addins
|
||||||
addin_build_dir="$( mktemp -d )"
|
addin_build_dir="$( mktemp -d )"
|
||||||
|
|
|
@ -54,7 +54,7 @@ The Jellyfin media server backend.
|
||||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
||||||
dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
|
dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
|
||||||
"-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
|
"-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server
|
||||||
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
|
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
|
||||||
%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
|
%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
|
||||||
%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
|
%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
|
||||||
|
|
|
@ -40,7 +40,7 @@ function Build-JellyFin {
|
||||||
Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
|
Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
|
||||||
Write-Verbose "InstallLocation: $ResolvedInstallLocation"
|
Write-Verbose "InstallLocation: $ResolvedInstallLocation"
|
||||||
Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
|
Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
|
||||||
dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server
|
dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=true -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
function Install-FFMPEG {
|
function Install-FFMPEG {
|
||||||
|
|
Loading…
Reference in a new issue