mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Merge remote-tracking branch 'upstream/master' into warn-259810
This commit is contained in:
commit
ec13412155
3
.gitignore
vendored
3
.gitignore
vendored
@ -278,3 +278,6 @@ web/
|
|||||||
web-src.*
|
web-src.*
|
||||||
MediaBrowser.WebDashboard/jellyfin-web
|
MediaBrowser.WebDashboard/jellyfin-web
|
||||||
apiclient/generated
|
apiclient/generated
|
||||||
|
|
||||||
|
# Omnisharp crash logs
|
||||||
|
mono_crash.*.json
|
||||||
|
@ -8,7 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||||||
&& npm ci --no-audit --unsafe-perm \
|
&& npm ci --no-audit --unsafe-perm \
|
||||||
&& mv dist /dist
|
&& mv dist /dist
|
||||||
|
|
||||||
FROM debian:buster-slim as app
|
FROM debian:bullseye-slim as app
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
ARG DEBIAN_FRONTEND="noninteractive"
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
|
@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||||||
&& mv dist /dist
|
&& mv dist /dist
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||||
FROM arm32v7/debian:buster-slim as app
|
FROM arm32v7/debian:bullseye-slim as app
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
ARG DEBIAN_FRONTEND="noninteractive"
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
|
@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||||||
&& mv dist /dist
|
&& mv dist /dist
|
||||||
|
|
||||||
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 as app
|
FROM arm64v8/debian:bullseye-slim as app
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
ARG DEBIAN_FRONTEND="noninteractive"
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
|
@ -1100,7 +1100,6 @@ namespace Emby.Server.Implementations
|
|||||||
ServerName = FriendlyName,
|
ServerName = FriendlyName,
|
||||||
LocalAddress = GetSmartApiUrl(source),
|
LocalAddress = GetSmartApiUrl(source),
|
||||||
SupportsLibraryMonitor = true,
|
SupportsLibraryMonitor = true,
|
||||||
EncoderLocation = _mediaEncoder.EncoderLocation,
|
|
||||||
SystemArchitecture = RuntimeInformation.OSArchitecture,
|
SystemArchitecture = RuntimeInformation.OSArchitecture,
|
||||||
PackageName = _startupOptions.PackageName
|
PackageName = _startupOptions.PackageName
|
||||||
};
|
};
|
||||||
|
@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
var libraryOptions = new LibraryOptions
|
var libraryOptions = new LibraryOptions
|
||||||
{
|
{
|
||||||
PathInfos = new[] { new MediaPathInfo { Path = path } },
|
PathInfos = new[] { new MediaPathInfo(path) },
|
||||||
EnableRealtimeMonitor = false,
|
EnableRealtimeMonitor = false,
|
||||||
SaveLocalMetadata = true
|
SaveLocalMetadata = true
|
||||||
};
|
};
|
||||||
|
@ -1141,15 +1141,25 @@ namespace Emby.Server.Implementations.Data
|
|||||||
Path = RestorePath(path.ToString())
|
Path = RestorePath(path.ToString())
|
||||||
};
|
};
|
||||||
|
|
||||||
if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
|
if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)
|
||||||
|
&& ticks >= DateTime.MinValue.Ticks
|
||||||
|
&& ticks <= DateTime.MaxValue.Ticks)
|
||||||
{
|
{
|
||||||
image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
|
image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
|
if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
|
||||||
{
|
{
|
||||||
image.Type = type;
|
image.Type = type;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Optional parameters: width*height*blurhash
|
// Optional parameters: width*height*blurhash
|
||||||
if (nextSegment + 1 < value.Length - 1)
|
if (nextSegment + 1 < value.Length - 1)
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DiscUtils.Udf" Version="0.16.4" />
|
||||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
|
||||||
@ -30,7 +31,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.1" />
|
<PackageReference Include="Mono.Nat" Version="3.0.1" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.0" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.28.3" />
|
<PackageReference Include="sharpcompress" Version="0.28.3" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||||
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
|
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
|
||||||
|
@ -3173,10 +3173,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
|
if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
|
||||||
{
|
{
|
||||||
list.Add(new MediaPathInfo
|
list.Add(new MediaPathInfo(location));
|
||||||
{
|
|
||||||
Path = location
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using DiscUtils.Udf;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@ -201,6 +202,22 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
{
|
{
|
||||||
video.IsoType = IsoType.BluRay;
|
video.IsoType = IsoType.BluRay;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use disc-utils, both DVDs and BDs use UDF filesystem
|
||||||
|
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
|
||||||
|
{
|
||||||
|
UdfReader udfReader = new UdfReader(videoFileStream);
|
||||||
|
if (udfReader.DirectoryExists("VIDEO_TS"))
|
||||||
|
{
|
||||||
|
video.IsoType = IsoType.Dvd;
|
||||||
|
}
|
||||||
|
else if (udfReader.DirectoryExists("BDMV"))
|
||||||
|
{
|
||||||
|
video.IsoType = IsoType.BluRay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -33,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
return targetFile;
|
return targetFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
public Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (directStreamProvider != null)
|
if (directStreamProvider != null)
|
||||||
{
|
{
|
||||||
@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
|
||||||
|
|
||||||
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
|
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
|
||||||
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
|
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||||
@ -71,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
_logger.LogInformation("Opened recording stream from tuner provider");
|
_logger.LogInformation("Opened recording stream from tuner provider");
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
|
||||||
|
|
||||||
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
|
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
|
||||||
await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);
|
await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||||
|
@ -159,8 +159,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var recordingFolders = GetRecordingFolders().ToArray();
|
var recordingFolders = GetRecordingFolders().ToArray();
|
||||||
var virtualFolders = _libraryManager.GetVirtualFolders()
|
var virtualFolders = _libraryManager.GetVirtualFolders();
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
|
var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
|
||||||
|
|
||||||
@ -177,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray();
|
var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo(i)).ToArray();
|
||||||
|
|
||||||
var libraryOptions = new LibraryOptions
|
var libraryOptions = new LibraryOptions
|
||||||
{
|
{
|
||||||
@ -210,7 +209,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
foreach (var path in pathsToRemove)
|
foreach (var path in pathsToRemove)
|
||||||
{
|
{
|
||||||
await RemovePathFromLibrary(path).ConfigureAwait(false);
|
await RemovePathFromLibraryAsync(path).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -219,13 +218,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RemovePathFromLibrary(string path)
|
private async Task RemovePathFromLibraryAsync(string path)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Removing path from library: {0}", path);
|
_logger.LogDebug("Removing path from library: {0}", path);
|
||||||
|
|
||||||
var requiresRefresh = false;
|
var requiresRefresh = false;
|
||||||
var virtualFolders = _libraryManager.GetVirtualFolders()
|
var virtualFolders = _libraryManager.GetVirtualFolders();
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var virtualFolder in virtualFolders)
|
foreach (var virtualFolder in virtualFolders)
|
||||||
{
|
{
|
||||||
@ -460,7 +458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
|
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
|
||||||
{
|
{
|
||||||
var tunerChannelId = tunerChannel.TunerChannelId;
|
var tunerChannelId = tunerChannel.TunerChannelId;
|
||||||
if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1)
|
if (tunerChannelId.Contains(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
|
tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
|
||||||
}
|
}
|
||||||
@ -620,8 +618,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
if (existingTimer != null)
|
if (existingTimer != null)
|
||||||
{
|
{
|
||||||
if (existingTimer.Status == RecordingStatus.Cancelled ||
|
if (existingTimer.Status == RecordingStatus.Cancelled
|
||||||
existingTimer.Status == RecordingStatus.Completed)
|
|| existingTimer.Status == RecordingStatus.Completed)
|
||||||
{
|
{
|
||||||
existingTimer.Status = RecordingStatus.New;
|
existingTimer.Status = RecordingStatus.New;
|
||||||
existingTimer.IsManual = true;
|
existingTimer.IsManual = true;
|
||||||
@ -913,18 +911,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
|
var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
List<ProgramInfo> programs;
|
|
||||||
|
|
||||||
if (epgChannel == null)
|
if (epgChannel == null)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
|
_logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
|
||||||
programs = new List<ProgramInfo>();
|
continue;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
List<ProgramInfo> programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
|
||||||
programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
|
|
||||||
.ConfigureAwait(false)).ToList();
|
.ConfigureAwait(false)).ToList();
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the value that came from the provider with a normalized value
|
// Replace the value that came from the provider with a normalized value
|
||||||
foreach (var program in programs)
|
foreach (var program in programs)
|
||||||
@ -940,7 +934,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<ProgramInfo>();
|
return Enumerable.Empty<ProgramInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
|
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
|
||||||
@ -1292,7 +1286,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
_logger.LogInformation("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
_logger.LogInformation("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
_logger.LogInformation("Writing file to path: " + recordPath);
|
_logger.LogInformation("Writing file to: {Path}", recordPath);
|
||||||
|
|
||||||
Action onStarted = async () =>
|
Action onStarted = async () =>
|
||||||
{
|
{
|
||||||
@ -1417,13 +1411,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
private void TriggerRefresh(string path)
|
private void TriggerRefresh(string path)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Triggering refresh on {path}", path);
|
_logger.LogInformation("Triggering refresh on {Path}", path);
|
||||||
|
|
||||||
var item = GetAffectedBaseItem(Path.GetDirectoryName(path));
|
var item = GetAffectedBaseItem(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Refreshing recording parent {path}", item.Path);
|
_logger.LogInformation("Refreshing recording parent {Path}", item.Path);
|
||||||
|
|
||||||
_providerManager.QueueRefresh(
|
_providerManager.QueueRefresh(
|
||||||
item.Id,
|
item.Id,
|
||||||
@ -1512,8 +1506,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
DeleteLibraryItemsForTimers(timersToDelete);
|
DeleteLibraryItemsForTimers(timersToDelete);
|
||||||
|
|
||||||
var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder;
|
if (_libraryManager.FindByPath(seriesPath, true) is not Folder librarySeries)
|
||||||
if (librarySeries == null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1667,7 +1660,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
process.Exited += Process_Exited;
|
process.Exited += OnProcessExited;
|
||||||
process.Start();
|
process.Start();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -1681,7 +1674,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
return arguments.Replace("{path}", path, StringComparison.OrdinalIgnoreCase);
|
return arguments.Replace("{path}", path, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Process_Exited(object sender, EventArgs e)
|
private void OnProcessExited(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (var process = (Process)sender)
|
using (var process = (Process)sender)
|
||||||
{
|
{
|
||||||
@ -2239,7 +2232,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
var enabledTimersForSeries = new List<TimerInfo>();
|
var enabledTimersForSeries = new List<TimerInfo>();
|
||||||
foreach (var timer in allTimers)
|
foreach (var timer in allTimers)
|
||||||
{
|
{
|
||||||
var existingTimer = _timerProvider.GetTimer(timer.Id)
|
var existingTimer = _timerProvider.GetTimer(timer.Id)
|
||||||
?? (string.IsNullOrWhiteSpace(timer.ProgramId)
|
?? (string.IsNullOrWhiteSpace(timer.ProgramId)
|
||||||
? null
|
? null
|
||||||
: _timerProvider.GetTimerByProgramId(timer.ProgramId));
|
: _timerProvider.GetTimerByProgramId(timer.ProgramId));
|
||||||
|
@ -319,11 +319,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
// TODO Investigate and properly fix.
|
|
||||||
// Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error reading ffmpeg recording log");
|
_logger.LogError(ex, "Error reading ffmpeg recording log");
|
||||||
|
@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Records the specified media source.
|
/// Records the specified media source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
|
Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
|
||||||
|
|
||||||
string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
|
string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -23,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
|
public event EventHandler<GenericEventArgs<TimerInfo>>? TimerFired;
|
||||||
|
|
||||||
public void RestartTimers()
|
public void RestartTimers()
|
||||||
{
|
{
|
||||||
@ -145,9 +143,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimerCallback(object state)
|
private void TimerCallback(object? state)
|
||||||
{
|
{
|
||||||
var timerId = (string)state;
|
var timerId = (string?)state ?? throw new ArgumentNullException(nameof(state));
|
||||||
|
|
||||||
var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
|
var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
|
||||||
if (timer != null)
|
if (timer != null)
|
||||||
@ -156,12 +154,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimerInfo GetTimer(string id)
|
public TimerInfo? GetTimer(string id)
|
||||||
{
|
{
|
||||||
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
|
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimerInfo GetTimerByProgramId(string programId)
|
public TimerInfo? GetTimerByProgramId(string programId)
|
||||||
{
|
{
|
||||||
return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
|
return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
@ -295,11 +295,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes.TryGetValue("tvg-name", out string name);
|
string name = nameInExtInf;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
{
|
{
|
||||||
name = nameInExtInf;
|
attributes.TryGetValue("tvg-name", out name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"HeaderLiveTV": "Canlı TV",
|
"HeaderLiveTV": "Canlı TV",
|
||||||
"HeaderNextUp": "Gelecek Hafta",
|
"HeaderNextUp": "Gelecek Hafta",
|
||||||
"HeaderRecordingGroups": "Kayıt Grupları",
|
"HeaderRecordingGroups": "Kayıt Grupları",
|
||||||
"HomeVideos": "Ev videoları",
|
"HomeVideos": "Ana sayfa videoları",
|
||||||
"Inherit": "Devral",
|
"Inherit": "Devral",
|
||||||
"ItemAddedWithName": "{0} kütüphaneye eklendi",
|
"ItemAddedWithName": "{0} kütüphaneye eklendi",
|
||||||
"ItemRemovedWithName": "{0} kütüphaneden silindi",
|
"ItemRemovedWithName": "{0} kütüphaneden silindi",
|
||||||
|
@ -16,6 +16,7 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: NeutralResourcesLanguage("en")]
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
[assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")]
|
[assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")]
|
||||||
|
[assembly: InternalsVisibleTo("Emby.Server.Implementations.Fuzz")]
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
// to COM components. If you need to access a type in this assembly from
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||||||
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||||
{
|
{
|
||||||
var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
|
var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
|
||||||
if (!retentionDays.HasValue || retentionDays <= 0)
|
if (!retentionDays.HasValue || retentionDays < 0)
|
||||||
{
|
{
|
||||||
throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
|
throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (paths != null && paths.Length > 0)
|
if (paths != null && paths.Length > 0)
|
||||||
{
|
{
|
||||||
libraryOptions.PathInfos = paths.Select(i => new MediaPathInfo { Path = i }).ToArray();
|
libraryOptions.PathInfos = paths.Select(i => new MediaPathInfo(i)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _libraryManager.AddVirtualFolder(name, collectionType, libraryOptions, refreshLibrary).ConfigureAwait(false);
|
await _libraryManager.AddVirtualFolder(name, collectionType, libraryOptions, refreshLibrary).ConfigureAwait(false);
|
||||||
@ -212,7 +212,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var mediaPath = mediaPathDto.PathInfo ?? new MediaPathInfo { Path = mediaPathDto.Path };
|
var mediaPath = mediaPathDto.PathInfo ?? new MediaPathInfo(mediaPathDto.Path ?? throw new ArgumentException("PathInfo and Path can't both be null."));
|
||||||
|
|
||||||
_libraryManager.AddMediaPath(mediaPathDto.Name, mediaPath);
|
_libraryManager.AddMediaPath(mediaPathDto.Name, mediaPath);
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
|
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -37,6 +37,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
"ConstrainedHigh"
|
"ConstrainedHigh"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly Version minVersionForCudaOverlay = new Version(4, 4);
|
||||||
|
|
||||||
public EncodingHelper(
|
public EncodingHelper(
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
ISubtitleEncoder subtitleEncoder)
|
ISubtitleEncoder subtitleEncoder)
|
||||||
@ -106,17 +108,41 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
private bool IsCudaSupported()
|
private bool IsCudaSupported()
|
||||||
{
|
{
|
||||||
return _mediaEncoder.SupportsHwaccel("cuda")
|
return _mediaEncoder.SupportsHwaccel("cuda")
|
||||||
&& _mediaEncoder.SupportsFilter("scale_cuda", null)
|
&& _mediaEncoder.SupportsFilter("scale_cuda")
|
||||||
&& _mediaEncoder.SupportsFilter("yadif_cuda", null);
|
&& _mediaEncoder.SupportsFilter("yadif_cuda")
|
||||||
|
&& _mediaEncoder.SupportsFilter("hwupload_cuda");
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
|
private bool IsOpenclTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
|
||||||
{
|
{
|
||||||
var videoStream = state.VideoStream;
|
var videoStream = state.VideoStream;
|
||||||
return IsColorDepth10(state)
|
if (videoStream == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.EnableTonemapping
|
||||||
|
&& (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
|
||||||
|
&& IsColorDepth10(state)
|
||||||
&& _mediaEncoder.SupportsHwaccel("opencl")
|
&& _mediaEncoder.SupportsHwaccel("opencl")
|
||||||
&& options.EnableTonemapping
|
&& _mediaEncoder.SupportsFilter("tonemap_opencl");
|
||||||
&& string.Equals(videoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
|
}
|
||||||
|
|
||||||
|
private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
|
||||||
|
{
|
||||||
|
var videoStream = state.VideoStream;
|
||||||
|
if (videoStream == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.EnableTonemapping
|
||||||
|
&& (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
|
||||||
|
&& IsColorDepth10(state)
|
||||||
|
&& _mediaEncoder.SupportsHwaccel("cuda")
|
||||||
|
&& _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
|
private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
|
||||||
@ -132,23 +158,25 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Limited to HEVC for now since the filter doesn't accept master data from VP9.
|
// Limited to HEVC for now since the filter doesn't accept master data from VP9.
|
||||||
return IsColorDepth10(state)
|
return options.EnableVppTonemapping
|
||||||
|
&& string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& IsColorDepth10(state)
|
||||||
&& string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
&& _mediaEncoder.SupportsHwaccel("vaapi")
|
&& _mediaEncoder.SupportsHwaccel("vaapi")
|
||||||
&& options.EnableVppTonemapping
|
&& _mediaEncoder.SupportsFilter("tonemap_vaapi");
|
||||||
&& string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hybrid VPP tonemapping for QSV with VAAPI
|
// Hybrid VPP tonemapping for QSV with VAAPI
|
||||||
if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Limited to HEVC for now since the filter doesn't accept master data from VP9.
|
// Limited to HEVC for now since the filter doesn't accept master data from VP9.
|
||||||
return IsColorDepth10(state)
|
return options.EnableVppTonemapping
|
||||||
|
&& string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& IsColorDepth10(state)
|
||||||
&& string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
&& _mediaEncoder.SupportsHwaccel("vaapi")
|
&& _mediaEncoder.SupportsHwaccel("vaapi")
|
||||||
&& _mediaEncoder.SupportsHwaccel("qsv")
|
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
|
||||||
&& options.EnableVppTonemapping
|
&& _mediaEncoder.SupportsHwaccel("qsv");
|
||||||
&& string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Native VPP tonemapping may come to QSV in the future.
|
// Native VPP tonemapping may come to QSV in the future.
|
||||||
@ -497,13 +525,16 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
/// Gets the input argument.
|
/// Gets the input argument.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">Encoding state.</param>
|
/// <param name="state">Encoding state.</param>
|
||||||
/// <param name="encodingOptions">Encoding options.</param>
|
/// <param name="options">Encoding options.</param>
|
||||||
/// <returns>Input arguments.</returns>
|
/// <returns>Input arguments.</returns>
|
||||||
public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
|
public string GetInputArgument(EncodingJobInfo state, EncodingOptions options)
|
||||||
{
|
{
|
||||||
var arg = new StringBuilder();
|
var arg = new StringBuilder();
|
||||||
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
|
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
|
||||||
var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
|
var outputVideoCodec = GetVideoEncoder(state, options) ?? string.Empty;
|
||||||
|
var isWindows = OperatingSystem.IsWindows();
|
||||||
|
var isLinux = OperatingSystem.IsLinux();
|
||||||
|
var isMacOS = OperatingSystem.IsMacOS();
|
||||||
#pragma warning disable CA1508 // Defaults to string.Empty
|
#pragma warning disable CA1508 // Defaults to string.Empty
|
||||||
var isSwDecoder = string.IsNullOrEmpty(videoDecoder);
|
var isSwDecoder = string.IsNullOrEmpty(videoDecoder);
|
||||||
#pragma warning restore CA1508
|
#pragma warning restore CA1508
|
||||||
@ -514,26 +545,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
|
var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
|
||||||
var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
|
var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
|
||||||
var isWindows = OperatingSystem.IsWindows();
|
var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase);
|
||||||
var isLinux = OperatingSystem.IsLinux();
|
var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
|
||||||
var isMacOS = OperatingSystem.IsMacOS();
|
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
|
||||||
var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions);
|
var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
|
||||||
var isVppTonemappingSupported = IsVppTonemappingSupported(state, encodingOptions);
|
|
||||||
|
|
||||||
if (!IsCopyCodec(outputVideoCodec))
|
if (!IsCopyCodec(outputVideoCodec))
|
||||||
{
|
{
|
||||||
if (state.IsVideoRequest
|
if (state.IsVideoRequest
|
||||||
&& _mediaEncoder.SupportsHwaccel("vaapi")
|
&& _mediaEncoder.SupportsHwaccel("vaapi")
|
||||||
&& string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (isVaapiDecoder)
|
if (isVaapiDecoder)
|
||||||
{
|
{
|
||||||
if (isTonemappingSupported && !isVppTonemappingSupported)
|
if (isOpenclTonemappingSupported && !isVppTonemappingSupported)
|
||||||
{
|
{
|
||||||
arg.Append("-init_hw_device vaapi=va:")
|
arg.Append("-init_hw_device vaapi=va:")
|
||||||
.Append(encodingOptions.VaapiDevice)
|
.Append(options.VaapiDevice)
|
||||||
.Append(' ')
|
.Append(" -init_hw_device opencl=ocl@va ")
|
||||||
.Append("-init_hw_device opencl=ocl@va ")
|
|
||||||
.Append("-hwaccel_device va ")
|
.Append("-hwaccel_device va ")
|
||||||
.Append("-hwaccel_output_format vaapi ")
|
.Append("-hwaccel_output_format vaapi ")
|
||||||
.Append("-filter_hw_device ocl ");
|
.Append("-filter_hw_device ocl ");
|
||||||
@ -542,14 +571,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
arg.Append("-hwaccel_output_format vaapi ")
|
arg.Append("-hwaccel_output_format vaapi ")
|
||||||
.Append("-vaapi_device ")
|
.Append("-vaapi_device ")
|
||||||
.Append(encodingOptions.VaapiDevice)
|
.Append(options.VaapiDevice)
|
||||||
.Append(' ');
|
.Append(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!isVaapiDecoder && isVaapiEncoder)
|
else if (!isVaapiDecoder && isVaapiEncoder)
|
||||||
{
|
{
|
||||||
arg.Append("-vaapi_device ")
|
arg.Append("-vaapi_device ")
|
||||||
.Append(encodingOptions.VaapiDevice)
|
.Append(options.VaapiDevice)
|
||||||
.Append(' ');
|
.Append(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,7 +586,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.IsVideoRequest
|
if (state.IsVideoRequest
|
||||||
&& string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
|
|
||||||
@ -593,9 +622,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
else if (isVaapiDecoder && isVppTonemappingSupported)
|
else if (isVaapiDecoder && isVppTonemappingSupported)
|
||||||
{
|
{
|
||||||
arg.Append("-init_hw_device vaapi=va:")
|
arg.Append("-init_hw_device vaapi=va:")
|
||||||
.Append(encodingOptions.VaapiDevice)
|
.Append(options.VaapiDevice)
|
||||||
.Append(' ')
|
.Append(" -init_hw_device qsv@va ")
|
||||||
.Append("-init_hw_device qsv@va ")
|
|
||||||
.Append("-hwaccel_output_format vaapi ");
|
.Append("-hwaccel_output_format vaapi ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,7 +632,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.IsVideoRequest
|
if (state.IsVideoRequest
|
||||||
&& string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
|
||||||
&& isNvdecDecoder)
|
&& isNvdecDecoder)
|
||||||
{
|
{
|
||||||
// Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
|
// Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
|
||||||
@ -612,22 +640,31 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.IsVideoRequest
|
if (state.IsVideoRequest
|
||||||
&& ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
|
&& ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
|
||||||
&& (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder))
|
&& (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder))))
|
||||||
|| (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& (isD3d11vaDecoder || isSwDecoder))))
|
|
||||||
{
|
{
|
||||||
if (isTonemappingSupported)
|
if (!isCudaTonemappingSupported && isOpenclTonemappingSupported)
|
||||||
{
|
{
|
||||||
arg.Append("-init_hw_device opencl=ocl:")
|
arg.Append("-init_hw_device opencl=ocl:")
|
||||||
.Append(encodingOptions.OpenclDevice)
|
.Append(options.OpenclDevice)
|
||||||
.Append(' ')
|
.Append(" -filter_hw_device ocl ");
|
||||||
.Append("-filter_hw_device ocl ");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.IsVideoRequest
|
if (state.IsVideoRequest
|
||||||
&& string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& (isD3d11vaDecoder || isSwDecoder))
|
||||||
|
{
|
||||||
|
if (isOpenclTonemappingSupported)
|
||||||
|
{
|
||||||
|
arg.Append("-init_hw_device opencl=ocl:")
|
||||||
|
.Append(options.OpenclDevice)
|
||||||
|
.Append(" -filter_hw_device ocl ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.IsVideoRequest
|
||||||
|
&& string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
arg.Append("-hwaccel videotoolbox ");
|
arg.Append("-hwaccel videotoolbox ");
|
||||||
}
|
}
|
||||||
@ -2012,14 +2049,18 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
|
var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
|
||||||
var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
|
var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
|
||||||
var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
|
var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
|
||||||
var isTonemappingSupported = IsTonemappingSupported(state, options);
|
|
||||||
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
|
|
||||||
var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
|
var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
|
||||||
var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
|
var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
|
||||||
|
var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
|
||||||
|
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
|
||||||
|
|
||||||
|
var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
|
||||||
|
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
|
||||||
|
var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
|
||||||
|
|
||||||
// Tonemapping and burn-in graphical subtitles requires overlay_vaapi.
|
// Tonemapping and burn-in graphical subtitles requires overlay_vaapi.
|
||||||
// But it's still in ffmpeg mailing list. Disable it for now.
|
// But it's still in ffmpeg mailing list. Disable it for now.
|
||||||
if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
|
if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported)
|
||||||
{
|
{
|
||||||
return GetOutputSizeParam(state, options, outputVideoCodec);
|
return GetOutputSizeParam(state, options, outputVideoCodec);
|
||||||
}
|
}
|
||||||
@ -2045,13 +2086,22 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
if (!string.IsNullOrEmpty(videoSizeParam)
|
if (!string.IsNullOrEmpty(videoSizeParam)
|
||||||
&& !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
|
&& !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
|
||||||
{
|
{
|
||||||
// For QSV, feed it into hardware encoder now
|
// upload graphical subtitle to QSV
|
||||||
if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
|
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
videoSizeParam += ",hwupload=extra_hw_frames=64";
|
videoSizeParam += ",hwupload=extra_hw_frames=64";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(videoSizeParam))
|
||||||
|
{
|
||||||
|
// upload graphical subtitle to cuda
|
||||||
|
if (isNvdecDecoder && isNvencEncoder && isCudaOverlaySupported && isCudaFormatConversionSupported)
|
||||||
|
{
|
||||||
|
videoSizeParam += ",hwupload_cuda";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapPrefix = state.SubtitleStream.IsExternal ?
|
var mapPrefix = state.SubtitleStream.IsExternal ?
|
||||||
@ -2064,9 +2114,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
|
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
|
||||||
// Always put the scaler before the overlay for better performance
|
// Always put the scaler before the overlay for better performance
|
||||||
var retStr = !outputSizeParam.IsEmpty
|
var retStr = outputSizeParam.IsEmpty
|
||||||
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""
|
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""
|
||||||
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
|
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
|
||||||
|
|
||||||
// When the input may or may not be hardware VAAPI decodable
|
// When the input may or may not be hardware VAAPI decodable
|
||||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||||
@ -2077,9 +2127,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
[sub]: SW scaling subtitle to FixedOutputSize
|
[sub]: SW scaling subtitle to FixedOutputSize
|
||||||
[base][sub]: SW overlay
|
[base][sub]: SW overlay
|
||||||
*/
|
*/
|
||||||
retStr = !outputSizeParam.IsEmpty
|
retStr = outputSizeParam.IsEmpty
|
||||||
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""
|
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""
|
||||||
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
|
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||||
@ -2092,9 +2142,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
[sub]: SW scaling subtitle to FixedOutputSize
|
[sub]: SW scaling subtitle to FixedOutputSize
|
||||||
[base][sub]: SW overlay
|
[base][sub]: SW overlay
|
||||||
*/
|
*/
|
||||||
retStr = !outputSizeParam.IsEmpty
|
retStr = outputSizeParam.IsEmpty
|
||||||
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""
|
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""
|
||||||
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
|
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
|
||||||
}
|
}
|
||||||
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
@ -2111,16 +2161,25 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
else if (isLinux)
|
else 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}][sub]overlay_qsv\""
|
||||||
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
|
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isNvdecDecoder && isNvencEncoder)
|
else if (isNvdecDecoder && isNvencEncoder)
|
||||||
{
|
{
|
||||||
retStr = !outputSizeParam.IsEmpty
|
if (isCudaOverlaySupported && isCudaFormatConversionSupported)
|
||||||
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""
|
{
|
||||||
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"";
|
retStr = outputSizeParam.IsEmpty
|
||||||
|
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]scale_cuda=format=yuv420p[base];[base][sub]overlay_cuda\""
|
||||||
|
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_cuda\"";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
retStr = outputSizeParam.IsEmpty
|
||||||
|
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""
|
||||||
|
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format(
|
return string.Format(
|
||||||
@ -2217,11 +2276,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase);
|
var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase);
|
||||||
var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase);
|
var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase);
|
||||||
var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
|
var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
|
||||||
var isTonemappingSupported = IsTonemappingSupported(state, options);
|
var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
|
||||||
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
|
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
|
||||||
var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
|
var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
|
||||||
var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
|
var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
|
||||||
var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))
|
var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported))
|
||||||
|| (isTonemappingSupportedOnQsv && isVppTonemappingSupported);
|
|| (isTonemappingSupportedOnQsv && isVppTonemappingSupported);
|
||||||
|
|
||||||
var outputPixFmt = "format=nv12";
|
var outputPixFmt = "format=nv12";
|
||||||
@ -2272,15 +2331,23 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var outputWidth = width.Value;
|
var outputWidth = width.Value;
|
||||||
var outputHeight = height.Value;
|
var outputHeight = height.Value;
|
||||||
|
|
||||||
var isTonemappingSupported = IsTonemappingSupported(state, options);
|
var isNvencEncoder = videoEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
|
||||||
|
var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
|
||||||
var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase);
|
var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase);
|
||||||
var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")");
|
var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
|
||||||
|
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
|
||||||
|
var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
|
||||||
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
|
|
||||||
var outputPixFmt = string.Empty;
|
var outputPixFmt = string.Empty;
|
||||||
if (isCudaFormatConversionSupported)
|
if (isCudaFormatConversionSupported)
|
||||||
{
|
{
|
||||||
outputPixFmt = "format=nv12";
|
outputPixFmt = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder)
|
||||||
if (isTonemappingSupported && isTonemappingSupportedOnNvenc)
|
? "format=yuv420p"
|
||||||
|
: "format=nv12";
|
||||||
|
if ((isOpenclTonemappingSupported || isCudaTonemappingSupported)
|
||||||
|
&& isTonemappingSupportedOnNvenc)
|
||||||
{
|
{
|
||||||
outputPixFmt = "format=p010";
|
outputPixFmt = "format=p010";
|
||||||
}
|
}
|
||||||
@ -2558,16 +2625,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
|
var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
|
||||||
var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase);
|
var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase);
|
||||||
var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
|
var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase);
|
||||||
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
|
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
|
var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
var isLinux = OperatingSystem.IsLinux();
|
var isLinux = OperatingSystem.IsLinux();
|
||||||
var isColorDepth10 = IsColorDepth10(state);
|
var isColorDepth10 = IsColorDepth10(state);
|
||||||
var isTonemappingSupported = IsTonemappingSupported(state, options);
|
|
||||||
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
|
var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder);
|
||||||
var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder);
|
|
||||||
var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder);
|
var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder);
|
||||||
var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
|
var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
|
||||||
var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
|
var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
|
||||||
|
var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
|
||||||
|
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
|
||||||
|
var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
|
||||||
|
var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
|
||||||
|
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
|
||||||
|
|
||||||
var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
@ -2579,19 +2651,25 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var isScalingInAdvance = false;
|
var isScalingInAdvance = false;
|
||||||
var isCudaDeintInAdvance = false;
|
var isCudaDeintInAdvance = false;
|
||||||
var isHwuploadCudaRequired = false;
|
var isHwuploadCudaRequired = false;
|
||||||
|
var isNoTonemapFilterApplied = true;
|
||||||
var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
|
var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
|
||||||
var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
|
var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
|
||||||
|
|
||||||
// Add OpenCL tonemapping filter for NVENC/AMF/VAAPI.
|
// Add OpenCL tonemapping filter for NVENC/AMF/VAAPI.
|
||||||
if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported))
|
if ((isTonemappingSupportedOnNvenc && !isCudaTonemappingSupported) || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported))
|
||||||
{
|
{
|
||||||
// Currently only with the use of NVENC decoder can we get a decent performance.
|
|
||||||
// Currently only the HEVC/H265 format is supported with NVDEC decoder.
|
|
||||||
// NVIDIA Pascal and Turing or higher are recommended.
|
// NVIDIA Pascal and Turing or higher are recommended.
|
||||||
// AMD Polaris and Vega or higher are recommended.
|
// AMD Polaris and Vega or higher are recommended.
|
||||||
// Intel Kaby Lake or newer is required.
|
// Intel Kaby Lake or newer is required.
|
||||||
if (isTonemappingSupported)
|
if (isOpenclTonemappingSupported)
|
||||||
{
|
{
|
||||||
|
isNoTonemapFilterApplied = false;
|
||||||
|
var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer);
|
||||||
|
if (!string.IsNullOrEmpty(inputHdrParams))
|
||||||
|
{
|
||||||
|
filters.Add(inputHdrParams);
|
||||||
|
}
|
||||||
|
|
||||||
var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
|
var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
|
||||||
|
|
||||||
if (options.TonemappingParam != 0)
|
if (options.TonemappingParam != 0)
|
||||||
@ -2663,7 +2741,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
filters.Add("hwdownload,format=p010");
|
filters.Add("hwdownload,format=p010");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder)
|
if (isNvdecDecoder
|
||||||
|
|| isCuvidHevcDecoder
|
||||||
|
|| isCuvidVp9Decoder
|
||||||
|
|| isSwDecoder
|
||||||
|
|| isD3d11vaDecoder)
|
||||||
{
|
{
|
||||||
// Upload the HDR10 or HLG data to the OpenCL device,
|
// Upload the HDR10 or HLG data to the OpenCL device,
|
||||||
// use tonemap_opencl filter for tone mapping,
|
// use tonemap_opencl filter for tone mapping,
|
||||||
@ -2671,6 +2753,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
filters.Add("hwupload");
|
filters.Add("hwupload");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback to hable if bt2390 is chosen but not supported in tonemap_opencl.
|
||||||
|
var isBt2390SupportedInOpenclTonemap = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390);
|
||||||
|
if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !isBt2390SupportedInOpenclTonemap)
|
||||||
|
{
|
||||||
|
options.TonemappingAlgorithm = "hable";
|
||||||
|
}
|
||||||
|
|
||||||
filters.Add(
|
filters.Add(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
@ -2682,7 +2772,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
options.TonemappingParam,
|
options.TonemappingParam,
|
||||||
options.TonemappingRange));
|
options.TonemappingRange));
|
||||||
|
|
||||||
if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder)
|
if (isNvdecDecoder
|
||||||
|
|| isCuvidHevcDecoder
|
||||||
|
|| isCuvidVp9Decoder
|
||||||
|
|| isSwDecoder
|
||||||
|
|| isD3d11vaDecoder)
|
||||||
{
|
{
|
||||||
filters.Add("hwdownload");
|
filters.Add("hwdownload");
|
||||||
filters.Add("format=nv12");
|
filters.Add("format=nv12");
|
||||||
@ -2698,12 +2792,18 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
// Reverse the data route from opencl to vaapi.
|
// Reverse the data route from opencl to vaapi.
|
||||||
filters.Add("hwmap=derive_device=vaapi:reverse=1");
|
filters.Add("hwmap=derive_device=vaapi:reverse=1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var outputSdrParams = GetOutputSdrParams(options.TonemappingRange);
|
||||||
|
if (!string.IsNullOrEmpty(outputSdrParams))
|
||||||
|
{
|
||||||
|
filters.Add(outputSdrParams);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the input may or may not be hardware VAAPI decodable.
|
// When the input may or may not be hardware VAAPI decodable.
|
||||||
if ((isVaapiH264Encoder || isVaapiHevcEncoder)
|
if ((isVaapiH264Encoder || isVaapiHevcEncoder)
|
||||||
&& !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)))
|
&& !(isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported)))
|
||||||
{
|
{
|
||||||
filters.Add("format=nv12|vaapi");
|
filters.Add("format=nv12|vaapi");
|
||||||
filters.Add("hwupload");
|
filters.Add("hwupload");
|
||||||
@ -2811,6 +2911,61 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
request.MaxHeight));
|
request.MaxHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Cuda tonemapping filter.
|
||||||
|
if (isNvdecDecoder && isCudaTonemappingSupported)
|
||||||
|
{
|
||||||
|
isNoTonemapFilterApplied = false;
|
||||||
|
var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer);
|
||||||
|
if (!string.IsNullOrEmpty(inputHdrParams))
|
||||||
|
{
|
||||||
|
filters.Add(inputHdrParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder)
|
||||||
|
? "tonemap_cuda=format=yuv420p:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}"
|
||||||
|
: "tonemap_cuda=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}";
|
||||||
|
|
||||||
|
if (options.TonemappingParam != 0)
|
||||||
|
{
|
||||||
|
parameters += ":param={3}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
parameters += ":range={4}";
|
||||||
|
}
|
||||||
|
|
||||||
|
filters.Add(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
parameters,
|
||||||
|
options.TonemappingAlgorithm,
|
||||||
|
options.TonemappingPeak,
|
||||||
|
options.TonemappingDesat,
|
||||||
|
options.TonemappingParam,
|
||||||
|
options.TonemappingRange));
|
||||||
|
|
||||||
|
if (isLibX264Encoder
|
||||||
|
|| isLibX265Encoder
|
||||||
|
|| hasTextSubs
|
||||||
|
|| (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))
|
||||||
|
{
|
||||||
|
if (isNvencEncoder)
|
||||||
|
{
|
||||||
|
isHwuploadCudaRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
filters.Add("hwdownload");
|
||||||
|
filters.Add("format=nv12");
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputSdrParams = GetOutputSdrParams(options.TonemappingRange);
|
||||||
|
if (!string.IsNullOrEmpty(outputSdrParams))
|
||||||
|
{
|
||||||
|
filters.Add(outputSdrParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add VPP tonemapping filter for VAAPI.
|
// Add VPP tonemapping filter for VAAPI.
|
||||||
// Full hardware based video post processing, faster than OpenCL but lacks fine tuning options.
|
// Full hardware based video post processing, faster than OpenCL but lacks fine tuning options.
|
||||||
if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv)
|
if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv)
|
||||||
@ -2820,10 +2975,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Another case is when using Nvenc decoder.
|
// Another case is when using Nvenc decoder.
|
||||||
if (isNvdecDecoder && !isTonemappingSupported)
|
if (isNvdecDecoder && !isOpenclTonemappingSupported && !isCudaTonemappingSupported)
|
||||||
{
|
{
|
||||||
var codec = videoStream.Codec;
|
var codec = videoStream.Codec;
|
||||||
var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")");
|
var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
|
||||||
|
|
||||||
// Assert 10-bit hardware decodable
|
// Assert 10-bit hardware decodable
|
||||||
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
@ -2832,7 +2987,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
if (isCudaFormatConversionSupported)
|
if (isCudaFormatConversionSupported)
|
||||||
{
|
{
|
||||||
if (isLibX264Encoder || isLibX265Encoder || hasSubs)
|
if (isLibX264Encoder
|
||||||
|
|| isLibX265Encoder
|
||||||
|
|| hasTextSubs
|
||||||
|
|| (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))
|
||||||
{
|
{
|
||||||
if (isNvencEncoder)
|
if (isNvencEncoder)
|
||||||
{
|
{
|
||||||
@ -2859,7 +3017,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Assert 8-bit hardware decodable
|
// Assert 8-bit hardware decodable
|
||||||
else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs))
|
else if (!isColorDepth10
|
||||||
|
&& (isLibX264Encoder
|
||||||
|
|| isLibX265Encoder
|
||||||
|
|| hasTextSubs
|
||||||
|
|| (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)))
|
||||||
{
|
{
|
||||||
if (isNvencEncoder)
|
if (isNvencEncoder)
|
||||||
{
|
{
|
||||||
@ -2880,7 +3042,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
// Convert hw context from ocl to va.
|
// Convert hw context from ocl to va.
|
||||||
// For tonemapping and text subs burn-in.
|
// For tonemapping and text subs burn-in.
|
||||||
if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
|
if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported)
|
||||||
{
|
{
|
||||||
filters.Add("scale_vaapi");
|
filters.Add("scale_vaapi");
|
||||||
}
|
}
|
||||||
@ -2926,6 +3088,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
filters.Add("hwupload_cuda");
|
filters.Add("hwupload_cuda");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no tonemap filter is applied,
|
||||||
|
// tag the video range as SDR to prevent the encoder from encoding HDR video.
|
||||||
|
if (isNoTonemapFilterApplied)
|
||||||
|
{
|
||||||
|
var outputSdrParams = GetOutputSdrParams(null);
|
||||||
|
if (!string.IsNullOrEmpty(outputSdrParams))
|
||||||
|
{
|
||||||
|
filters.Add(outputSdrParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var output = string.Empty;
|
var output = string.Empty;
|
||||||
if (filters.Count > 0)
|
if (filters.Count > 0)
|
||||||
{
|
{
|
||||||
@ -2938,6 +3111,36 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetInputHdrParams(string colorTransfer)
|
||||||
|
{
|
||||||
|
if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// HLG
|
||||||
|
return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// HDR10
|
||||||
|
return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetOutputSdrParams(string tonemappingRange)
|
||||||
|
{
|
||||||
|
// SDR
|
||||||
|
if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of threads.
|
/// Gets the number of threads.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -3408,8 +3611,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
|
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
|
||||||
&& IsVppTonemappingSupported(state, encodingOptions))
|
&& IsVppTonemappingSupported(state, encodingOptions))
|
||||||
{
|
{
|
||||||
// Since tonemap_vaapi only support HEVC for now, no need to check the codec again.
|
var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
|
||||||
return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
|
var isQsvEncoder = outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (isQsvEncoder)
|
||||||
|
{
|
||||||
|
// Since tonemap_vaapi only support HEVC for now, no need to check the codec again.
|
||||||
|
return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
@ -3942,6 +4150,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (videoStream != null)
|
if (videoStream != null)
|
||||||
{
|
{
|
||||||
|
if (videoStream.BitDepth.HasValue)
|
||||||
|
{
|
||||||
|
return videoStream.BitDepth.Value == 10;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(videoStream.PixelFormat))
|
if (!string.IsNullOrEmpty(videoStream.PixelFormat))
|
||||||
{
|
{
|
||||||
result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
|
result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
|
||||||
@ -3961,12 +4174,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = (videoStream.BitDepth ?? 8) == 10;
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
23
MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs
Normal file
23
MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enum FilterOptionType.
|
||||||
|
/// </summary>
|
||||||
|
public enum FilterOptionType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The scale_cuda_format.
|
||||||
|
/// </summary>
|
||||||
|
ScaleCudaFormat = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tonemap_cuda_name.
|
||||||
|
/// </summary>
|
||||||
|
TonemapCudaName = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tonemap_opencl_bt2390.
|
||||||
|
/// </summary>
|
||||||
|
TonemapOpenclBt2390 = 2
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,6 @@ using MediaBrowser.Model.Dlna;
|
|||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.System;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.MediaEncoding
|
namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
@ -19,11 +18,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IMediaEncoder : ITranscoderSupport
|
public interface IMediaEncoder : ITranscoderSupport
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets location of the discovered FFmpeg tool.
|
|
||||||
/// </summary>
|
|
||||||
FFmpegLocation EncoderLocation { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the encoder path.
|
/// Gets the encoder path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -55,9 +49,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
/// Whether given filter is supported.
|
/// Whether given filter is supported.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filter">The filter.</param>
|
/// <param name="filter">The filter.</param>
|
||||||
|
/// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
|
||||||
|
bool SupportsFilter(string filter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether filter is supported with the given option.
|
||||||
|
/// </summary>
|
||||||
/// <param name="option">The option.</param>
|
/// <param name="option">The option.</param>
|
||||||
/// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
|
/// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
|
||||||
bool SupportsFilter(string filter, string option);
|
bool SupportsFilterWithOption(FilterOptionType option);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the version of media encoder.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The version of media encoder.</returns>
|
||||||
|
Version GetMediaEncoderVersion();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracts the audio image.
|
/// Extracts the audio image.
|
||||||
|
@ -12,8 +12,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
{
|
{
|
||||||
public class EncoderValidator
|
public class EncoderValidator
|
||||||
{
|
{
|
||||||
private const string DefaultEncoderPath = "ffmpeg";
|
|
||||||
|
|
||||||
private static readonly string[] _requiredDecoders = new[]
|
private static readonly string[] _requiredDecoders = new[]
|
||||||
{
|
{
|
||||||
"h264",
|
"h264",
|
||||||
@ -89,6 +87,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
"hevc_videotoolbox"
|
"hevc_videotoolbox"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly string[] _requiredFilters = new[]
|
||||||
|
{
|
||||||
|
"scale_cuda",
|
||||||
|
"yadif_cuda",
|
||||||
|
"hwupload_cuda",
|
||||||
|
"overlay_cuda",
|
||||||
|
"tonemap_cuda",
|
||||||
|
"tonemap_opencl",
|
||||||
|
"tonemap_vaapi",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
|
||||||
|
{
|
||||||
|
{ 0, new string[] { "scale_cuda", "Output format (default \"same\")" } },
|
||||||
|
{ 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } },
|
||||||
|
{ 2, new string[] { "tonemap_opencl", "bt2390" } }
|
||||||
|
};
|
||||||
|
|
||||||
// These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
|
// These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
|
||||||
private static readonly IReadOnlyDictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
|
private static readonly IReadOnlyDictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
|
||||||
{
|
{
|
||||||
@ -106,7 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
private readonly string _encoderPath;
|
private readonly string _encoderPath;
|
||||||
|
|
||||||
public EncoderValidator(ILogger logger, string encoderPath = DefaultEncoderPath)
|
public EncoderValidator(ILogger logger, string encoderPath)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_encoderPath = encoderPath;
|
_encoderPath = encoderPath;
|
||||||
@ -156,7 +172,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Work out what the version under test is
|
// Work out what the version under test is
|
||||||
var version = GetFFmpegVersion(versionOutput);
|
var version = GetFFmpegVersionInternal(versionOutput);
|
||||||
|
|
||||||
_logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown");
|
_logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown");
|
||||||
|
|
||||||
@ -200,6 +216,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
|
public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
|
||||||
|
|
||||||
|
public IEnumerable<string> GetFilters() => GetFFmpegFilters();
|
||||||
|
|
||||||
|
public IDictionary<int, bool> GetFiltersWithOption() => GetFFmpegFiltersWithOption();
|
||||||
|
|
||||||
|
public Version? GetFFmpegVersion()
|
||||||
|
{
|
||||||
|
string output;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
output = GetProcessOutput(_encoderPath, "-version");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error validating encoder");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(output))
|
||||||
|
{
|
||||||
|
_logger.LogError("FFmpeg validation: The process returned no result");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("ffmpeg output: {Output}", output);
|
||||||
|
|
||||||
|
return GetFFmpegVersionInternal(output);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Using the output from "ffmpeg -version" work out the FFmpeg version.
|
/// Using the output from "ffmpeg -version" work out the FFmpeg version.
|
||||||
/// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
|
/// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
|
||||||
@ -208,7 +252,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="output">The output from "ffmpeg -version".</param>
|
/// <param name="output">The output from "ffmpeg -version".</param>
|
||||||
/// <returns>The FFmpeg version.</returns>
|
/// <returns>The FFmpeg version.</returns>
|
||||||
internal Version? GetFFmpegVersion(string output)
|
internal Version? GetFFmpegVersionInternal(string output)
|
||||||
{
|
{
|
||||||
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
||||||
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
|
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
|
||||||
@ -297,9 +341,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckFilter(string filter, string option)
|
public bool CheckFilterWithOption(string filter, string option)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(filter))
|
if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -317,11 +361,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
if (output.Contains("Filter " + filter, StringComparison.Ordinal))
|
if (output.Contains("Filter " + filter, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(option))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.Contains(option, StringComparison.Ordinal);
|
return output.Contains(option, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,6 +401,49 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> GetFFmpegFilters()
|
||||||
|
{
|
||||||
|
string output;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
output = GetProcessOutput(_encoderPath, "-filters");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error detecting available filters");
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(output))
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var found = Regex
|
||||||
|
.Matches(output, @"^\s\S{3}\s(?<filter>[\w|-]+)\s+.+$", RegexOptions.Multiline)
|
||||||
|
.Cast<Match>()
|
||||||
|
.Select(x => x.Groups["filter"].Value)
|
||||||
|
.Where(x => _requiredFilters.Contains(x));
|
||||||
|
|
||||||
|
_logger.LogInformation("Available filters: {Filters}", found);
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<int, bool> GetFFmpegFiltersWithOption()
|
||||||
|
{
|
||||||
|
IDictionary<int, bool> dict = new Dictionary<int, bool>();
|
||||||
|
for (int i = 0; i < _filterOptionsDict.Count; i++)
|
||||||
|
{
|
||||||
|
if (_filterOptionsDict.TryGetValue(i, out var val) && val.Length == 2)
|
||||||
|
{
|
||||||
|
dict.Add(i, CheckFilterWithOption(val[0], val[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetProcessOutput(string path, string arguments)
|
private string GetProcessOutput(string path, string arguments)
|
||||||
{
|
{
|
||||||
using (var process = new Process()
|
using (var process = new Process()
|
||||||
|
@ -23,7 +23,6 @@ using MediaBrowser.Model.Entities;
|
|||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.System;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@ -66,10 +65,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
private List<string> _encoders = new List<string>();
|
private List<string> _encoders = new List<string>();
|
||||||
private List<string> _decoders = new List<string>();
|
private List<string> _decoders = new List<string>();
|
||||||
private List<string> _hwaccels = new List<string>();
|
private List<string> _hwaccels = new List<string>();
|
||||||
|
private List<string> _filters = new List<string>();
|
||||||
|
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
|
||||||
|
|
||||||
|
private Version _ffmpegVersion = null;
|
||||||
private string _ffmpegPath = string.Empty;
|
private string _ffmpegPath = string.Empty;
|
||||||
private string _ffprobePath;
|
private string _ffprobePath;
|
||||||
private int threads;
|
private int _threads;
|
||||||
|
|
||||||
public MediaEncoder(
|
public MediaEncoder(
|
||||||
ILogger<MediaEncoder> logger,
|
ILogger<MediaEncoder> logger,
|
||||||
@ -89,9 +91,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string EncoderPath => _ffmpegPath;
|
public string EncoderPath => _ffmpegPath;
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public FFmpegLocation EncoderLocation { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Run at startup or if the user removes a Custom path from transcode page.
|
/// Run at startup or if the user removes a Custom path from transcode page.
|
||||||
/// Sets global variables FFmpegPath.
|
/// Sets global variables FFmpegPath.
|
||||||
@ -100,20 +99,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
public void SetFFmpegPath()
|
public void SetFFmpegPath()
|
||||||
{
|
{
|
||||||
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
|
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
|
||||||
if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom))
|
var ffmpegPath = _configurationManager.GetEncodingOptions().EncoderAppPath;
|
||||||
|
if (string.IsNullOrEmpty(ffmpegPath))
|
||||||
{
|
{
|
||||||
// 2) Check if the --ffmpeg CLI switch has been given
|
// 2) Check if the --ffmpeg CLI switch has been given
|
||||||
if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
|
ffmpegPath = _startupOptionFFmpegPath;
|
||||||
|
if (string.IsNullOrEmpty(ffmpegPath))
|
||||||
{
|
{
|
||||||
// 3) Search system $PATH environment variable for valid FFmpeg
|
// 3) Check "ffmpeg"
|
||||||
if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
|
ffmpegPath = "ffmpeg";
|
||||||
{
|
|
||||||
EncoderLocation = FFmpegLocation.NotFound;
|
|
||||||
_ffmpegPath = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ValidatePath(ffmpegPath))
|
||||||
|
{
|
||||||
|
_ffmpegPath = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
|
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
|
||||||
var config = _configurationManager.GetEncodingOptions();
|
var config = _configurationManager.GetEncodingOptions();
|
||||||
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
|
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
|
||||||
@ -130,11 +132,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
SetAvailableDecoders(validator.GetDecoders());
|
SetAvailableDecoders(validator.GetDecoders());
|
||||||
SetAvailableEncoders(validator.GetEncoders());
|
SetAvailableEncoders(validator.GetEncoders());
|
||||||
|
SetAvailableFilters(validator.GetFilters());
|
||||||
|
SetAvailableFiltersWithOption(validator.GetFiltersWithOption());
|
||||||
SetAvailableHwaccels(validator.GetHwaccels());
|
SetAvailableHwaccels(validator.GetHwaccels());
|
||||||
threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
|
SetMediaEncoderVersion(validator);
|
||||||
|
|
||||||
|
_threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty);
|
_logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -153,15 +159,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
{
|
{
|
||||||
throw new ArgumentException("Unexpected pathType value");
|
throw new ArgumentException("Unexpected pathType value");
|
||||||
}
|
}
|
||||||
else if (string.IsNullOrWhiteSpace(path))
|
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
{
|
{
|
||||||
// User had cleared the custom path in UI
|
// User had cleared the custom path in UI
|
||||||
newPath = string.Empty;
|
newPath = string.Empty;
|
||||||
}
|
}
|
||||||
else if (File.Exists(path))
|
|
||||||
{
|
|
||||||
newPath = path;
|
|
||||||
}
|
|
||||||
else if (Directory.Exists(path))
|
else if (Directory.Exists(path))
|
||||||
{
|
{
|
||||||
// Given path is directory, so resolve down to filename
|
// Given path is directory, so resolve down to filename
|
||||||
@ -169,7 +172,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ResourceNotFoundException();
|
newPath = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the new ffmpeg path to the xml as <EncoderAppPath>
|
// Write the new ffmpeg path to the xml as <EncoderAppPath>
|
||||||
@ -184,37 +187,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates the supplied FQPN to ensure it is a ffmpeg utility.
|
/// Validates the supplied FQPN to ensure it is a ffmpeg utility.
|
||||||
/// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
|
/// If checks pass, global variable FFmpegPath is updated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">FQPN to test.</param>
|
/// <param name="path">FQPN to test.</param>
|
||||||
/// <param name="location">Location (External, Custom, System) of tool.</param>
|
|
||||||
/// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
|
||||||
private bool ValidatePath(string path, FFmpegLocation location)
|
private bool ValidatePath(string path)
|
||||||
{
|
{
|
||||||
bool rc = false;
|
if (string.IsNullOrEmpty(path))
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path))
|
|
||||||
{
|
{
|
||||||
if (File.Exists(path))
|
return false;
|
||||||
{
|
|
||||||
rc = new EncoderValidator(_logger, path).ValidateVersion();
|
|
||||||
|
|
||||||
if (!rc)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ffmpegPath = path;
|
|
||||||
EncoderLocation = location;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
bool rc = new EncoderValidator(_logger, path).ValidateVersion();
|
||||||
|
if (!rc)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("FFmpeg: Failed version check: {Path}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ffmpegPath = path;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false)
|
private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false)
|
||||||
@ -235,34 +227,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Search the system $PATH environment variable looking for given filename.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fileName">The filename.</param>
|
|
||||||
/// <returns>The full path to the file.</returns>
|
|
||||||
private string ExistsOnSystemPath(string fileName)
|
|
||||||
{
|
|
||||||
var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
|
|
||||||
if (!string.IsNullOrEmpty(inJellyfinPath))
|
|
||||||
{
|
|
||||||
return inJellyfinPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
var values = Environment.GetEnvironmentVariable("PATH");
|
|
||||||
|
|
||||||
foreach (var path in values.Split(Path.PathSeparator))
|
|
||||||
{
|
|
||||||
var candidatePath = GetEncoderPathFromDirectory(path, fileName);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(candidatePath))
|
|
||||||
{
|
|
||||||
return candidatePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetAvailableEncoders(IEnumerable<string> list)
|
public void SetAvailableEncoders(IEnumerable<string> list)
|
||||||
{
|
{
|
||||||
_encoders = list.ToList();
|
_encoders = list.ToList();
|
||||||
@ -278,6 +242,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
_hwaccels = list.ToList();
|
_hwaccels = list.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetAvailableFilters(IEnumerable<string> list)
|
||||||
|
{
|
||||||
|
_filters = list.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAvailableFiltersWithOption(IDictionary<int, bool> dict)
|
||||||
|
{
|
||||||
|
_filtersWithOption = dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMediaEncoderVersion(EncoderValidator validator)
|
||||||
|
{
|
||||||
|
_ffmpegVersion = validator.GetFFmpegVersion();
|
||||||
|
}
|
||||||
|
|
||||||
public bool SupportsEncoder(string encoder)
|
public bool SupportsEncoder(string encoder)
|
||||||
{
|
{
|
||||||
return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
|
return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
|
||||||
@ -293,17 +272,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
|
return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SupportsFilter(string filter, string option)
|
public bool SupportsFilter(string filter)
|
||||||
{
|
{
|
||||||
if (_ffmpegPath != null)
|
return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SupportsFilterWithOption(FilterOptionType option)
|
||||||
|
{
|
||||||
|
if (_filtersWithOption.TryGetValue((int)option, out var val))
|
||||||
{
|
{
|
||||||
var validator = new EncoderValidator(_logger, _ffmpegPath);
|
return val;
|
||||||
return validator.CheckFilter(filter, option);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Version GetMediaEncoderVersion()
|
||||||
|
{
|
||||||
|
return _ffmpegVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public bool CanEncodeToAudioCodec(string codec)
|
public bool CanEncodeToAudioCodec(string codec)
|
||||||
{
|
{
|
||||||
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
|
||||||
@ -394,7 +382,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
var args = extractChapters
|
var args = extractChapters
|
||||||
? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
|
? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
|
||||||
: "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
|
: "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
|
||||||
args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, threads).Trim();
|
args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim();
|
||||||
|
|
||||||
var process = new Process
|
var process = new Process
|
||||||
{
|
{
|
||||||
@ -503,15 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
{
|
{
|
||||||
var inputArgument = GetInputArgument(inputFile, mediaSource);
|
var inputArgument = GetInputArgument(inputFile, mediaSource);
|
||||||
|
|
||||||
if (isAudio)
|
if (!isAudio)
|
||||||
{
|
|
||||||
if (imageStreamIndex.HasValue && imageStreamIndex.Value > 0)
|
|
||||||
{
|
|
||||||
// It seems for audio files we need to subtract 1 (for the audio stream??)
|
|
||||||
imageStreamIndex = imageStreamIndex.Value - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter.
|
// The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter.
|
||||||
try
|
try
|
||||||
@ -582,7 +562,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
|
var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
|
||||||
|
|
||||||
var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
|
var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
|
||||||
if (enableHdrExtraction)
|
if (enableHdrExtraction)
|
||||||
@ -615,7 +595,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads);
|
var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
|
||||||
|
|
||||||
if (offset.HasValue)
|
if (offset.HasValue)
|
||||||
{
|
{
|
||||||
@ -728,7 +708,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
Directory.CreateDirectory(targetDirectory);
|
Directory.CreateDirectory(targetDirectory);
|
||||||
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
|
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
|
||||||
|
|
||||||
var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads);
|
var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(container))
|
if (!string.IsNullOrWhiteSpace(container))
|
||||||
{
|
{
|
||||||
|
@ -740,6 +740,23 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||||||
stream.BitDepth = streamInfo.BitsPerRawSample;
|
stream.BitDepth = streamInfo.BitsPerRawSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!stream.BitDepth.HasValue)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(streamInfo.PixelFormat)
|
||||||
|
&& streamInfo.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
stream.BitDepth = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(streamInfo.Profile)
|
||||||
|
&& (streamInfo.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| streamInfo.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| streamInfo.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
stream.BitDepth = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
|
// stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
|
||||||
// string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
|
// string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
|
||||||
// string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
|
// string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
#nullable disable
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Configuration
|
namespace MediaBrowser.Model.Configuration
|
||||||
{
|
{
|
||||||
public class MediaPathInfo
|
public class MediaPathInfo
|
||||||
{
|
{
|
||||||
|
public MediaPathInfo(string path)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
public string NetworkPath { get; set; }
|
public string? NetworkPath { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,11 @@ namespace MediaBrowser.Model.Entities
|
|||||||
attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced);
|
attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Codec))
|
||||||
|
{
|
||||||
|
attributes.Add(Codec.ToUpperInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Title))
|
if (!string.IsNullOrEmpty(Title))
|
||||||
{
|
{
|
||||||
var result = new StringBuilder(Title);
|
var result = new StringBuilder(Title);
|
||||||
|
@ -133,6 +133,7 @@ namespace MediaBrowser.Model.System
|
|||||||
[Obsolete("This should be handled by the package manager")]
|
[Obsolete("This should be handled by the package manager")]
|
||||||
public bool HasUpdateAvailable { get; set; }
|
public bool HasUpdateAvailable { get; set; }
|
||||||
|
|
||||||
|
[Obsolete("This isn't set correctly anymore")]
|
||||||
public FFmpegLocation EncoderLocation { get; set; }
|
public FFmpegLocation EncoderLocation { get; set; }
|
||||||
|
|
||||||
public Architecture SystemArchitecture { get; set; }
|
public Architecture SystemArchitecture { get; set; }
|
||||||
|
@ -88,22 +88,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
|
|
||||||
if (imageStream != null)
|
if (imageStream != null)
|
||||||
{
|
{
|
||||||
// Instead of using the raw stream index, we need to use nth video/embedded image stream
|
|
||||||
var videoIndex = -1;
|
|
||||||
foreach (var mediaStream in mediaStreams)
|
|
||||||
{
|
|
||||||
if (mediaStream.Type == MediaStreamType.Video ||
|
|
||||||
mediaStream.Type == MediaStreamType.EmbeddedImage)
|
|
||||||
{
|
|
||||||
videoIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaStream == imageStream)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaSourceInfo mediaSource = new MediaSourceInfo
|
MediaSourceInfo mediaSource = new MediaSourceInfo
|
||||||
{
|
{
|
||||||
VideoType = item.VideoType,
|
VideoType = item.VideoType,
|
||||||
@ -111,7 +95,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
Protocol = item.PathProtocol.Value,
|
Protocol = item.PathProtocol.Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, videoIndex, cancellationToken).ConfigureAwait(false);
|
extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -15,8 +15,8 @@ namespace MediaBrowser.XbmcMetadata.Configuration
|
|||||||
{
|
{
|
||||||
new ConfigurationStore
|
new ConfigurationStore
|
||||||
{
|
{
|
||||||
ConfigurationType = typeof(XbmcMetadataOptions),
|
ConfigurationType = typeof(XbmcMetadataOptions),
|
||||||
Key = "xbmcmetadata"
|
Key = "xbmcmetadata"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -779,59 +779,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
|||||||
|
|
||||||
case "thumb":
|
case "thumb":
|
||||||
{
|
{
|
||||||
var artType = reader.GetAttribute("aspect");
|
FetchThumbNode(reader, itemResult);
|
||||||
var val = reader.ReadElementContentAsString();
|
break;
|
||||||
|
}
|
||||||
// skip:
|
|
||||||
// - empty aspect tag
|
|
||||||
// - empty uri
|
|
||||||
// - tag containing '.' because we can't set images for seasons, episodes or movie sets within series or movies
|
|
||||||
if (string.IsNullOrEmpty(artType) || string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageType imageType = GetImageType(artType);
|
|
||||||
|
|
||||||
if (!Uri.TryCreate(val, UriKind.Absolute, out var uri))
|
|
||||||
{
|
|
||||||
Logger.LogError("Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, item.Name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.IsFile)
|
|
||||||
{
|
|
||||||
// only allow one item of each type
|
|
||||||
if (itemResult.Images.Any(x => x.Type == imageType))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileSystemMetadata = _directoryService.GetFile(val);
|
|
||||||
// non existing file returns null
|
|
||||||
if (fileSystemMetadata == null || !fileSystemMetadata.Exists)
|
|
||||||
{
|
|
||||||
Logger.LogWarning("Artwork file {Path} specified in nfo file for {ItemName} does not exist.", uri, item.Name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemResult.Images.Add(new LocalImageInfo()
|
|
||||||
{
|
|
||||||
FileInfo = fileSystemMetadata,
|
|
||||||
Type = imageType
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// only allow one item of each type
|
|
||||||
if (itemResult.RemoteImages.Any(x => x.type == imageType))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemResult.RemoteImages.Add((uri.ToString(), imageType));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
case "fanart":
|
||||||
|
{
|
||||||
|
var subtree = reader.ReadSubtree();
|
||||||
|
subtree.ReadToDescendant("thumb");
|
||||||
|
FetchThumbNode(subtree, itemResult);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -854,6 +810,68 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FetchThumbNode(XmlReader reader, MetadataResult<T> itemResult)
|
||||||
|
{
|
||||||
|
var artType = reader.GetAttribute("aspect");
|
||||||
|
var val = reader.ReadElementContentAsString();
|
||||||
|
|
||||||
|
// artType is null if the thumb node is a child of the fanart tag
|
||||||
|
// -> set image type to fanart
|
||||||
|
if (string.IsNullOrWhiteSpace(artType))
|
||||||
|
{
|
||||||
|
artType = "fanart";
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip:
|
||||||
|
// - empty uri
|
||||||
|
// - tag containing '.' because we can't set images for seasons, episodes or movie sets within series or movies
|
||||||
|
if (string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageType imageType = GetImageType(artType);
|
||||||
|
|
||||||
|
if (!Uri.TryCreate(val, UriKind.Absolute, out var uri))
|
||||||
|
{
|
||||||
|
Logger.LogError("Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, itemResult.Item.Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.IsFile)
|
||||||
|
{
|
||||||
|
// only allow one item of each type
|
||||||
|
if (itemResult.Images.Any(x => x.Type == imageType))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileSystemMetadata = _directoryService.GetFile(val);
|
||||||
|
// non existing file returns null
|
||||||
|
if (fileSystemMetadata == null || !fileSystemMetadata.Exists)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Artwork file {Path} specified in nfo file for {ItemName} does not exist.", uri, itemResult.Item.Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemResult.Images.Add(new LocalImageInfo()
|
||||||
|
{
|
||||||
|
FileInfo = fileSystemMetadata,
|
||||||
|
Type = imageType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// only allow one item of each type
|
||||||
|
if (itemResult.RemoteImages.Any(x => x.type == imageType))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemResult.RemoteImages.Add((uri.ToString(), imageType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void FetchFromFileInfoNode(XmlReader reader, T item)
|
private void FetchFromFileInfoNode(XmlReader reader, T item)
|
||||||
{
|
{
|
||||||
reader.MoveToContent();
|
reader.MoveToContent();
|
||||||
|
@ -12,6 +12,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
<PackageReference Include="SharpFuzz" Version="1.6.2" />
|
<PackageReference Include="SharpFuzz" Version="1.6.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using AutoFixture;
|
||||||
|
using AutoFixture.AutoMoq;
|
||||||
|
using Emby.Server.Implementations.Data;
|
||||||
using Emby.Server.Implementations.Library;
|
using Emby.Server.Implementations.Library;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using Moq;
|
||||||
using SharpFuzz;
|
using SharpFuzz;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Fuzz
|
namespace Emby.Server.Implementations.Fuzz
|
||||||
@ -11,6 +18,7 @@ namespace Emby.Server.Implementations.Fuzz
|
|||||||
switch (args[0])
|
switch (args[0])
|
||||||
{
|
{
|
||||||
case "PathExtensions.TryReplaceSubPath": Run(PathExtensions_TryReplaceSubPath); return;
|
case "PathExtensions.TryReplaceSubPath": Run(PathExtensions_TryReplaceSubPath); return;
|
||||||
|
case "SqliteItemRepository.ItemImageInfoFromValueString": Run(SqliteItemRepository_ItemImageInfoFromValueString); return;
|
||||||
default: throw new ArgumentException($"Unknown fuzzing function: {args[0]}");
|
default: throw new ArgumentException($"Unknown fuzzing function: {args[0]}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,5 +36,27 @@ namespace Emby.Server.Implementations.Fuzz
|
|||||||
|
|
||||||
_ = PathExtensions.TryReplaceSubPath(parts[0], parts[1], parts[2], out _);
|
_ = PathExtensions.TryReplaceSubPath(parts[0], parts[1], parts[2], out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SqliteItemRepository_ItemImageInfoFromValueString(string data)
|
||||||
|
{
|
||||||
|
var sqliteItemRepository = MockSqliteItemRepository();
|
||||||
|
sqliteItemRepository.ItemImageInfoFromValueString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SqliteItemRepository MockSqliteItemRepository()
|
||||||
|
{
|
||||||
|
const string VirtualMetaDataPath = "%MetadataPath%";
|
||||||
|
const string MetaDataPath = "/meta/data/path";
|
||||||
|
|
||||||
|
var appHost = new Mock<IServerApplicationHost>();
|
||||||
|
appHost.Setup(x => x.ExpandVirtualPath(It.IsAny<string>()))
|
||||||
|
.Returns((string x) => x.Replace(VirtualMetaDataPath, MetaDataPath, StringComparison.Ordinal));
|
||||||
|
appHost.Setup(x => x.ReverseVirtualPath(It.IsAny<string>()))
|
||||||
|
.Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
IFixture fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
|
||||||
|
fixture.Inject(appHost);
|
||||||
|
return fixture.Create<SqliteItemRepository>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN
|
@ -16,7 +16,7 @@
|
|||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
||||||
<PackageReference Include="FsCheck.Xunit" Version="2.16.0" />
|
<PackageReference Include="FsCheck.Xunit" Version="2.16.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers -->
|
<!-- Code Analyzers -->
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="FsCheck.Xunit" Version="2.16.0" />
|
<PackageReference Include="FsCheck.Xunit" Version="2.16.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers -->
|
<!-- Code Analyzers -->
|
||||||
|
@ -9,15 +9,18 @@ namespace Jellyfin.MediaEncoding.Tests
|
|||||||
{
|
{
|
||||||
public class EncoderValidatorTests
|
public class EncoderValidatorTests
|
||||||
{
|
{
|
||||||
|
private readonly EncoderValidator _encoderValidator = new EncoderValidator(new NullLogger<EncoderValidatorTests>(), "ffmpeg");
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[ClassData(typeof(GetFFmpegVersionTestData))]
|
[ClassData(typeof(GetFFmpegVersionTestData))]
|
||||||
public void GetFFmpegVersionTest(string versionOutput, Version? version)
|
public void GetFFmpegVersionTest(string versionOutput, Version? version)
|
||||||
{
|
{
|
||||||
var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
|
Assert.Equal(version, _encoderValidator.GetFFmpegVersionInternal(versionOutput));
|
||||||
Assert.Equal(version, val.GetFFmpegVersion(versionOutput));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
[InlineData(EncoderValidatorTestsData.FFmpegV44Output, true)]
|
||||||
|
[InlineData(EncoderValidatorTestsData.FFmpegV432Output, true)]
|
||||||
[InlineData(EncoderValidatorTestsData.FFmpegV431Output, true)]
|
[InlineData(EncoderValidatorTestsData.FFmpegV431Output, true)]
|
||||||
[InlineData(EncoderValidatorTestsData.FFmpegV43Output, true)]
|
[InlineData(EncoderValidatorTestsData.FFmpegV43Output, true)]
|
||||||
[InlineData(EncoderValidatorTestsData.FFmpegV421Output, true)]
|
[InlineData(EncoderValidatorTestsData.FFmpegV421Output, true)]
|
||||||
@ -28,14 +31,15 @@ namespace Jellyfin.MediaEncoding.Tests
|
|||||||
[InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, false)]
|
[InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, false)]
|
||||||
public void ValidateVersionInternalTest(string versionOutput, bool valid)
|
public void ValidateVersionInternalTest(string versionOutput, bool valid)
|
||||||
{
|
{
|
||||||
var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
|
Assert.Equal(valid, _encoderValidator.ValidateVersionInternal(versionOutput));
|
||||||
Assert.Equal(valid, val.ValidateVersionInternal(versionOutput));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GetFFmpegVersionTestData : IEnumerable<object?[]>
|
private class GetFFmpegVersionTestData : IEnumerable<object?[]>
|
||||||
{
|
{
|
||||||
public IEnumerator<object?[]> GetEnumerator()
|
public IEnumerator<object?[]> GetEnumerator()
|
||||||
{
|
{
|
||||||
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegV44Output, new Version(4, 4) };
|
||||||
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegV432Output, new Version(4, 3, 2) };
|
||||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV431Output, new Version(4, 3, 1) };
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegV431Output, new Version(4, 3, 1) };
|
||||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3) };
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3) };
|
||||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
|
||||||
|
@ -2,6 +2,30 @@ namespace Jellyfin.MediaEncoding.Tests
|
|||||||
{
|
{
|
||||||
internal static class EncoderValidatorTestsData
|
internal static class EncoderValidatorTestsData
|
||||||
{
|
{
|
||||||
|
public const string FFmpegV44Output = @"ffmpeg version 4.4-Jellyfin Copyright (c) 2000-2021 the FFmpeg developers
|
||||||
|
built with gcc 10.3.0 (Rev5, Built by MSYS2 project)
|
||||||
|
configuration: --disable-static --enable-shared --extra-version=Jellyfin --disable-ffplay --disable-debug --enable-gpl --enable-version3 --enable-bzlib --enable-iconv --enable-lzma --enable-zlib --enable-sdl2 --enable-fontconfig --enable-gmp --enable-libass --enable-libzimg --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libvpx --enable-libx264 --enable-libx265 --enable-libdav1d --enable-opencl --enable-dxva2 --enable-d3d11va --enable-amf --enable-libmfx --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvenc --enable-nvdec --enable-ffnvcodec --enable-gnutls
|
||||||
|
libavutil 56. 70.100 / 56. 70.100
|
||||||
|
libavcodec 58.134.100 / 58.134.100
|
||||||
|
libavformat 58. 76.100 / 58. 76.100
|
||||||
|
libavdevice 58. 13.100 / 58. 13.100
|
||||||
|
libavfilter 7.110.100 / 7.110.100
|
||||||
|
libswscale 5. 9.100 / 5. 9.100
|
||||||
|
libswresample 3. 9.100 / 3. 9.100
|
||||||
|
libpostproc 55. 9.100 / 55. 9.100";
|
||||||
|
|
||||||
|
public const string FFmpegV432Output = @"ffmpeg version n4.3.2-Jellyfin Copyright (c) 2000-2021 the FFmpeg developers
|
||||||
|
built with gcc 10.2.0 (Rev9, Built by MSYS2 project)
|
||||||
|
configuration: --disable-static --enable-shared --cc='ccache gcc' --cxx='ccache g++' --extra-version=Jellyfin --disable-ffplay --disable-debug --enable-lto --enable-gpl --enable-version3 --enable-bzlib --enable-iconv --enable-lzma --enable-zlib --enable-sdl2 --enable-fontconfig --enable-gmp --enable-libass --enable-libzimg --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libvpx --enable-libx264 --enable-libx265 --enable-libdav1d --enable-opencl --enable-dxva2 --enable-d3d11va --enable-amf --enable-libmfx --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvenc --enable-nvdec --enable-ffnvcodec --enable-gnutls
|
||||||
|
libavutil 56. 51.100 / 56. 51.100
|
||||||
|
libavcodec 58. 91.100 / 58. 91.100
|
||||||
|
libavformat 58. 45.100 / 58. 45.100
|
||||||
|
libavdevice 58. 10.100 / 58. 10.100
|
||||||
|
libavfilter 7. 85.100 / 7. 85.100
|
||||||
|
libswscale 5. 7.100 / 5. 7.100
|
||||||
|
libswresample 3. 7.100 / 3. 7.100
|
||||||
|
libpostproc 55. 7.100 / 55. 7.100";
|
||||||
|
|
||||||
public const string FFmpegV431Output = @"ffmpeg version n4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
|
public const string FFmpegV431Output = @"ffmpeg version n4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
|
||||||
built with gcc 10.1.0 (GCC)
|
built with gcc 10.1.0 (GCC)
|
||||||
configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-avisynth --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librav1e --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-nvdec --enable-nvenc --enable-omx --enable-shared --enable-version3
|
configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-avisynth --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librav1e --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-nvdec --enable-nvenc --enable-omx --enable-shared --enable-version3
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@ -5,6 +6,85 @@ namespace Jellyfin.Model.Tests.Entities
|
|||||||
{
|
{
|
||||||
public class MediaStreamTests
|
public class MediaStreamTests
|
||||||
{
|
{
|
||||||
|
public static IEnumerable<object[]> Get_DisplayTitle_TestData()
|
||||||
|
{
|
||||||
|
return new List<object[]>
|
||||||
|
{
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new MediaStream
|
||||||
|
{
|
||||||
|
Type = MediaStreamType.Subtitle,
|
||||||
|
Title = "English",
|
||||||
|
Language = string.Empty,
|
||||||
|
IsForced = false,
|
||||||
|
IsDefault = false,
|
||||||
|
Codec = "ASS"
|
||||||
|
},
|
||||||
|
"English - Und - ASS"
|
||||||
|
},
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new MediaStream
|
||||||
|
{
|
||||||
|
Type = MediaStreamType.Subtitle,
|
||||||
|
Title = "English",
|
||||||
|
Language = string.Empty,
|
||||||
|
IsForced = false,
|
||||||
|
IsDefault = false,
|
||||||
|
Codec = string.Empty
|
||||||
|
},
|
||||||
|
"English - Und"
|
||||||
|
},
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new MediaStream
|
||||||
|
{
|
||||||
|
Type = MediaStreamType.Subtitle,
|
||||||
|
Title = "English",
|
||||||
|
Language = "EN",
|
||||||
|
IsForced = false,
|
||||||
|
IsDefault = false,
|
||||||
|
Codec = string.Empty
|
||||||
|
},
|
||||||
|
"English"
|
||||||
|
},
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new MediaStream
|
||||||
|
{
|
||||||
|
Type = MediaStreamType.Subtitle,
|
||||||
|
Title = "English",
|
||||||
|
Language = "EN",
|
||||||
|
IsForced = true,
|
||||||
|
IsDefault = true,
|
||||||
|
Codec = "SRT"
|
||||||
|
},
|
||||||
|
"English - Default - Forced - SRT"
|
||||||
|
},
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new MediaStream
|
||||||
|
{
|
||||||
|
Type = MediaStreamType.Subtitle,
|
||||||
|
Title = null,
|
||||||
|
Language = null,
|
||||||
|
IsForced = false,
|
||||||
|
IsDefault = false,
|
||||||
|
Codec = null
|
||||||
|
},
|
||||||
|
"Und"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Get_DisplayTitle_TestData))]
|
||||||
|
public void Get_DisplayTitle_should_return_valid_title(MediaStream mediaStream, string expected)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, mediaStream.DisplayTitle);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(null, null, false, null)]
|
[InlineData(null, null, false, null)]
|
||||||
[InlineData(null, 0, false, null)]
|
[InlineData(null, 0, false, null)]
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
||||||
<PackageReference Include="FsCheck.Xunit" Version="2.16.0" />
|
<PackageReference Include="FsCheck.Xunit" Version="2.16.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers -->
|
<!-- Code Analyzers -->
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
||||||
<PackageReference Include="FsCheck.Xunit" Version="2.16.0" />
|
<PackageReference Include="FsCheck.Xunit" Version="2.16.1" />
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -109,6 +109,9 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
|||||||
[InlineData("")]
|
[InlineData("")]
|
||||||
[InlineData("*")]
|
[InlineData("*")]
|
||||||
[InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")]
|
[InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")]
|
||||||
|
[InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*6374520964785129080*WjQbtJtSO8nhNZ%L_Io#R/oaS<o}-;adXAoIn7j[%hW9s:WGw[nN")] // Invalid modified date
|
||||||
|
[InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*-637452096478512963*WjQbtJtSO8nhNZ%L_Io#R/oaS<o}-;adXAoIn7j[%hW9s:WGw[nN")] // Negative modified date
|
||||||
|
[InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Invalid*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN")] // Invalid type
|
||||||
public void ItemImageInfoFromValueString_Invalid_Null(string value)
|
public void ItemImageInfoFromValueString_Invalid_Null(string value)
|
||||||
{
|
{
|
||||||
Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
|
Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Models.LibraryStructureDto;
|
||||||
|
using Jellyfin.Extensions.Json;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Integration.Tests.Controllers
|
||||||
|
{
|
||||||
|
public sealed class MediaStructureControllerTests : IClassFixture<JellyfinApplicationFactory>
|
||||||
|
{
|
||||||
|
private readonly JellyfinApplicationFactory _factory;
|
||||||
|
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
||||||
|
private static string? _accessToken;
|
||||||
|
|
||||||
|
public MediaStructureControllerTests(JellyfinApplicationFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenameVirtualFolder_WhiteSpaceName_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
|
||||||
|
|
||||||
|
using var postContent = new ByteArrayContent(Array.Empty<byte>());
|
||||||
|
var response = await client.PostAsync("Library/VirtualFolders/Name?name=+&newName=test", postContent).ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenameVirtualFolder_WhiteSpaceNewName_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
|
||||||
|
|
||||||
|
using var postContent = new ByteArrayContent(Array.Empty<byte>());
|
||||||
|
var response = await client.PostAsync("Library/VirtualFolders/Name?name=test&newName=+", postContent).ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenameVirtualFolder_NameDoesntExist_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
|
||||||
|
|
||||||
|
using var postContent = new ByteArrayContent(Array.Empty<byte>());
|
||||||
|
var response = await client.PostAsync("Library/VirtualFolders/Name?name=doesnt+exist&newName=test", postContent).ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddMediaPath_PathDoesntExist_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
|
||||||
|
|
||||||
|
var data = new MediaPathDto()
|
||||||
|
{
|
||||||
|
Name = "Test",
|
||||||
|
Path = "/this/path/doesnt/exist"
|
||||||
|
};
|
||||||
|
|
||||||
|
using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions));
|
||||||
|
postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
|
||||||
|
var response = await client.PostAsync("Library/VirtualFolders/Paths", postContent).ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateMediaPath_WhiteSpaceName_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
|
||||||
|
|
||||||
|
var data = new UpdateMediaPathRequestDto()
|
||||||
|
{
|
||||||
|
Name = " ",
|
||||||
|
PathInfo = new MediaPathInfo("test")
|
||||||
|
};
|
||||||
|
|
||||||
|
using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions));
|
||||||
|
postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
|
||||||
|
var response = await client.PostAsync("Library/VirtualFolders/Paths/Update", postContent).ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RemoveMediaPath_WhiteSpaceName_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
|
||||||
|
|
||||||
|
var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=+").ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RemoveMediaPath_PathDoesntExist_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
|
||||||
|
|
||||||
|
var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=none&path=%2Fthis%2Fpath%2Fdoesnt%2Fexist").ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -207,6 +207,20 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
|
|||||||
Assert.Equal(id, item.ProviderIds[provider]);
|
Assert.Equal(id, item.ProviderIds[provider]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_GivenFileWithFanartTag_Success()
|
||||||
|
{
|
||||||
|
var result = new MetadataResult<Video>()
|
||||||
|
{
|
||||||
|
Item = new Movie()
|
||||||
|
};
|
||||||
|
|
||||||
|
_parser.Fetch(result, "Test Data/Fanart.nfo", CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Single(result.RemoteImages.Where(x => x.type == ImageType.Backdrop));
|
||||||
|
Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg", result.RemoteImages.First(x => x.type == ImageType.Backdrop).url);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Parse_RadarrUrlFile_Success()
|
public void Parse_RadarrUrlFile_Success()
|
||||||
{
|
{
|
||||||
|
33
tests/Jellyfin.XbmcMetadata.Tests/Test Data/Fanart.nfo
Normal file
33
tests/Jellyfin.XbmcMetadata.Tests/Test Data/Fanart.nfo
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||||
|
<movie>
|
||||||
|
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png</thumb>
|
||||||
|
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png</thumb>
|
||||||
|
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-57b476a831d74.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57b476a831d74.png</thumb>
|
||||||
|
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-57947e28cf10b.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57947e28cf10b.png</thumb>
|
||||||
|
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5863d5c0cf0c9.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5863d5c0cf0c9.png</thumb>
|
||||||
|
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5a801747e5545.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5a801747e5545.png</thumb>
|
||||||
|
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5cd75683df92b.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5cd75683df92b.png</thumb>
|
||||||
|
<thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-586017e95adbd.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg</thumb>
|
||||||
|
<thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-5934d45bc6592.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-5934d45bc6592.jpg</thumb>
|
||||||
|
<thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-5aa9289a379fa.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-5aa9289a379fa.jpg</thumb>
|
||||||
|
<thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-585fb155c3743.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg</thumb>
|
||||||
|
<thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-585edbda91d82.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585edbda91d82.jpg</thumb>
|
||||||
|
<thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-5b86588882c12.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-5b86588882c12.jpg</thumb>
|
||||||
|
<thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-5bbb9babe600c.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-5bbb9babe600c.jpg</thumb>
|
||||||
|
<thumb aspect="clearart" preview="https://assets.fanart.tv/preview/movies/141052/hdmovieclearart/justice-league-5865c23193041.png">https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png</thumb>
|
||||||
|
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a3af26360617.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png</thumb>
|
||||||
|
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-58690967b9765.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-58690967b9765.png</thumb>
|
||||||
|
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a953ca4db6a6.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a953ca4db6a6.png</thumb>
|
||||||
|
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a0b913c233be.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a0b913c233be.png</thumb>
|
||||||
|
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png</thumb>
|
||||||
|
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-59dc595362ef1.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-59dc595362ef1.png</thumb>
|
||||||
|
<fanart>
|
||||||
|
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg</thumb>
|
||||||
|
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg</thumb>
|
||||||
|
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg</thumb>
|
||||||
|
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-58fa1f1932897.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-58fa1f1932897.jpg</thumb>
|
||||||
|
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a14f5fd8dd16.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a14f5fd8dd16.jpg</thumb>
|
||||||
|
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a119394ea362.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a119394ea362.jpg</thumb>
|
||||||
|
</fanart>
|
||||||
|
<thumb aspect="fanart">This-should-not-be-saved-as-a-fanart-image.jpg</thumb>
|
||||||
|
</movie>
|
Loading…
x
Reference in New Issue
Block a user