mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-23 17:16:57 +01:00
Merge remote-tracking branch 'upstream/master' into epg-fixes
This commit is contained in:
@@ -24,61 +24,29 @@ public class SkiaEncoder : IImageEncoder
|
||||
private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
|
||||
private readonly ILogger<SkiaEncoder> _logger;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private static readonly SKImageFilter _imageFilter;
|
||||
private static readonly SKTypeface[] _typefaces;
|
||||
private static readonly SKTypeface?[] _typefaces = InitializeTypefaces();
|
||||
private static readonly SKImageFilter _imageFilter = SKImageFilter.CreateMatrixConvolution(
|
||||
new SKSizeI(3, 3),
|
||||
[
|
||||
0, -.1f, 0,
|
||||
-.1f, 1.4f, -.1f,
|
||||
0, -.1f, 0
|
||||
],
|
||||
1f,
|
||||
0f,
|
||||
new SKPointI(1, 1),
|
||||
SKShaderTileMode.Clamp,
|
||||
true);
|
||||
|
||||
/// <summary>
|
||||
/// The default sampling options, equivalent to old high quality filter settings when upscaling.
|
||||
/// </summary>
|
||||
public static readonly SKSamplingOptions UpscaleSamplingOptions;
|
||||
public static readonly SKSamplingOptions UpscaleSamplingOptions = new SKSamplingOptions(SKCubicResampler.Mitchell);
|
||||
|
||||
/// <summary>
|
||||
/// The sampling options, used for downscaling images, equivalent to old high quality filter settings when not upscaling.
|
||||
/// </summary>
|
||||
public static readonly SKSamplingOptions DefaultSamplingOptions;
|
||||
|
||||
#pragma warning disable CA1810
|
||||
static SkiaEncoder()
|
||||
#pragma warning restore CA1810
|
||||
{
|
||||
var kernel = new[]
|
||||
{
|
||||
0, -.1f, 0,
|
||||
-.1f, 1.4f, -.1f,
|
||||
0, -.1f, 0,
|
||||
};
|
||||
|
||||
var kernelSize = new SKSizeI(3, 3);
|
||||
var kernelOffset = new SKPointI(1, 1);
|
||||
_imageFilter = SKImageFilter.CreateMatrixConvolution(
|
||||
kernelSize,
|
||||
kernel,
|
||||
1f,
|
||||
0f,
|
||||
kernelOffset,
|
||||
SKShaderTileMode.Clamp,
|
||||
true);
|
||||
|
||||
// Initialize the list of typefaces
|
||||
// We have to statically build a list of typefaces because MatchCharacter only accepts a single character or code point
|
||||
// But in reality a human-readable character (grapheme cluster) could be multiple code points. For example, 🚵🏻♀️ is a single emoji but 5 code points (U+1F6B5 + U+1F3FB + U+200D + U+2640 + U+FE0F)
|
||||
_typefaces =
|
||||
[
|
||||
SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, '鸡'), // CJK Simplified Chinese
|
||||
SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, '雞'), // CJK Traditional Chinese
|
||||
SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ノ'), // CJK Japanese
|
||||
SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, '각'), // CJK Korean
|
||||
SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 128169), // Emojis, 128169 is the 💩emoji
|
||||
SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ז'), // Hebrew
|
||||
SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ي'), // Arabic
|
||||
SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright) // Default font
|
||||
];
|
||||
|
||||
// use cubic for upscaling
|
||||
UpscaleSamplingOptions = new SKSamplingOptions(SKCubicResampler.Mitchell);
|
||||
// use bilinear for everything else
|
||||
DefaultSamplingOptions = new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear);
|
||||
}
|
||||
public static readonly SKSamplingOptions DefaultSamplingOptions = new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkiaEncoder"/> class.
|
||||
@@ -132,7 +100,7 @@ public class SkiaEncoder : IImageEncoder
|
||||
/// <summary>
|
||||
/// Gets the default typeface to use.
|
||||
/// </summary>
|
||||
public static SKTypeface DefaultTypeFace => _typefaces.Last();
|
||||
public static SKTypeface? DefaultTypeFace => _typefaces.Last();
|
||||
|
||||
/// <summary>
|
||||
/// Check if the native lib is available.
|
||||
@@ -152,6 +120,40 @@ public class SkiaEncoder : IImageEncoder
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the list of typefaces
|
||||
/// We have to statically build a list of typefaces because MatchCharacter only accepts a single character or code point
|
||||
/// But in reality a human-readable character (grapheme cluster) could be multiple code points. For example, 🚵🏻♀️ is a single emoji but 5 code points (U+1F6B5 + U+1F3FB + U+200D + U+2640 + U+FE0F).
|
||||
/// </summary>
|
||||
/// <returns>The list of typefaces.</returns>
|
||||
private static SKTypeface?[] InitializeTypefaces()
|
||||
{
|
||||
int[] chars = [
|
||||
'鸡', // CJK Simplified Chinese
|
||||
'雞', // CJK Traditional Chinese
|
||||
'ノ', // CJK Japanese
|
||||
'각', // CJK Korean
|
||||
128169, // Emojis, 128169 is the Pile of Poo (💩) emoji
|
||||
'ז', // Hebrew
|
||||
'ي' // Arabic
|
||||
];
|
||||
var fonts = new List<SKTypeface>(chars.Length + 1);
|
||||
foreach (var ch in chars)
|
||||
{
|
||||
var font = SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, ch);
|
||||
if (font is not null)
|
||||
{
|
||||
fonts.Add(font);
|
||||
}
|
||||
}
|
||||
|
||||
// Default font
|
||||
fonts.Add(SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright)
|
||||
?? SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'a'));
|
||||
|
||||
return fonts.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a <see cref="ImageFormat"/> to a <see cref="SKEncodedImageFormat"/>.
|
||||
/// </summary>
|
||||
@@ -809,7 +811,7 @@ public class SkiaEncoder : IImageEncoder
|
||||
{
|
||||
foreach (var typeface in _typefaces)
|
||||
{
|
||||
if (typeface.ContainsGlyphs(c))
|
||||
if (typeface is not null && typeface.ContainsGlyphs(c))
|
||||
{
|
||||
return typeface;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
"aiff",
|
||||
"cr2",
|
||||
"crw",
|
||||
"nef",
|
||||
|
||||
@@ -156,6 +156,13 @@ namespace Jellyfin.LiveTv.IO
|
||||
if (mediaSource.ReadAtNativeFramerate)
|
||||
{
|
||||
inputModifier += " -re";
|
||||
|
||||
// Set a larger catchup value to revert to the old behavior,
|
||||
// otherwise, remuxing might stall due to this new option
|
||||
if (_mediaEncoder.EncoderVersion >= new Version(8, 0))
|
||||
{
|
||||
inputModifier += " -readrate_catchup 100";
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaSource.RequiresLooping)
|
||||
|
||||
@@ -93,6 +93,13 @@ namespace Jellyfin.LiveTv.TunerHosts
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
|
||||
{
|
||||
if (!IsValidChannelUrl(trimmedLine))
|
||||
{
|
||||
_logger.LogWarning("Skipping M3U channel entry with non-HTTP path: {Path}", trimmedLine);
|
||||
extInf = string.Empty;
|
||||
continue;
|
||||
}
|
||||
|
||||
var channel = GetChannelInfo(extInf, tunerHostId, trimmedLine);
|
||||
channel.Id = channelIdPrefix + trimmedLine.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
@@ -247,6 +254,16 @@ namespace Jellyfin.LiveTv.TunerHosts
|
||||
return numberString;
|
||||
}
|
||||
|
||||
private static bool IsValidChannelUrl(string url)
|
||||
{
|
||||
return Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
&& (string.Equals(uri.Scheme, "http", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(uri.Scheme, "rtsp", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(uri.Scheme, "rtp", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(uri.Scheme, "udp", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static bool IsValidChannelNumber(string numberString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(numberString)
|
||||
|
||||
@@ -747,12 +747,13 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
|
||||
{
|
||||
return NetworkManager.GetAllBindInterfaces(individualInterfaces, _configurationManager, _interfaces, IsIPv4Enabled, IsIPv6Enabled);
|
||||
return NetworkManager.GetAllBindInterfaces(_logger, individualInterfaces, _configurationManager, _interfaces, IsIPv4Enabled, IsIPv6Enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the jellyfin configuration of the configuration manager and produces a list of interfaces that should be bound.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger to use for messages.</param>
|
||||
/// <param name="individualInterfaces">Defines that only known interfaces should be used.</param>
|
||||
/// <param name="configurationManager">The ConfigurationManager.</param>
|
||||
/// <param name="knownInterfaces">The known interfaces that gets returned if possible or instructed.</param>
|
||||
@@ -760,6 +761,7 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
/// <param name="readIpv6">Include IPV6 type interfaces.</param>
|
||||
/// <returns>A list of ip address of which jellyfin should bind to.</returns>
|
||||
public static IReadOnlyList<IPData> GetAllBindInterfaces(
|
||||
ILogger<NetworkManager> logger,
|
||||
bool individualInterfaces,
|
||||
IConfigurationManager configurationManager,
|
||||
IReadOnlyList<IPData> knownInterfaces,
|
||||
@@ -773,6 +775,13 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
return knownInterfaces;
|
||||
}
|
||||
|
||||
// TODO: remove when upgrade to dotnet 11 is done
|
||||
if (readIpv6 && !Socket.OSSupportsIPv6)
|
||||
{
|
||||
logger.LogWarning("IPv6 Unsupported by OS, not listening on IPv6");
|
||||
readIpv6 = false;
|
||||
}
|
||||
|
||||
// No bind address and no exclusions, so listen on all interfaces.
|
||||
var result = new List<IPData>();
|
||||
if (readIpv4 && readIpv6)
|
||||
@@ -869,7 +878,20 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
if (availableInterfaces.Count == 0)
|
||||
{
|
||||
// There isn't any others, so we'll use the loopback.
|
||||
result = IsIPv4Enabled && !IsIPv6Enabled ? "127.0.0.1" : "::1";
|
||||
// Prefer loopback address matching the source's address family
|
||||
if (source is not null && source.AddressFamily == AddressFamily.InterNetwork && IsIPv4Enabled)
|
||||
{
|
||||
result = "127.0.0.1";
|
||||
}
|
||||
else if (source is not null && source.AddressFamily == AddressFamily.InterNetworkV6 && IsIPv6Enabled)
|
||||
{
|
||||
result = "::1";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = IsIPv4Enabled ? "127.0.0.1" : "::1";
|
||||
}
|
||||
|
||||
_logger.LogWarning("{Source}: Only loopback {Result} returned, using that as bind address.", source, result);
|
||||
return result;
|
||||
}
|
||||
@@ -894,9 +916,19 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to first available interface
|
||||
// Fallback to an interface matching the source's address family, or first available
|
||||
var preferredInterface = availableInterfaces
|
||||
.FirstOrDefault(x => x.Address.AddressFamily == source.AddressFamily);
|
||||
|
||||
if (preferredInterface is not null)
|
||||
{
|
||||
result = NetworkUtils.FormatIPString(preferredInterface.Address);
|
||||
_logger.LogDebug("{Source}: No matching subnet found, using interface with matching address family: {Result}", source, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = NetworkUtils.FormatIPString(availableInterfaces[0].Address);
|
||||
_logger.LogDebug("{Source}: No matching interfaces found, using preferred interface as bind address: {Result}", source, result);
|
||||
_logger.LogDebug("{Source}: No matching interfaces found, using first available interface as bind address: {Result}", source, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user