Merge remote-tracking branch 'upstream/master' into http-client-migrate

This commit is contained in:
crobibero 2020-09-04 08:16:49 -06:00
commit 2a8653b309
69 changed files with 586 additions and 460 deletions

View file

@ -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)

View file

@ -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:DebugSymbols=false;DebugType=none"
FROM debian:buster-slim FROM debian:buster-slim

View file

@ -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:DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM multiarch/qemu-user-static:x86_64-arm as qemu

View file

@ -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: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

View file

@ -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());
} }

View file

@ -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);
} }
} }

View file

@ -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);
} }

View file

@ -280,6 +280,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)
@ -309,16 +313,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.
@ -1392,7 +1396,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;
} }
} }

View file

@ -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);
} }
} }

View file

@ -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);
} }
} }

View file

@ -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;

View file

@ -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);
} }
} }
} }

View file

@ -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

View file

@ -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;

View file

@ -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);
@ -80,7 +80,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var durationToken = new CancellationTokenSource(duration); var durationToken = new CancellationTokenSource(duration);
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
await _streamHelper.CopyUntilCancelled(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), output, 81920, cancellationToken).ConfigureAwait(false); await _streamHelper.CopyUntilCancelled(
await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
output,
81920,
cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Recording completed to file {0}", targetFile); _logger.LogInformation("Recording completed to file {0}", targetFile);
} }

View file

@ -605,11 +605,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();
@ -809,11 +804,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))
@ -1016,16 +1006,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))
@ -1655,7 +1635,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, _httpClientFactory, _streamHelper); return new DirectRecorder(_logger, _httpClientFactory, _streamHelper);

View file

@ -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)
{ {

View file

@ -26,14 +26,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 IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
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,
@ -63,7 +63,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);
} }
@ -352,13 +352,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;
} }
@ -557,7 +558,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();

View file

@ -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}",

View file

@ -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"
} }

View file

@ -18,7 +18,7 @@
"MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன", "MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன",
"MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது", "MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது",
"MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது", "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
"Inherit": "மரபரிமையாகப் பெறு", "Inherit": "மரபரிமையாகப் பெறு",
"HeaderRecordingGroups": "பதிவு குழுக்கள்", "HeaderRecordingGroups": "பதிவு குழுக்கள்",
"HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்", "HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
"Folders": "கோப்புறைகள்", "Folders": "கோப்புறைகள்",
@ -31,7 +31,7 @@
"TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு", "TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
"TaskRefreshChannels": "சேனல்களை புதுப்பி", "TaskRefreshChannels": "சேனல்களை புதுப்பி",
"TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி", "TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி",
"TaskRefreshLibrary": "மீடியா நூலகத்தை ஆராய்", "TaskRefreshLibrary": "ஊடக நூலகத்தை ஆராய்",
"TasksChannelsCategory": "இணைய சேனல்கள்", "TasksChannelsCategory": "இணைய சேனல்கள்",
"TasksApplicationCategory": "செயலி", "TasksApplicationCategory": "செயலி",
"TasksLibraryCategory": "நூலகம்", "TasksLibraryCategory": "நூலகம்",
@ -46,7 +46,7 @@
"Sync": "ஒத்திசைவு", "Sync": "ஒத்திசைவு",
"StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.", "StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
"Songs": "பாடல்கள்", "Songs": "பாடல்கள்",
"Shows": "தொடர்கள்", "Shows": "நிகழ்ச்சிகள்",
"ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்", "ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
"ScheduledTaskStartedWithName": "{0} துவங்கியது", "ScheduledTaskStartedWithName": "{0} துவங்கியது",
"ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது", "ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது",
@ -67,20 +67,20 @@
"NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது", "NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது",
"NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது", "NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது",
"NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்", "NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்",
"NameSeasonUnknown": "பருவம் அறியப்படாதவை", "NameSeasonUnknown": "அறியப்படாத பருவம்",
"NameSeasonNumber": "பருவம் {0}", "NameSeasonNumber": "பருவம் {0}",
"NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது", "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது",
"MusicVideos": "இசைப்படங்கள்", "MusicVideos": "இசைப்படங்கள்",
"Music": "இசை", "Music": "இசை",
"Movies": "திரைப்படங்கள்", "Movies": "திரைப்படங்கள்",
"Latest": "புதிய", "Latest": "புதியவை",
"LabelRunningTimeValue": "ஓடும் நேரம்: {0}", "LabelRunningTimeValue": "ஓடும் நேரம்: {0}",
"LabelIpAddressValue": "ஐபி முகவரி: {0}", "LabelIpAddressValue": "ஐபி முகவரி: {0}",
"ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது", "ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது",
"ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது", "ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது",
"HeaderNextUp": "அடுத்ததாக", "HeaderNextUp": "அடுத்தத",
"HeaderLiveTV": "நேரடித் தொலைக்காட்சி", "HeaderLiveTV": "நேரடித் தொலைக்காட்சி",
"HeaderFavoriteSongs": "பிடித்த பாடடுகள்", "HeaderFavoriteSongs": "பிடித்த பாட்கள்",
"HeaderFavoriteShows": "பிடித்த தொடர்கள்", "HeaderFavoriteShows": "பிடித்த தொடர்கள்",
"HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்", "HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்",
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்", "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
@ -93,25 +93,25 @@
"Channels": "சேனல்கள்", "Channels": "சேனல்கள்",
"Books": "புத்தகங்கள்", "Books": "புத்தகங்கள்",
"AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது", "AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
"Artists": "கலைஞர்", "Artists": "கலைஞர்கள்",
"Application": "செயலி", "Application": "செயலி",
"Albums": "ஆல்பங்கள்", "Albums": "ஆல்பங்கள்",
"NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.", "NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
"MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது", "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0} புதுப்பிக்கப்பட்டது",
"TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.", "TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
"UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது", "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
"SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன", "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
"TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.", "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
"TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.", "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
"TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.", "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
"TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.", "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.",
"TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.", "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
"TaskCleanLogs": "பதிவு அடைவ சுத்தம் செய்யுங்கள்", "TaskCleanLogs": "பதிவு அடைவ சுத்தம் செய்யுங்கள்",
"TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.", "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் ஊடக நூலகத்தை ஆராய்ந்து மீத்தரவை புதுப்பிக்கும்.",
"TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.", "TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
"ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது", "ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
"UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்", "UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
"HomeVideos": "முகப்பு வீடியோக்கள்", "HomeVideos": "முகப்பு வீடியோக்கள்",
"UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது", "UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது",
"UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது" "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
} }

View file

@ -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;
} }
} }

View file

@ -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;
} }
} }

View file

@ -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>

View file

@ -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>

View file

@ -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);
}
} }
} }
} }

View file

@ -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);
}
} }
} }
} }

View file

@ -1,4 +1,5 @@
using System.Net.Mime; using System.Net.Mime;
using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api namespace Jellyfin.Api
@ -8,7 +9,10 @@ namespace Jellyfin.Api
/// </summary> /// </summary>
[ApiController] [ApiController]
[Route("[controller]")] [Route("[controller]")]
[Produces(MediaTypeNames.Application.Json)] [Produces(
MediaTypeNames.Application.Json,
JsonDefaults.CamelCaseMediaType,
JsonDefaults.PascalCaseMediaType)]
public class BaseJellyfinApiController : ControllerBase public class BaseJellyfinApiController : ControllerBase
{ {
} }

View file

@ -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();
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Mime;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna; using Emby.Dlna;
using Emby.Dlna.Main; using Emby.Dlna.Main;
@ -17,8 +18,6 @@ namespace Jellyfin.Api.Controllers
[Route("Dlna")] [Route("Dlna")]
public class DlnaServerController : BaseJellyfinApiController public class DlnaServerController : BaseJellyfinApiController
{ {
private const string XMLContentType = "text/xml; charset=UTF-8";
private readonly IDlnaManager _dlnaManager; private readonly IDlnaManager _dlnaManager;
private readonly IContentDirectory _contentDirectory; private readonly IContentDirectory _contentDirectory;
private readonly IConnectionManager _connectionManager; private readonly IConnectionManager _connectionManager;
@ -44,7 +43,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the description xml.</returns> /// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
[HttpGet("{serverId}/description")] [HttpGet("{serverId}/description")]
[HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")] [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
[Produces(XMLContentType)] [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetDescriptionXml([FromRoute] string serverId) public ActionResult GetDescriptionXml([FromRoute] string serverId)
{ {
@ -61,8 +60,9 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Dlna content directory returned.</response> /// <response code="200">Dlna content directory returned.</response>
/// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns> /// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
[HttpGet("{serverId}/ContentDirectory")] [HttpGet("{serverId}/ContentDirectory")]
[HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")] [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
[Produces(XMLContentType)] [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
[Produces(MediaTypeNames.Text.Xml)]
[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)
@ -76,8 +76,9 @@ 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}/MediaReceiverRegistrar")] [HttpGet("{serverId}/MediaReceiverRegistrar")]
[HttpGet("{serverId}/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")] [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
[Produces(XMLContentType)] [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
[Produces(MediaTypeNames.Text.Xml)]
[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 GetMediaReceiverRegistrar([FromRoute] string serverId) public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId)
@ -91,8 +92,9 @@ 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")]
[Produces(XMLContentType)] [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
[Produces(MediaTypeNames.Text.Xml)]
[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 GetConnectionManager([FromRoute] string serverId) public ActionResult GetConnectionManager([FromRoute] string serverId)

View file

@ -1354,15 +1354,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,

View file

@ -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();
} }
} }
} }

View file

@ -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>();

View file

@ -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");
}); });
} }

View file

@ -152,6 +152,10 @@ namespace Jellyfin.Server.Extensions
.AddMvc(opts => .AddMvc(opts =>
{ {
opts.UseGeneralRoutePrefix(baseUrl); opts.UseGeneralRoutePrefix(baseUrl);
// Allow requester to change between camelCase and PascalCase
opts.RespectBrowserAcceptHeader = true;
opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter()); opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter());
opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter()); opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());

View file

@ -15,7 +15,7 @@ namespace Jellyfin.Server.Formatters
public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions()) public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions())
{ {
SupportedMediaTypes.Clear(); SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\"")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType));
} }
} }
} }

View file

@ -1,3 +1,4 @@
using System.Net.Mime;
using MediaBrowser.Common.Json; using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -16,8 +17,8 @@ namespace Jellyfin.Server.Formatters
{ {
SupportedMediaTypes.Clear(); SupportedMediaTypes.Clear();
// Add application/json for default formatter // Add application/json for default formatter
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json));
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"PascalCase\"")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.PascalCaseMediaType));
} }
} }
} }

View file

@ -16,8 +16,9 @@ namespace Jellyfin.Server.Formatters
/// </summary> /// </summary>
public XmlOutputFormatter() public XmlOutputFormatter()
{ {
SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeNames.Text.Xml); SupportedMediaTypes.Add(MediaTypeNames.Text.Xml);
SupportedMediaTypes.Add("text/xml;charset=UTF-8");
SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode); SupportedEncodings.Add(Encoding.Unicode);
} }

View file

@ -1,36 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Server.Implementations;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace Jellyfin.Server.HealthChecks
{
/// <summary>
/// Checks connectivity to the database.
/// </summary>
public class JellyfinDbHealthCheck : IHealthCheck
{
private readonly JellyfinDbProvider _dbProvider;
/// <summary>
/// Initializes a new instance of the <see cref="JellyfinDbHealthCheck"/> class.
/// </summary>
/// <param name="dbProvider">The jellyfin db provider.</param>
public JellyfinDbHealthCheck(JellyfinDbProvider dbProvider)
{
_dbProvider = dbProvider;
}
/// <inheritdoc />
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
await using var jellyfinDb = _dbProvider.CreateContext();
if (await jellyfinDb.Database.CanConnectAsync(cancellationToken).ConfigureAwait(false))
{
return HealthCheckResult.Healthy("Database connection successful.");
}
return HealthCheckResult.Unhealthy("Unable to connect to the database.");
}
}
}

View file

@ -44,6 +44,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.7" />
<PackageReference Include="prometheus-net" Version="3.6.0" /> <PackageReference Include="prometheus-net" Version="3.6.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" /> <PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" /> <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
@ -64,4 +65,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>

View file

@ -3,7 +3,7 @@ using System.ComponentModel;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Jellyfin.Api.TypeConverters; using Jellyfin.Api.TypeConverters;
using Jellyfin.Server.Extensions; using Jellyfin.Server.Extensions;
using Jellyfin.Server.HealthChecks; using Jellyfin.Server.Implementations;
using Jellyfin.Server.Middleware; using Jellyfin.Server.Middleware;
using Jellyfin.Server.Models; using Jellyfin.Server.Models;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -79,7 +79,7 @@ namespace Jellyfin.Server
.ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler()); .ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
services.AddHealthChecks() services.AddHealthChecks()
.AddCheck<JellyfinDbHealthCheck>("JellyfinDb"); .AddDbContextCheck<JellyfinDb>();
} }
/// <summary> /// <summary>
@ -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();

View file

@ -9,6 +9,16 @@ namespace MediaBrowser.Common.Json
/// </summary> /// </summary>
public static class JsonDefaults public static class JsonDefaults
{ {
/// <summary>
/// Pascal case json profile media type.
/// </summary>
public const string PascalCaseMediaType = "application/json; profile=\"PascalCase\"";
/// <summary>
/// Camel case json profile media type.
/// </summary>
public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\"";
/// <summary> /// <summary>
/// Gets the default <see cref="JsonSerializerOptions" /> options. /// Gets the default <see cref="JsonSerializerOptions" /> options.
/// </summary> /// </summary>

View file

@ -60,8 +60,6 @@ namespace MediaBrowser.Controller.Entities
protected BaseItem() protected BaseItem()
{ {
ThemeSongIds = Array.Empty<Guid>();
ThemeVideoIds = Array.Empty<Guid>();
Tags = Array.Empty<string>(); Tags = Array.Empty<string>();
Genres = Array.Empty<string>(); Genres = Array.Empty<string>();
Studios = Array.Empty<string>(); Studios = Array.Empty<string>();
@ -100,12 +98,52 @@ namespace MediaBrowser.Controller.Entities
}; };
[JsonIgnore] [JsonIgnore]
public Guid[] ThemeSongIds { get; set; } public Guid[] ThemeSongIds
{
get
{
if (_themeSongIds == null)
{
_themeSongIds = GetExtras()
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong)
.Select(song => song.Id)
.ToArray();
}
return _themeSongIds;
}
private set
{
_themeSongIds = value;
}
}
[JsonIgnore] [JsonIgnore]
public Guid[] ThemeVideoIds { get; set; } public Guid[] ThemeVideoIds
{
get
{
if (_themeVideoIds == null)
{
_themeVideoIds = GetExtras()
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo)
.Select(song => song.Id)
.ToArray();
}
return _themeVideoIds;
}
private set
{
_themeVideoIds = value;
}
}
[JsonIgnore] [JsonIgnore]
public string PreferredMetadataCountryCode { get; set; } public string PreferredMetadataCountryCode { get; set; }
[JsonIgnore] [JsonIgnore]
public string PreferredMetadataLanguage { get; set; } public string PreferredMetadataLanguage { get; set; }
@ -635,6 +673,9 @@ namespace MediaBrowser.Controller.Entities
} }
private string _sortName; private string _sortName;
private Guid[] _themeSongIds;
private Guid[] _themeVideoIds;
/// <summary> /// <summary>
/// Gets the name of the sort. /// Gets the name of the sort.
/// </summary> /// </summary>
@ -1582,7 +1623,8 @@ namespace MediaBrowser.Controller.Entities
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
item.ThemeVideoIds = newThemeVideoIds; // They are expected to be sorted by SortName
item.ThemeVideoIds = newThemeVideos.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
return themeVideosChanged; return themeVideosChanged;
} }
@ -1619,7 +1661,8 @@ namespace MediaBrowser.Controller.Entities
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
item.ThemeSongIds = newThemeSongIds; // They are expected to be sorted by SortName
item.ThemeSongIds = newThemeSongs.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
return themeSongsChanged; return themeSongsChanged;
} }
@ -2910,12 +2953,12 @@ namespace MediaBrowser.Controller.Entities
public IEnumerable<BaseItem> GetThemeSongs() public IEnumerable<BaseItem> GetThemeSongs()
{ {
return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeSong)).OrderBy(i => i.SortName); return ThemeSongIds.Select(LibraryManager.GetItemById);
} }
public IEnumerable<BaseItem> GetThemeVideos() public IEnumerable<BaseItem> GetThemeVideos()
{ {
return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeVideo)).OrderBy(i => i.SortName); return ThemeVideoIds.Select(LibraryManager.GetItemById);
} }
/// <summary> /// <summary>

View 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);
} }
} }

View file

@ -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>

View file

@ -456,6 +456,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isNvencHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
@ -515,6 +516,24 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
} }
if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
{
var isColorDepth10 = IsColorDepth10(state);
if (isNvencHevcDecoder && isColorDepth10
&& _mediaEncoder.SupportsHwaccel("opencl")
&& encodingOptions.EnableTonemapping
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange)
&& state.VideoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
{
arg.Append("-init_hw_device opencl=ocl:")
.Append(encodingOptions.OpenclDevice)
.Append(' ')
.Append("-filter_hw_device ocl ");
}
}
if (state.IsVideoRequest if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) && string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
{ {
@ -1003,11 +1022,33 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
{ {
param = "-pix_fmt yuv420p " + param; param = "-pix_fmt yuv420p " + param;
} }
if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
var videoStream = state.VideoStream;
var isColorDepth10 = IsColorDepth10(state);
if (videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1
&& isColorDepth10
&& _mediaEncoder.SupportsHwaccel("opencl")
&& encodingOptions.EnableTonemapping
&& !string.IsNullOrEmpty(videoStream.VideoRange)
&& videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt nv12 " + param;
}
else
{
param = "-pix_fmt yuv420p " + param;
}
}
if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
{ {
param = "-pix_fmt nv21 " + param; param = "-pix_fmt nv21 " + param;
@ -1611,64 +1652,45 @@ namespace MediaBrowser.Controller.MediaEncoding
var outputSizeParam = ReadOnlySpan<char>.Empty; var outputSizeParam = ReadOnlySpan<char>.Empty;
var request = state.BaseRequest; var request = state.BaseRequest;
// Add resolution params, if specified
if (request.Width.HasValue
|| request.Height.HasValue
|| request.MaxHeight.HasValue
|| request.MaxWidth.HasValue)
{
outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
// All possible beginning of video filters
// Don't break the order
string[] beginOfOutputSizeParam = new[]
{
// for tonemap_opencl
"hwupload,tonemap_opencl",
// hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux) // hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux)
var index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase); "hwupload=extra_hw_frames",
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{
// vpp_qsv // vpp_qsv
index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase); "vpp",
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{
// hwdownload,format=p010le (hardware decode + software encode for vaapi) // hwdownload,format=p010le (hardware decode + software encode for vaapi)
index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase); "hwdownload",
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{
// format=nv12|vaapi,hwupload,scale_vaapi // format=nv12|vaapi,hwupload,scale_vaapi
index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); "format",
if (index != -1)
{ // bwdif,scale=expr
outputSizeParam = outputSizeParam.Slice(index); "bwdif",
}
else
{
// yadif,scale=expr // yadif,scale=expr
index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase); "yadif",
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{
// scale=expr // scale=expr
index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); "scale"
};
var index = -1;
foreach (var param in beginOfOutputSizeParam)
{
index = outputSizeParam.IndexOf(param, StringComparison.OrdinalIgnoreCase);
if (index != -1) if (index != -1)
{ {
outputSizeParam = outputSizeParam.Slice(index); outputSizeParam = outputSizeParam.Slice(index);
} break;
}
}
}
}
} }
} }
@ -1747,9 +1769,9 @@ namespace MediaBrowser.Controller.MediaEncoding
*/ */
if (isLinux) if (isLinux)
{ {
retStr = !outputSizeParam.IsEmpty ? retStr = !outputSizeParam.IsEmpty
" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" : ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""
" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
} }
} }
@ -2084,12 +2106,61 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isColorDepth10 = IsColorDepth10(state);
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;
// Currently only with the use of NVENC decoder can we get a decent performance.
// Currently only the HEVC/H265 format is supported.
// NVIDIA Pascal and Turing or higher are recommended.
if (isNvdecHevcDecoder && isColorDepth10
&& _mediaEncoder.SupportsHwaccel("opencl")
&& options.EnableTonemapping
&& !string.IsNullOrEmpty(videoStream.VideoRange)
&& videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
{
var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
if (options.TonemappingParam != 0)
{
parameters += ":param={4}";
}
if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
{
parameters += ":range={5}";
}
// Upload the HDR10 or HLG data to the OpenCL device,
// use tonemap_opencl filter for tone mapping,
// and then download the SDR data to memory.
filters.Add("hwupload");
filters.Add(
string.Format(
CultureInfo.InvariantCulture,
parameters,
options.TonemappingAlgorithm,
options.TonemappingDesat,
options.TonemappingThreshold,
options.TonemappingPeak,
options.TonemappingParam,
options.TonemappingRange));
filters.Add("hwdownload");
if (hasGraphicalSubs || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)
|| string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12");
}
}
// 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)
{ {
@ -2107,7 +2178,6 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder) else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
{ {
var codec = videoStream.Codec.ToLowerInvariant(); var codec = videoStream.Codec.ToLowerInvariant();
var isColorDepth10 = IsColorDepth10(state);
// Assert 10-bit hardware VAAPI decodable // Assert 10-bit hardware VAAPI decodable
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
@ -2136,35 +2206,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 +2470,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";
}
} }
} }
} }

View file

@ -282,6 +282,20 @@ namespace MediaBrowser.MediaEncoding.Probing
[JsonPropertyName("disposition")] [JsonPropertyName("disposition")]
public IReadOnlyDictionary<string, int> Disposition { get; set; } public IReadOnlyDictionary<string, int> Disposition { get; set; }
/// <summary>
/// Gets or sets the color range.
/// </summary>
/// <value>The color range.</value>
[JsonPropertyName("color_range")]
public string ColorRange { get; set; }
/// <summary>
/// Gets or sets the color space.
/// </summary>
/// <value>The color space.</value>
[JsonPropertyName("color_space")]
public string ColorSpace { get; set; }
/// <summary> /// <summary>
/// Gets or sets the color transfer. /// Gets or sets the color transfer.
/// </summary> /// </summary>

View file

@ -714,6 +714,16 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.RefFrames = streamInfo.Refs; stream.RefFrames = streamInfo.Refs;
} }
if (!string.IsNullOrEmpty(streamInfo.ColorRange))
{
stream.ColorRange = streamInfo.ColorRange;
}
if (!string.IsNullOrEmpty(streamInfo.ColorSpace))
{
stream.ColorSpace = streamInfo.ColorSpace;
}
if (!string.IsNullOrEmpty(streamInfo.ColorTransfer)) if (!string.IsNullOrEmpty(streamInfo.ColorTransfer))
{ {
stream.ColorTransfer = streamInfo.ColorTransfer; stream.ColorTransfer = streamInfo.ColorTransfer;

View file

@ -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; }
@ -29,12 +31,30 @@ namespace MediaBrowser.Model.Configuration
public string VaapiDevice { get; set; } public string VaapiDevice { get; set; }
public string OpenclDevice { get; set; }
public bool EnableTonemapping { get; set; }
public string TonemappingAlgorithm { get; set; }
public string TonemappingRange { get; set; }
public double TonemappingDesat { get; set; }
public double TonemappingThreshold { get; set; }
public double TonemappingPeak { get; set; }
public double TonemappingParam { get; set; }
public int H264Crf { get; set; } public int H264Crf { get; set; }
public int H265Crf { get; set; } public int H265Crf { get; set; }
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,13 +70,26 @@ 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;
// This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything // This is a DRM device that is almost guaranteed to be there on every intel platform,
// plus it's the default one in ffmpeg if you don't specify anything
VaapiDevice = "/dev/dri/renderD128"; VaapiDevice = "/dev/dri/renderD128";
// This is the OpenCL device that is used for tonemapping.
// The left side of the dot is the platform number, and the right side is the device number on the platform.
OpenclDevice = "0.0";
EnableTonemapping = false;
TonemappingAlgorithm = "reinhard";
TonemappingRange = "auto";
TonemappingDesat = 0;
TonemappingThreshold = 0.8;
TonemappingPeak = 0;
TonemappingParam = 0;
H264Crf = 23; H264Crf = 23;
H265Crf = 28; H265Crf = 28;
DeinterlaceDoubleRate = false;
DeinterlaceMethod = "yadif"; DeinterlaceMethod = "yadif";
EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true; EnableDecodingColorDepth10Vp9 = true;

View file

@ -35,6 +35,18 @@ namespace MediaBrowser.Model.Entities
/// <value>The language.</value> /// <value>The language.</value>
public string Language { get; set; } public string Language { get; set; }
/// <summary>
/// Gets or sets the color range.
/// </summary>
/// <value>The color range.</value>
public string ColorRange { get; set; }
/// <summary>
/// Gets or sets the color space.
/// </summary>
/// <value>The color space.</value>
public string ColorSpace { get; set; }
/// <summary> /// <summary>
/// Gets or sets the color transfer. /// Gets or sets the color transfer.
/// </summary> /// </summary>
@ -47,12 +59,6 @@ namespace MediaBrowser.Model.Entities
/// <value>The color primaries.</value> /// <value>The color primaries.</value>
public string ColorPrimaries { get; set; } public string ColorPrimaries { get; set; }
/// <summary>
/// Gets or sets the color space.
/// </summary>
/// <value>The color space.</value>
public string ColorSpace { get; set; }
/// <summary> /// <summary>
/// Gets or sets the comment. /// Gets or sets the comment.
/// </summary> /// </summary>

View file

@ -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; }

View file

@ -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);
} }
} }

View file

@ -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);
} }
} }

View file

@ -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);

View file

@ -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>

View file

@ -32,9 +32,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)
{ {
@ -126,8 +130,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(NamedClient.Default).GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

View file

@ -33,7 +33,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;
@ -56,6 +56,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)
@ -96,7 +98,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)
@ -201,8 +207,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>
@ -227,7 +231,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
View file

@ -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: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

View file

@ -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:DebugSymbols=false;DebugType=none"

View file

@ -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:DebugSymbols=false;DebugType=none"

View file

@ -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:DebugSymbols=false;DebugType=none"

View file

@ -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: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}

View file

@ -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: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}

View file

@ -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: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}

View file

@ -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:DebugSymbols=false;DebugType=none;UseAppHost=true"
# Prepare addins # Prepare addins
addin_build_dir="$( mktemp -d )" addin_build_dir="$( mktemp -d )"

View file

@ -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: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

View file

@ -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 {