Merge branch 'master' into gzip
This commit is contained in:
commit
aadd8ee971
|
@ -221,6 +221,7 @@ namespace Emby.Dlna.Didl
|
||||||
streamInfo.IsDirectStream,
|
streamInfo.IsDirectStream,
|
||||||
streamInfo.RunTimeTicks ?? 0,
|
streamInfo.RunTimeTicks ?? 0,
|
||||||
streamInfo.TargetVideoProfile,
|
streamInfo.TargetVideoProfile,
|
||||||
|
streamInfo.TargetVideoRangeType,
|
||||||
streamInfo.TargetVideoLevel,
|
streamInfo.TargetVideoLevel,
|
||||||
streamInfo.TargetFramerate ?? 0,
|
streamInfo.TargetFramerate ?? 0,
|
||||||
streamInfo.TargetPacketLength,
|
streamInfo.TargetPacketLength,
|
||||||
|
@ -376,6 +377,7 @@ namespace Emby.Dlna.Didl
|
||||||
targetHeight,
|
targetHeight,
|
||||||
streamInfo.TargetVideoBitDepth,
|
streamInfo.TargetVideoBitDepth,
|
||||||
streamInfo.TargetVideoProfile,
|
streamInfo.TargetVideoProfile,
|
||||||
|
streamInfo.TargetVideoRangeType,
|
||||||
streamInfo.TargetVideoLevel,
|
streamInfo.TargetVideoLevel,
|
||||||
streamInfo.TargetFramerate ?? 0,
|
streamInfo.TargetFramerate ?? 0,
|
||||||
streamInfo.TargetPacketLength,
|
streamInfo.TargetPacketLength,
|
||||||
|
|
|
@ -313,7 +313,7 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, address);
|
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, address);
|
||||||
|
|
||||||
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(false) + descriptorUri);
|
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(address, false) + descriptorUri);
|
||||||
|
|
||||||
var device = new SsdpRootDevice
|
var device = new SsdpRootDevice
|
||||||
{
|
{
|
||||||
|
|
|
@ -561,6 +561,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
streamInfo.IsDirectStream,
|
streamInfo.IsDirectStream,
|
||||||
streamInfo.RunTimeTicks ?? 0,
|
streamInfo.RunTimeTicks ?? 0,
|
||||||
streamInfo.TargetVideoProfile,
|
streamInfo.TargetVideoProfile,
|
||||||
|
streamInfo.TargetVideoRangeType,
|
||||||
streamInfo.TargetVideoLevel,
|
streamInfo.TargetVideoLevel,
|
||||||
streamInfo.TargetFramerate ?? 0,
|
streamInfo.TargetFramerate ?? 0,
|
||||||
streamInfo.TargetPacketLength,
|
streamInfo.TargetPacketLength,
|
||||||
|
|
|
@ -395,7 +395,13 @@ namespace Emby.Drawing
|
||||||
public string GetImageBlurHash(string path)
|
public string GetImageBlurHash(string path)
|
||||||
{
|
{
|
||||||
var size = GetImageDimensions(path);
|
var size = GetImageDimensions(path);
|
||||||
if (size.Width <= 0 || size.Height <= 0)
|
return GetImageBlurHash(path, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string GetImageBlurHash(string path, ImageDimensions imageDimensions)
|
||||||
|
{
|
||||||
|
if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
@ -403,8 +409,8 @@ namespace Emby.Drawing
|
||||||
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
|
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
|
||||||
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
|
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
|
||||||
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
|
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
|
||||||
float xCompF = MathF.Sqrt(16.0f * size.Width / size.Height);
|
float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height);
|
||||||
float yCompF = xCompF * size.Height / size.Width;
|
float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width;
|
||||||
|
|
||||||
int xComp = Math.Min((int)xCompF + 1, 9);
|
int xComp = Math.Min((int)xCompF + 1, 9);
|
||||||
int yComp = Math.Min((int)yCompF + 1, 9);
|
int yComp = Math.Min((int)yCompF + 1, 9);
|
||||||
|
@ -439,47 +445,46 @@ namespace Emby.Drawing
|
||||||
.ToString("N", CultureInfo.InvariantCulture);
|
.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||||
{
|
{
|
||||||
var inputFormat = Path.GetExtension(originalImagePath)
|
var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString();
|
||||||
.TrimStart('.')
|
|
||||||
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// These are just jpg files renamed as tbn
|
// These are just jpg files renamed as tbn
|
||||||
if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return (originalImagePath, dateModified);
|
return Task.FromResult((originalImagePath, dateModified));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
|
// TODO _mediaEncoder.ConvertImage is not implemented
|
||||||
{
|
// if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
|
||||||
try
|
// {
|
||||||
{
|
// try
|
||||||
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
// {
|
||||||
|
// string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
//
|
||||||
|
// string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||||
|
// var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||||
|
//
|
||||||
|
// var file = _fileSystem.GetFileInfo(outputPath);
|
||||||
|
// if (!file.Exists)
|
||||||
|
// {
|
||||||
|
// await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
||||||
|
// dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// dateModified = file.LastWriteTimeUtc;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// originalImagePath = outputPath;
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
|
return Task.FromResult((originalImagePath, dateModified));
|
||||||
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
|
||||||
|
|
||||||
var file = _fileSystem.GetFileInfo(outputPath);
|
|
||||||
if (!file.Exists)
|
|
||||||
{
|
|
||||||
await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
|
||||||
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dateModified = file.LastWriteTimeUtc;
|
|
||||||
}
|
|
||||||
|
|
||||||
originalImagePath = outputPath;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (originalImagePath, dateModified);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1111,13 +1111,13 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetApiUrlForLocalAccess(bool allowHttps = true)
|
public string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true)
|
||||||
{
|
{
|
||||||
// With an empty source, the port will be null
|
// With an empty source, the port will be null
|
||||||
string smart = NetManager.GetBindInterface(string.Empty, out _);
|
var smart = NetManager.GetBindInterface(hostname ?? IPHost.None, out _);
|
||||||
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
|
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
|
||||||
int? port = !allowHttps ? HttpPort : null;
|
int? port = !allowHttps ? HttpPort : null;
|
||||||
return GetLocalApiUrl(smart.Trim('/'), scheme, port);
|
return GetLocalApiUrl(smart, scheme, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -1131,11 +1131,13 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
|
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
|
||||||
// not. For consistency, always trim the trailing slash.
|
// not. For consistency, always trim the trailing slash.
|
||||||
|
scheme ??= ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
|
||||||
|
var isHttps = string.Equals(scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
|
||||||
return new UriBuilder
|
return new UriBuilder
|
||||||
{
|
{
|
||||||
Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
|
Scheme = scheme,
|
||||||
Host = hostname,
|
Host = hostname,
|
||||||
Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
|
Port = port ?? (isHttps ? HttpsPort : HttpPort),
|
||||||
Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl
|
Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl
|
||||||
}.ToString().TrimEnd('/');
|
}.ToString().TrimEnd('/');
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,15 @@ namespace Emby.Server.Implementations.Data
|
||||||
"CodecTimeBase",
|
"CodecTimeBase",
|
||||||
"ColorPrimaries",
|
"ColorPrimaries",
|
||||||
"ColorSpace",
|
"ColorSpace",
|
||||||
"ColorTransfer"
|
"ColorTransfer",
|
||||||
|
"DvVersionMajor",
|
||||||
|
"DvVersionMinor",
|
||||||
|
"DvProfile",
|
||||||
|
"DvLevel",
|
||||||
|
"RpuPresentFlag",
|
||||||
|
"ElPresentFlag",
|
||||||
|
"BlPresentFlag",
|
||||||
|
"DvBlSignalCompatibilityId"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly string _mediaStreamSaveColumnsInsertQuery =
|
private static readonly string _mediaStreamSaveColumnsInsertQuery =
|
||||||
|
@ -341,7 +349,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
|
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
|
||||||
{
|
{
|
||||||
const string CreateMediaStreamsTableCommand
|
const string CreateMediaStreamsTableCommand
|
||||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||||
const string CreateMediaAttachmentsTableCommand
|
const string CreateMediaAttachmentsTableCommand
|
||||||
= "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecTag TEXT NULL, Comment TEXT NULL, Filename TEXT NULL, MIMEType TEXT NULL, PRIMARY KEY (ItemId, AttachmentIndex))";
|
= "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecTag TEXT NULL, Comment TEXT NULL, Filename TEXT NULL, MIMEType TEXT NULL, PRIMARY KEY (ItemId, AttachmentIndex))";
|
||||||
|
|
||||||
|
@ -555,6 +563,15 @@ namespace Emby.Server.Implementations.Data
|
||||||
AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
|
AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
|
||||||
AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
|
AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
|
||||||
AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
|
AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
|
||||||
|
|
||||||
|
AddColumn(db, "MediaStreams", "DvVersionMajor", "INT", existingColumnNames);
|
||||||
|
AddColumn(db, "MediaStreams", "DvVersionMinor", "INT", existingColumnNames);
|
||||||
|
AddColumn(db, "MediaStreams", "DvProfile", "INT", existingColumnNames);
|
||||||
|
AddColumn(db, "MediaStreams", "DvLevel", "INT", existingColumnNames);
|
||||||
|
AddColumn(db, "MediaStreams", "RpuPresentFlag", "INT", existingColumnNames);
|
||||||
|
AddColumn(db, "MediaStreams", "ElPresentFlag", "INT", existingColumnNames);
|
||||||
|
AddColumn(db, "MediaStreams", "BlPresentFlag", "INT", existingColumnNames);
|
||||||
|
AddColumn(db, "MediaStreams", "DvBlSignalCompatibilityId", "INT", existingColumnNames);
|
||||||
},
|
},
|
||||||
TransactionMode);
|
TransactionMode);
|
||||||
|
|
||||||
|
@ -2403,7 +2420,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
// genres, tags, studios, person, year?
|
// genres, tags, studios, person, year?
|
||||||
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId))");
|
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from ItemValues where ItemId=@SimilarItemId))");
|
||||||
|
|
||||||
if (item is MusicArtist)
|
if (item is MusicArtist)
|
||||||
{
|
{
|
||||||
|
@ -3058,12 +3075,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)";
|
return "(select CleanValue from ItemValues where ItemId=Guid and Type=0 LIMIT 1)";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)";
|
return "(select CleanValue from ItemValues where ItemId=Guid and Type=1 LIMIT 1)";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -3073,7 +3090,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)";
|
return "(select CleanValue from ItemValues where ItemId=Guid and Type=3 LIMIT 1)";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -3146,6 +3163,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
return ItemSortBy.IndexNumber;
|
return ItemSortBy.IndexNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(name, ItemSortBy.SimilarityScore, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ItemSortBy.SimilarityScore;
|
||||||
|
}
|
||||||
|
|
||||||
// Unknown SortBy, just sort by the SortName.
|
// Unknown SortBy, just sort by the SortName.
|
||||||
return ItemSortBy.SortName;
|
return ItemSortBy.SortName;
|
||||||
}
|
}
|
||||||
|
@ -3846,7 +3868,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
var paramName = "@ArtistIds" + index;
|
var paramName = "@ArtistIds" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, artistId);
|
statement.TryBind(paramName, artistId);
|
||||||
|
@ -3867,7 +3889,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
var paramName = "@ArtistIds" + index;
|
var paramName = "@ArtistIds" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
|
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, artistId);
|
statement.TryBind(paramName, artistId);
|
||||||
|
@ -3888,7 +3910,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
var paramName = "@ArtistIds" + index;
|
var paramName = "@ArtistIds" + index;
|
||||||
|
|
||||||
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))");
|
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, artistId);
|
statement.TryBind(paramName, artistId);
|
||||||
|
@ -3930,7 +3952,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
var paramName = "@ExcludeArtistId" + index;
|
var paramName = "@ExcludeArtistId" + index;
|
||||||
|
|
||||||
clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, artistId);
|
statement.TryBind(paramName, artistId);
|
||||||
|
@ -3951,7 +3973,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
var paramName = "@GenreId" + index;
|
var paramName = "@GenreId" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
|
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, genreId);
|
statement.TryBind(paramName, genreId);
|
||||||
|
@ -3970,7 +3992,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var item in query.Genres)
|
foreach (var item in query.Genres)
|
||||||
{
|
{
|
||||||
clauses.Add("@Genre" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=2)");
|
clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind("@Genre" + index, GetCleanValue(item));
|
statement.TryBind("@Genre" + index, GetCleanValue(item));
|
||||||
|
@ -3989,7 +4011,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var item in tags)
|
foreach (var item in tags)
|
||||||
{
|
{
|
||||||
clauses.Add("@Tag" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
|
clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind("@Tag" + index, GetCleanValue(item));
|
statement.TryBind("@Tag" + index, GetCleanValue(item));
|
||||||
|
@ -4008,7 +4030,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var item in excludeTags)
|
foreach (var item in excludeTags)
|
||||||
{
|
{
|
||||||
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
|
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
|
statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
|
||||||
|
@ -4029,7 +4051,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
var paramName = "@StudioId" + index;
|
var paramName = "@StudioId" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
|
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
|
||||||
|
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
|
@ -4508,7 +4530,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
int index = 0;
|
int index = 0;
|
||||||
string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++));
|
string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++));
|
||||||
whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
|
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -4743,11 +4765,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
';',
|
';',
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"delete from itemvalues where type = 6",
|
"delete from ItemValues where type = 6",
|
||||||
|
|
||||||
"insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
|
"insert into ItemValues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
|
||||||
|
|
||||||
@"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
|
@"insert into ItemValues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
|
||||||
FROM AncestorIds
|
FROM AncestorIds
|
||||||
LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId)
|
LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId)
|
||||||
where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 "
|
where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 "
|
||||||
|
@ -5854,6 +5876,15 @@ AND Type = @InternalPersonType)");
|
||||||
statement.TryBind("@ColorPrimaries" + index, stream.ColorPrimaries);
|
statement.TryBind("@ColorPrimaries" + index, stream.ColorPrimaries);
|
||||||
statement.TryBind("@ColorSpace" + index, stream.ColorSpace);
|
statement.TryBind("@ColorSpace" + index, stream.ColorSpace);
|
||||||
statement.TryBind("@ColorTransfer" + index, stream.ColorTransfer);
|
statement.TryBind("@ColorTransfer" + index, stream.ColorTransfer);
|
||||||
|
|
||||||
|
statement.TryBind("@DvVersionMajor" + index, stream.DvVersionMajor);
|
||||||
|
statement.TryBind("@DvVersionMinor" + index, stream.DvVersionMinor);
|
||||||
|
statement.TryBind("@DvProfile" + index, stream.DvProfile);
|
||||||
|
statement.TryBind("@DvLevel" + index, stream.DvLevel);
|
||||||
|
statement.TryBind("@RpuPresentFlag" + index, stream.RpuPresentFlag);
|
||||||
|
statement.TryBind("@ElPresentFlag" + index, stream.ElPresentFlag);
|
||||||
|
statement.TryBind("@BlPresentFlag" + index, stream.BlPresentFlag);
|
||||||
|
statement.TryBind("@DvBlSignalCompatibilityId" + index, stream.DvBlSignalCompatibilityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
statement.Reset();
|
statement.Reset();
|
||||||
|
@ -6025,6 +6056,46 @@ AND Type = @InternalPersonType)");
|
||||||
item.ColorTransfer = colorTransfer;
|
item.ColorTransfer = colorTransfer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetInt32(35, out var dvVersionMajor))
|
||||||
|
{
|
||||||
|
item.DvVersionMajor = dvVersionMajor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetInt32(36, out var dvVersionMinor))
|
||||||
|
{
|
||||||
|
item.DvVersionMinor = dvVersionMinor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetInt32(37, out var dvProfile))
|
||||||
|
{
|
||||||
|
item.DvProfile = dvProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetInt32(38, out var dvLevel))
|
||||||
|
{
|
||||||
|
item.DvLevel = dvLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetInt32(39, out var rpuPresentFlag))
|
||||||
|
{
|
||||||
|
item.RpuPresentFlag = rpuPresentFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetInt32(40, out var elPresentFlag))
|
||||||
|
{
|
||||||
|
item.ElPresentFlag = elPresentFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetInt32(41, out var blPresentFlag))
|
||||||
|
{
|
||||||
|
item.BlPresentFlag = blPresentFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetInt32(42, out var dvBlSignalCompatibilityId))
|
||||||
|
{
|
||||||
|
item.DvBlSignalCompatibilityId = dvBlSignalCompatibilityId;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.Type == MediaStreamType.Subtitle)
|
if (item.Type == MediaStreamType.Subtitle)
|
||||||
{
|
{
|
||||||
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
|
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.6" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||||
|
|
|
@ -1860,7 +1860,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
throw new ArgumentNullException(nameof(item));
|
throw new ArgumentNullException(nameof(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
var outdated = forceUpdate
|
||||||
|
? item.ImageInfos.Where(i => i.Path != null).ToArray()
|
||||||
|
: item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
||||||
// Skip image processing if current or live tv source
|
// Skip image processing if current or live tv source
|
||||||
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
|
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
|
||||||
{
|
{
|
||||||
|
@ -1883,7 +1885,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
|
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
|
catch (Exception ex) when (ex is InvalidOperationException or IOException)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
|
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
|
||||||
continue;
|
continue;
|
||||||
|
@ -1895,23 +1897,24 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageDimensions size;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
|
size = _imageProcessor.GetImageDimensions(item, image);
|
||||||
image.Width = size.Width;
|
image.Width = size.Width;
|
||||||
image.Height = size.Height;
|
image.Height = size.Height;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
|
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
|
||||||
|
size = new ImageDimensions(0, 0);
|
||||||
image.Width = 0;
|
image.Width = 0;
|
||||||
image.Height = 0;
|
image.Height = 0;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
|
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path, size);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,17 +5,17 @@
|
||||||
"Artists": "Καλλιτέχνες",
|
"Artists": "Καλλιτέχνες",
|
||||||
"AuthenticationSucceededWithUserName": "Ο χρήστης {0} επαληθεύτηκε επιτυχώς",
|
"AuthenticationSucceededWithUserName": "Ο χρήστης {0} επαληθεύτηκε επιτυχώς",
|
||||||
"Books": "Βιβλία",
|
"Books": "Βιβλία",
|
||||||
"CameraImageUploadedFrom": "Μια νέα εικόνα κάμερας έχει αποσταλεί από {0}",
|
"CameraImageUploadedFrom": "Μια νέα φωτογραφία φορτώθηκε από {0}",
|
||||||
"Channels": "Κανάλια",
|
"Channels": "Κανάλια",
|
||||||
"ChapterNameValue": "Κεφάλαιο {0}",
|
"ChapterNameValue": "Κεφάλαιο {0}",
|
||||||
"Collections": "Συλλογές",
|
"Collections": "Συλλογές",
|
||||||
"DeviceOfflineWithName": "{0} αποσυνδέθηκε",
|
"DeviceOfflineWithName": "Ο/Η {0} αποσυνδέθηκε",
|
||||||
"DeviceOnlineWithName": "{0} συνδέθηκε",
|
"DeviceOnlineWithName": "Ο/Η {0} συνδέθηκε",
|
||||||
"FailedLoginAttemptWithUserName": "Αποτυχημένη προσπάθεια σύνδεσης από {0}",
|
"FailedLoginAttemptWithUserName": "Αποτυχημένη προσπάθεια σύνδεσης από {0}",
|
||||||
"Favorites": "Αγαπημένα",
|
"Favorites": "Αγαπημένα",
|
||||||
"Folders": "Φάκελοι",
|
"Folders": "Φάκελοι",
|
||||||
"Genres": "Είδη",
|
"Genres": "Είδη",
|
||||||
"HeaderAlbumArtists": "Καλλιτέχνες άλμπουμ",
|
"HeaderAlbumArtists": "Δισκογραφικοί καλλιτέχνες",
|
||||||
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
|
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
|
||||||
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
|
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
|
||||||
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
|
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
"HeaderFavoriteSongs": "Αγαπημένα Τραγούδια",
|
"HeaderFavoriteSongs": "Αγαπημένα Τραγούδια",
|
||||||
"HeaderLiveTV": "Ζωντανή Τηλεόραση",
|
"HeaderLiveTV": "Ζωντανή Τηλεόραση",
|
||||||
"HeaderNextUp": "Επόμενο",
|
"HeaderNextUp": "Επόμενο",
|
||||||
"HeaderRecordingGroups": "Γκρουπ Εγγραφών",
|
"HeaderRecordingGroups": "Μουσικά Συγκροτήματα",
|
||||||
"HomeVideos": "Προσωπικά βίντεο",
|
"HomeVideos": "Προσωπικά βίντεο",
|
||||||
"Inherit": "Κληρονόμηση",
|
"Inherit": "Κληρονόμηση",
|
||||||
"ItemAddedWithName": "{0} προστέθηκε στη βιβλιοθήκη",
|
"ItemAddedWithName": "{0} προστέθηκε στη βιβλιοθήκη",
|
||||||
|
@ -32,10 +32,10 @@
|
||||||
"LabelIpAddressValue": "Διεύθυνση IP: {0}",
|
"LabelIpAddressValue": "Διεύθυνση IP: {0}",
|
||||||
"LabelRunningTimeValue": "Διάρκεια: {0}",
|
"LabelRunningTimeValue": "Διάρκεια: {0}",
|
||||||
"Latest": "Πρόσφατα",
|
"Latest": "Πρόσφατα",
|
||||||
"MessageApplicationUpdated": "Ο Jellyfin Server έχει ενημερωθεί",
|
"MessageApplicationUpdated": "Ο διακομιστής Jellyfin έχει ενημερωθεί",
|
||||||
"MessageApplicationUpdatedTo": "Ο server Jellyfin αναβαθμίστηκε σε έκδοση {0}",
|
"MessageApplicationUpdatedTo": "Ο διακομιστής Jellyfin αναβαθμίστηκε στην έκδοση {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Η ενότητα {0} ρύθμισης παραμέτρων του server έχει ενημερωθεί",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Η ενότητα {0} ρύθμισης παραμέτρων του διακομιστή έχει ενημερωθεί",
|
||||||
"MessageServerConfigurationUpdated": "Η ρύθμιση παραμέτρων του server έχει ενημερωθεί",
|
"MessageServerConfigurationUpdated": "Η ρύθμιση παραμέτρων του διακομιστή έχει ενημερωθεί",
|
||||||
"MixedContent": "Ανάμεικτο Περιεχόμενο",
|
"MixedContent": "Ανάμεικτο Περιεχόμενο",
|
||||||
"Movies": "Ταινίες",
|
"Movies": "Ταινίες",
|
||||||
"Music": "Μουσική",
|
"Music": "Μουσική",
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
"NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
|
"NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
|
||||||
"NameSeasonNumber": "Κύκλος {0}",
|
"NameSeasonNumber": "Κύκλος {0}",
|
||||||
"NameSeasonUnknown": "Άγνωστος Κύκλος",
|
"NameSeasonUnknown": "Άγνωστος Κύκλος",
|
||||||
"NewVersionIsAvailable": "Μια νέα έκδοση του Jellyfin Server είναι διαθέσιμη για λήψη.",
|
"NewVersionIsAvailable": "Μια νέα έκδοση του διακομιστή Jellyfin είναι διαθέσιμη για λήψη.",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Διαθέσιμη ενημερωμένη έκδοση εφαρμογής",
|
"NotificationOptionApplicationUpdateAvailable": "Διαθέσιμη ενημερωμένη έκδοση εφαρμογής",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Η ενημέρωση εφαρμογής εγκαταστάθηκε",
|
"NotificationOptionApplicationUpdateInstalled": "Η ενημέρωση εφαρμογής εγκαταστάθηκε",
|
||||||
"NotificationOptionAudioPlayback": "Η αναπαραγωγή ήχου ξεκίνησε",
|
"NotificationOptionAudioPlayback": "Η αναπαραγωγή ήχου ξεκίνησε",
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
"NotificationOptionPluginInstalled": "Το plugin εγκαταστάθηκε",
|
"NotificationOptionPluginInstalled": "Το plugin εγκαταστάθηκε",
|
||||||
"NotificationOptionPluginUninstalled": "Το plugin απεγκαταστάθηκε",
|
"NotificationOptionPluginUninstalled": "Το plugin απεγκαταστάθηκε",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Η αναβάθμιση του plugin εγκαταστάθηκε",
|
"NotificationOptionPluginUpdateInstalled": "Η αναβάθμιση του plugin εγκαταστάθηκε",
|
||||||
"NotificationOptionServerRestartRequired": "Απαιτείται επανεκκίνηση του server",
|
"NotificationOptionServerRestartRequired": "Ο διακομιστής χρειάζεται επανεκκίνηση",
|
||||||
"NotificationOptionTaskFailed": "Αποτυχία προγραμματισμένης εργασίας",
|
"NotificationOptionTaskFailed": "Αποτυχία προγραμματισμένης εργασίας",
|
||||||
"NotificationOptionUserLockedOut": "Ο χρήστης αποκλείστηκε",
|
"NotificationOptionUserLockedOut": "Ο χρήστης αποκλείστηκε",
|
||||||
"NotificationOptionVideoPlayback": "Η αναπαραγωγή βίντεο ξεκίνησε",
|
"NotificationOptionVideoPlayback": "Η αναπαραγωγή βίντεο ξεκίνησε",
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
"ServerNameNeedsToBeRestarted": "{0} χρειάζεται επανεκκίνηση",
|
"ServerNameNeedsToBeRestarted": "{0} χρειάζεται επανεκκίνηση",
|
||||||
"Shows": "Σειρές",
|
"Shows": "Σειρές",
|
||||||
"Songs": "Τραγούδια",
|
"Songs": "Τραγούδια",
|
||||||
"StartupEmbyServerIsLoading": "Ο Jellyfin Server φορτώνει. Παρακαλώ δοκιμάστε σε λίγο.",
|
"StartupEmbyServerIsLoading": "Ο διακομιστής Jellyfin φορτώνει. Περιμένετε λίγο και δοκιμάστε ξανά.",
|
||||||
"SubtitleDownloadFailureForItem": "Οι υπότιτλοι απέτυχαν να κατέβουν για {0}",
|
"SubtitleDownloadFailureForItem": "Οι υπότιτλοι απέτυχαν να κατέβουν για {0}",
|
||||||
"SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}",
|
"SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}",
|
||||||
"Sync": "Συγχρονισμός",
|
"Sync": "Συγχρονισμός",
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
"Default": "Προεπιλογή",
|
"Default": "Προεπιλογή",
|
||||||
"TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
|
"TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
|
||||||
"TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων",
|
"TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων",
|
||||||
"TaskKeyframeExtractorDescription": "Εξάγει τα βασικά καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς HLS λίστες αναπαραγωγής. Αυτή η εργασία μπορεί να διαρκέσει πολλή ώρα.",
|
"TaskKeyframeExtractorDescription": "Εξάγει καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς λίστες αναπαραγωγής HLS. Αυτή η διεργασία μπορεί να πάρει χρόνο.",
|
||||||
"TaskKeyframeExtractor": "Εξαγωγέας βασικών καρέ βίντεο",
|
"TaskKeyframeExtractor": "Εξαγωγέας βασικών καρέ βίντεο",
|
||||||
"External": "Εξωτερικό"
|
"External": "Εξωτερικό"
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,5 +119,6 @@
|
||||||
"Undefined": "Undefiniert",
|
"Undefined": "Undefiniert",
|
||||||
"Forced": "Erzwungen",
|
"Forced": "Erzwungen",
|
||||||
"Default": "Standard",
|
"Default": "Standard",
|
||||||
"TaskOptimizeDatabase": "Datenbank optimieren"
|
"TaskOptimizeDatabase": "Datenbank optimieren",
|
||||||
|
"External": "Extern"
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
"Songs": "Lagu",
|
"Songs": "Lagu",
|
||||||
"Playlists": "Daftar putar",
|
"Playlists": "Daftar putar",
|
||||||
"NotificationOptionPluginUninstalled": "Plugin dihapus",
|
"NotificationOptionPluginUninstalled": "Plugin dihapus",
|
||||||
"MusicVideos": "Video musik",
|
"MusicVideos": "Video Musik",
|
||||||
"VersionNumber": "Versi {0}",
|
"VersionNumber": "Versi {0}",
|
||||||
"ValueSpecialEpisodeName": "Spesial - {0}",
|
"ValueSpecialEpisodeName": "Spesial - {0}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} telah ditambahkan ke pustaka media Anda",
|
"ValueHasBeenAddedToLibrary": "{0} telah ditambahkan ke pustaka media Anda",
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
"VersionNumber": "バージョン {0}",
|
"VersionNumber": "バージョン {0}",
|
||||||
"TaskCleanLogsDescription": "{0} 日以上前のログを消去します。",
|
"TaskCleanLogsDescription": "{0} 日以上前のログを消去します。",
|
||||||
"TaskCleanLogs": "ログの掃除",
|
"TaskCleanLogs": "ログの掃除",
|
||||||
"TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータをリフレッシュします。",
|
"TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータを更新します。",
|
||||||
"TaskRefreshLibrary": "メディアライブラリのスキャン",
|
"TaskRefreshLibrary": "メディアライブラリのスキャン",
|
||||||
"TaskCleanCacheDescription": "不要なキャッシュを消去します。",
|
"TaskCleanCacheDescription": "不要なキャッシュを消去します。",
|
||||||
"TaskCleanCache": "キャッシュを消去",
|
"TaskCleanCache": "キャッシュを消去",
|
||||||
|
@ -101,15 +101,15 @@
|
||||||
"TasksApplicationCategory": "アプリケーション",
|
"TasksApplicationCategory": "アプリケーション",
|
||||||
"TasksLibraryCategory": "ライブラリ",
|
"TasksLibraryCategory": "ライブラリ",
|
||||||
"TasksMaintenanceCategory": "メンテナンス",
|
"TasksMaintenanceCategory": "メンテナンス",
|
||||||
"TaskRefreshChannelsDescription": "ネットチャンネルの情報をリフレッシュします。",
|
"TaskRefreshChannelsDescription": "ネットチャンネルの情報を更新する。",
|
||||||
"TaskRefreshChannels": "チャンネルのリフレッシュ",
|
"TaskRefreshChannels": "チャンネルの更新",
|
||||||
"TaskCleanTranscodeDescription": "1日以上経過したトランスコードファイルを削除します。",
|
"TaskCleanTranscodeDescription": "1日以上経過したトランスコードファイルを削除します。",
|
||||||
"TaskCleanTranscode": "トランスコードディレクトリの削除",
|
"TaskCleanTranscode": "トランスコードディレクトリの削除",
|
||||||
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
||||||
"TaskUpdatePlugins": "プラグインの更新",
|
"TaskUpdatePlugins": "プラグインの更新",
|
||||||
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータを更新します。",
|
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータを更新します。",
|
||||||
"TaskRefreshPeople": "俳優や監督のデータの更新",
|
"TaskRefreshPeople": "俳優や監督のデータの更新",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
|
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索する。",
|
||||||
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
||||||
"TaskRefreshChapterImages": "チャプター画像を抽出する",
|
"TaskRefreshChapterImages": "チャプター画像を抽出する",
|
||||||
"TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする",
|
"TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする",
|
||||||
|
|
|
@ -120,5 +120,7 @@
|
||||||
"Forced": "Prisilno",
|
"Forced": "Prisilno",
|
||||||
"Default": "Privzeto",
|
"Default": "Privzeto",
|
||||||
"TaskOptimizeDatabaseDescription": "Stisne bazo podatkov in uredi prazen prostor. Zagon tega opravila po iskanju predstavnosti ali drugih spremembah ki vplivajo na bazo podatkov lahko izboljša hitrost delovanja.",
|
"TaskOptimizeDatabaseDescription": "Stisne bazo podatkov in uredi prazen prostor. Zagon tega opravila po iskanju predstavnosti ali drugih spremembah ki vplivajo na bazo podatkov lahko izboljša hitrost delovanja.",
|
||||||
"TaskOptimizeDatabase": "Optimiziraj bazo podatkov"
|
"TaskOptimizeDatabase": "Optimiziraj bazo podatkov",
|
||||||
|
"TaskKeyframeExtractor": "Ekstraktor ključnih sličic",
|
||||||
|
"External": "Zunanje"
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,5 +119,6 @@
|
||||||
"Forced": "Принудно",
|
"Forced": "Принудно",
|
||||||
"Default": "Подразумевано",
|
"Default": "Подразумевано",
|
||||||
"TaskOptimizeDatabase": "Оптимизуј датабазу",
|
"TaskOptimizeDatabase": "Оптимизуј датабазу",
|
||||||
"TaskOptimizeDatabaseDescription": "Сажима базу података и скраћује слободан простор. Покретање овог задатка након скенирања библиотеке или других промена које подразумевају измене базе података које могу побољшати перформансе."
|
"TaskOptimizeDatabaseDescription": "Сажима базу података и скраћује слободан простор. Покретање овог задатка након скенирања библиотеке или других промена које подразумевају измене базе података које могу побољшати перформансе.",
|
||||||
|
"External": "Спољно"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"Favorites": "Favoriter",
|
"Favorites": "Favoriter",
|
||||||
"Folders": "Mappar",
|
"Folders": "Mappar",
|
||||||
"Genres": "Genrer",
|
"Genres": "Genrer",
|
||||||
"HeaderAlbumArtists": "Albumsartister",
|
"HeaderAlbumArtists": "Albumartister",
|
||||||
"HeaderContinueWatching": "Fortsätt titta på",
|
"HeaderContinueWatching": "Fortsätt titta på",
|
||||||
"HeaderFavoriteAlbums": "Favoritalbum",
|
"HeaderFavoriteAlbums": "Favoritalbum",
|
||||||
"HeaderFavoriteArtists": "Favoritartister",
|
"HeaderFavoriteArtists": "Favoritartister",
|
||||||
|
|
|
@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
// Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token
|
// Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token
|
||||||
// since it gets disposed when ffmpeg exits
|
// since it gets disposed when ffmpeg exits
|
||||||
var cancellationToken = cancellationTokenSource.Token;
|
var cancellationToken = cancellationTokenSource.Token;
|
||||||
using var state = await StreamingHelpers.GetStreamingState(
|
var state = await StreamingHelpers.GetStreamingState(
|
||||||
streamingRequest,
|
streamingRequest,
|
||||||
Request,
|
Request,
|
||||||
_authContext,
|
_authContext,
|
||||||
|
@ -1432,7 +1432,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
var cancellationToken = cancellationTokenSource.Token;
|
var cancellationToken = cancellationTokenSource.Token;
|
||||||
|
|
||||||
using var state = await StreamingHelpers.GetStreamingState(
|
var state = await StreamingHelpers.GetStreamingState(
|
||||||
streamingRequest,
|
streamingRequest,
|
||||||
Request,
|
Request,
|
||||||
_authContext,
|
_authContext,
|
||||||
|
|
|
@ -427,7 +427,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
StreamOptions = streamOptions
|
StreamOptions = streamOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
using var state = await StreamingHelpers.GetStreamingState(
|
var state = await StreamingHelpers.GetStreamingState(
|
||||||
streamingRequest,
|
streamingRequest,
|
||||||
Request,
|
Request,
|
||||||
_authContext,
|
_authContext,
|
||||||
|
|
|
@ -216,7 +216,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
|
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
|
||||||
sdrVideoUrl += "&AllowVideoStreamCopy=false";
|
sdrVideoUrl += "&AllowVideoStreamCopy=false";
|
||||||
|
|
||||||
var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec) ?? 0;
|
var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
|
||||||
var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0;
|
var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0;
|
||||||
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
|
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
|
||||||
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
{
|
{
|
||||||
containerInternal = streamingRequest.Static ?
|
containerInternal = streamingRequest.Static ?
|
||||||
StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio)
|
StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio)
|
||||||
: GetOutputFileExtension(state);
|
: GetOutputFileExtension(state, mediaSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
|
state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
|
||||||
|
@ -235,7 +235,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static);
|
ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static);
|
||||||
|
|
||||||
var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
|
var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
|
||||||
? GetOutputFileExtension(state)
|
? GetOutputFileExtension(state, mediaSource)
|
||||||
: ("." + state.OutputContainer);
|
: ("." + state.OutputContainer);
|
||||||
|
|
||||||
state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);
|
state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);
|
||||||
|
@ -312,7 +312,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
|
|
||||||
responseHeaders.Add(
|
responseHeaders.Add(
|
||||||
"contentFeatures.dlna.org",
|
"contentFeatures.dlna.org",
|
||||||
ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
|
ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,8 +409,9 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// Gets the output file extension.
|
/// Gets the output file extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="mediaSource">The mediaSource.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
private static string? GetOutputFileExtension(StreamState state)
|
private static string? GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
|
||||||
{
|
{
|
||||||
var ext = Path.GetExtension(state.RequestedUrl);
|
var ext = Path.GetExtension(state.RequestedUrl);
|
||||||
|
|
||||||
|
@ -425,7 +426,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
var videoCodec = state.Request.VideoCodec;
|
var videoCodec = state.Request.VideoCodec;
|
||||||
|
|
||||||
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase))
|
string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".ts";
|
return ".ts";
|
||||||
}
|
}
|
||||||
|
@ -474,6 +475,13 @@ namespace Jellyfin.Api.Helpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback to the container of mediaSource
|
||||||
|
if (!string.IsNullOrEmpty(mediaSource?.Container))
|
||||||
|
{
|
||||||
|
var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase);
|
||||||
|
return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,6 +541,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
state.TargetVideoBitDepth,
|
state.TargetVideoBitDepth,
|
||||||
state.OutputVideoBitrate,
|
state.OutputVideoBitrate,
|
||||||
state.TargetVideoProfile,
|
state.TargetVideoProfile,
|
||||||
|
state.TargetVideoRangeType,
|
||||||
state.TargetVideoLevel,
|
state.TargetVideoLevel,
|
||||||
state.TargetFramerate,
|
state.TargetFramerate,
|
||||||
state.TargetPacketLength,
|
state.TargetPacketLength,
|
||||||
|
|
|
@ -654,8 +654,8 @@ namespace Jellyfin.Api.Helpers
|
||||||
{
|
{
|
||||||
if (EnableThrottling(state))
|
if (EnableThrottling(state))
|
||||||
{
|
{
|
||||||
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem);
|
transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem);
|
||||||
state.TranscodingThrottler.Start();
|
transcodingJob.TranscodingThrottler.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -663,18 +663,11 @@ namespace Jellyfin.Api.Helpers
|
||||||
{
|
{
|
||||||
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
|
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
|
||||||
|
|
||||||
// enable throttling when NOT using hardware acceleration
|
return state.InputProtocol == MediaProtocol.File &&
|
||||||
if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
|
state.RunTimeTicks.HasValue &&
|
||||||
{
|
state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
|
||||||
return state.InputProtocol == MediaProtocol.File &&
|
state.IsInputVideo &&
|
||||||
state.RunTimeTicks.HasValue &&
|
state.VideoType == VideoType.VideoFile;
|
||||||
state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
|
|
||||||
state.IsInputVideo &&
|
|
||||||
state.VideoType == VideoType.VideoFile &&
|
|
||||||
!EncodingHelper.IsCopyCodec(state.OutputVideoCodec);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" />
|
||||||
|
|
|
@ -47,11 +47,6 @@ namespace Jellyfin.Api.Models.StreamingDtos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the transcoding throttler.
|
|
||||||
/// </summary>
|
|
||||||
public TranscodingThrottler? TranscodingThrottler { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the video request.
|
/// Gets the video request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -191,11 +186,8 @@ namespace Jellyfin.Api.Models.StreamingDtos
|
||||||
{
|
{
|
||||||
_mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
|
_mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
TranscodingThrottler?.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TranscodingThrottler = null;
|
|
||||||
TranscodingJob = null;
|
TranscodingJob = null;
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BlurHashSharp" Version="1.2.0" />
|
<PackageReference Include="BlurHashSharp" Version="1.2.0" />
|
||||||
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
|
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.71" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.71" />
|
||||||
|
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -10,7 +10,7 @@ using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using static Jellyfin.Drawing.Skia.SkiaHelper;
|
using SKSvg = SkiaSharp.Extended.Svg.SKSvg;
|
||||||
|
|
||||||
namespace Jellyfin.Drawing.Skia
|
namespace Jellyfin.Drawing.Skia
|
||||||
{
|
{
|
||||||
|
@ -19,8 +19,7 @@ namespace Jellyfin.Drawing.Skia
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SkiaEncoder : IImageEncoder
|
public class SkiaEncoder : IImageEncoder
|
||||||
{
|
{
|
||||||
private static readonly HashSet<string> _transparentImageTypes
|
private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
|
||||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
|
|
||||||
|
|
||||||
private readonly ILogger<SkiaEncoder> _logger;
|
private readonly ILogger<SkiaEncoder> _logger;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
@ -71,7 +70,7 @@ namespace Jellyfin.Drawing.Skia
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
|
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
|
||||||
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
|
=> new HashSet<ImageFormat> { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the native lib is available.
|
/// Check if the native lib is available.
|
||||||
|
@ -109,9 +108,7 @@ namespace Jellyfin.Drawing.Skia
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <exception cref="ArgumentNullException">The path is null.</exception>
|
|
||||||
/// <exception cref="FileNotFoundException">The path is not valid.</exception>
|
/// <exception cref="FileNotFoundException">The path is not valid.</exception>
|
||||||
/// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
|
|
||||||
public ImageDimensions GetImageSize(string path)
|
public ImageDimensions GetImageSize(string path)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
|
@ -119,12 +116,27 @@ namespace Jellyfin.Drawing.Skia
|
||||||
throw new FileNotFoundException("File not found", path);
|
throw new FileNotFoundException("File not found", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
|
if (extension.Equals(".svg", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var svg = new SKSvg();
|
||||||
|
svg.Load(path);
|
||||||
|
return new ImageDimensions(Convert.ToInt32(svg.Picture.CullRect.Width), Convert.ToInt32(svg.Picture.CullRect.Height));
|
||||||
|
}
|
||||||
|
|
||||||
using var codec = SKCodec.Create(path, out SKCodecResult result);
|
using var codec = SKCodec.Create(path, out SKCodecResult result);
|
||||||
EnsureSuccess(result);
|
switch (result)
|
||||||
|
{
|
||||||
var info = codec.Info;
|
case SKCodecResult.Success:
|
||||||
|
var info = codec.Info;
|
||||||
return new ImageDimensions(info.Width, info.Height);
|
return new ImageDimensions(info.Width, info.Height);
|
||||||
|
case SKCodecResult.Unimplemented:
|
||||||
|
_logger.LogDebug("Image format not supported: {FilePath}", path);
|
||||||
|
return new ImageDimensions(0, 0);
|
||||||
|
default:
|
||||||
|
_logger.LogError("Unable to determine image dimensions for {FilePath}: {SkCodecResult}", path, result);
|
||||||
|
return new ImageDimensions(0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -138,6 +150,13 @@ namespace Jellyfin.Drawing.Skia
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var extension = Path.GetExtension(path.AsSpan()).TrimStart('.');
|
||||||
|
if (!SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Unable to compute blur hash due to unsupported format: {ImagePath}", path);
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
// Any larger than 128x128 is too slow and there's no visually discernible difference
|
// Any larger than 128x128 is too slow and there's no visually discernible difference
|
||||||
return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
|
return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
|
||||||
}
|
}
|
||||||
|
@ -378,6 +397,13 @@ namespace Jellyfin.Drawing.Skia
|
||||||
throw new ArgumentException("String can't be empty.", nameof(outputPath));
|
throw new ArgumentException("String can't be empty.", nameof(outputPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var inputFormat = Path.GetExtension(inputPath.AsSpan()).TrimStart('.');
|
||||||
|
if (!SupportedInputFormats.Contains(inputFormat, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Unable to encode image due to unsupported format: {ImagePath}", inputPath);
|
||||||
|
return inputPath;
|
||||||
|
}
|
||||||
|
|
||||||
var skiaOutputFormat = GetImageFormat(outputFormat);
|
var skiaOutputFormat = GetImageFormat(outputFormat);
|
||||||
|
|
||||||
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
|
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
|
||||||
|
|
|
@ -8,19 +8,6 @@ namespace Jellyfin.Drawing.Skia
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class SkiaHelper
|
public static class SkiaHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Ensures the result is a success
|
|
||||||
/// by throwing an exception when that's not the case.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="result">The result returned by Skia.</param>
|
|
||||||
public static void EnsureSuccess(SKCodecResult result)
|
|
||||||
{
|
|
||||||
if (result != SKCodecResult.Success)
|
|
||||||
{
|
|
||||||
throw new SkiaCodecException(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the next valid image as a bitmap.
|
/// Gets the next valid image as a bitmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -27,13 +27,13 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.6">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
@ -37,8 +37,8 @@
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.6" />
|
||||||
<PackageReference Include="prometheus-net" Version="6.0.0" />
|
<PackageReference Include="prometheus-net" Version="6.0.0" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||||
|
|
|
@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.Drawing
|
||||||
/// <returns>BlurHash.</returns>
|
/// <returns>BlurHash.</returns>
|
||||||
string GetImageBlurHash(string path);
|
string GetImageBlurHash(string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the blurhash of the image.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path to the image file.</param>
|
||||||
|
/// <param name="imageDimensions">The image dimensions.</param>
|
||||||
|
/// <returns>BlurHash.</returns>
|
||||||
|
string GetImageBlurHash(string path, ImageDimensions imageDimensions);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the image cache tag.
|
/// Gets the image cache tag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
@ -74,9 +75,10 @@ namespace MediaBrowser.Controller
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an URL that can be used to access the API over LAN.
|
/// Gets an URL that can be used to access the API over LAN.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="hostname">An optional hostname to use.</param>
|
||||||
/// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
|
/// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
|
||||||
/// <returns>The API URL.</returns>
|
/// <returns>The API URL.</returns>
|
||||||
string GetApiUrlForLocalAccess(bool allowHttps = true);
|
string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a local (LAN) URL that can be used to access the API.
|
/// Gets a local (LAN) URL that can be used to access the API.
|
||||||
|
|
|
@ -75,6 +75,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
/// <value>The profile.</value>
|
/// <value>The profile.</value>
|
||||||
public string Profile { get; set; }
|
public string Profile { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the video range type.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The video range type.</value>
|
||||||
|
public string VideoRangeType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the level.
|
/// Gets or sets the level.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -125,6 +125,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
&& _mediaEncoder.SupportsFilter("scale_vaapi")
|
&& _mediaEncoder.SupportsFilter("scale_vaapi")
|
||||||
&& _mediaEncoder.SupportsFilter("deinterlace_vaapi")
|
&& _mediaEncoder.SupportsFilter("deinterlace_vaapi")
|
||||||
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
|
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
|
||||||
|
&& _mediaEncoder.SupportsFilter("procamp_vaapi")
|
||||||
&& _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
|
&& _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
|
||||||
&& _mediaEncoder.SupportsFilter("hwupload_vaapi");
|
&& _mediaEncoder.SupportsFilter("hwupload_vaapi");
|
||||||
}
|
}
|
||||||
|
@ -156,9 +157,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|
if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Only native SW decoder and HW accelerator can parse dovi rpu.
|
// Only native SW decoder and HW accelerator can parse dovi rpu.
|
||||||
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
|
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
|
||||||
|
@ -169,22 +170,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder;
|
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(state.VideoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase);
|
&& (string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(state.VideoStream.VideoRangeType, "HLG", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
|
private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
|
||||||
{
|
{
|
||||||
if (state.VideoStream == null)
|
if (state.VideoStream == null
|
||||||
|
|| !options.EnableVppTonemapping
|
||||||
|
|| GetVideoColorBitDepth(state) != 10)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Native VPP tonemapping may come to QSV in the future.
|
// Native VPP tonemapping may come to QSV in the future.
|
||||||
|
|
||||||
return options.EnableVppTonemapping
|
return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
|
||||||
&& string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase);
|
||||||
&& GetVideoColorBitDepth(state) == 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -713,6 +716,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
else if (_mediaEncoder.IsVaapiDeviceInteli965)
|
else if (_mediaEncoder.IsVaapiDeviceInteli965)
|
||||||
{
|
{
|
||||||
|
// Only override i965 since it has lower priority than iHD in libva lookup.
|
||||||
|
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
|
||||||
|
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
|
||||||
args.Append(GetVaapiDeviceArgs(null, "i965", null, VaapiAlias));
|
args.Append(GetVaapiDeviceArgs(null, "i965", null, VaapiAlias));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1753,6 +1759,20 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
|
||||||
|
if (requestedProfiles.Length > 0)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(videoStream.VideoRangeType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Video width must fall within requested value
|
// Video width must fall within requested value
|
||||||
if (request.MaxWidth.HasValue
|
if (request.MaxWidth.HasValue
|
||||||
&& (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
|
&& (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
|
||||||
|
@ -1893,7 +1913,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return request.EnableAutoStreamCopy;
|
return request.EnableAutoStreamCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
|
public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
|
||||||
{
|
{
|
||||||
var bitrate = request.VideoBitRate;
|
var bitrate = request.VideoBitRate;
|
||||||
|
|
||||||
|
@ -1925,7 +1945,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitrate;
|
// Cap the max target bitrate to intMax/2 to satisify the bufsize=bitrate*2.
|
||||||
|
return Math.Min(bitrate ?? 0, int.MaxValue / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
|
private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
|
||||||
|
@ -2272,7 +2293,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
|
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
|
||||||
if (state.AudioStream.IsExternal)
|
if (state.AudioStream.IsExternal)
|
||||||
{
|
{
|
||||||
bool hasExternalGraphicsSubs = state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream;
|
bool hasExternalGraphicsSubs = state.SubtitleStream != null
|
||||||
|
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
|
||||||
|
&& state.SubtitleStream.IsExternal
|
||||||
|
&& !state.SubtitleStream.IsTextSubtitleStream;
|
||||||
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
|
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
|
||||||
|
|
||||||
args += string.Format(
|
args += string.Format(
|
||||||
|
@ -2700,7 +2724,18 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
var args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709";
|
var args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709";
|
||||||
|
|
||||||
if (!hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
|
if (hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
args += ",procamp_vaapi=b={2}:c={3}:extra_hw_frames=16";
|
||||||
|
return string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
args,
|
||||||
|
hwTonemapSuffix,
|
||||||
|
videoFormat ?? "nv12",
|
||||||
|
options.VppTonemappingBrightness,
|
||||||
|
options.VppTonemappingContrast);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
args += ":tonemap={2}:peak={3}:desat={4}";
|
args += ":tonemap={2}:peak={3}:desat={4}";
|
||||||
|
|
||||||
|
@ -4255,6 +4290,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return videoStream.BitDepth.Value;
|
return videoStream.BitDepth.Value;
|
||||||
}
|
}
|
||||||
else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
|
else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return 8;
|
return 8;
|
||||||
|
@ -4287,14 +4323,18 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
|
protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
|
||||||
{
|
{
|
||||||
var videoStream = state.VideoStream;
|
var videoStream = state.VideoStream;
|
||||||
if (videoStream == null)
|
var mediaSource = state.MediaSource;
|
||||||
|
if (videoStream == null || mediaSource == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only use alternative encoders for video files.
|
// HWA decoders can handle both video files and video folders.
|
||||||
var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
|
var videoType = mediaSource.VideoType;
|
||||||
if (videoType != VideoType.VideoFile)
|
if (videoType != VideoType.VideoFile
|
||||||
|
&& videoType != VideoType.Iso
|
||||||
|
&& videoType != VideoType.Dvd
|
||||||
|
&& videoType != VideoType.BluRay)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -4541,7 +4581,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
|
var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
|
||||||
&& _mediaEncoder.SupportsFilter("alphasrc");
|
&& _mediaEncoder.SupportsFilter("alphasrc");
|
||||||
|
|
||||||
var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
// TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
|
// TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
|
||||||
|
|
||||||
|
@ -4600,7 +4641,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
|
var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
|
||||||
var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
// TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
|
// TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
|
||||||
|
|
||||||
|
@ -4666,7 +4708,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
|
var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
|
||||||
&& IsOpenclFullSupported()
|
&& IsOpenclFullSupported()
|
||||||
&& _mediaEncoder.SupportsFilter("alphasrc");
|
&& _mediaEncoder.SupportsFilter("alphasrc");
|
||||||
var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (is8bitSwFormatsAmf)
|
if (is8bitSwFormatsAmf)
|
||||||
|
@ -4722,7 +4765,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
&& IsVaapiFullSupported()
|
&& IsVaapiFullSupported()
|
||||||
&& IsOpenclFullSupported()
|
&& IsOpenclFullSupported()
|
||||||
&& _mediaEncoder.SupportsFilter("alphasrc");
|
&& _mediaEncoder.SupportsFilter("alphasrc");
|
||||||
var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (is8bitSwFormatsVaapi)
|
if (is8bitSwFormatsVaapi)
|
||||||
|
@ -4779,7 +4823,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (is8bitSwFormatsVt)
|
if (is8bitSwFormatsVt)
|
||||||
|
|
|
@ -366,6 +366,28 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the target video range type.
|
||||||
|
/// </summary>
|
||||||
|
public string TargetVideoRangeType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec))
|
||||||
|
{
|
||||||
|
return VideoStream?.VideoRangeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestedRangeType = GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault();
|
||||||
|
if (!string.IsNullOrEmpty(requestedRangeType))
|
||||||
|
{
|
||||||
|
return requestedRangeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string TargetVideoCodecTag
|
public string TargetVideoCodecTag
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -579,6 +601,26 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string[] GetRequestedRangeTypes(string codec)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(BaseRequest.VideoRangeType))
|
||||||
|
{
|
||||||
|
return BaseRequest.VideoRangeType.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(codec))
|
||||||
|
{
|
||||||
|
var rangetype = BaseRequest.GetOption(codec, "rangetype");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(rangetype))
|
||||||
|
{
|
||||||
|
return rangetype.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
public string GetRequestedLevel(string codec)
|
public string GetRequestedLevel(string codec)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(BaseRequest.Level))
|
if (!string.IsNullOrEmpty(BaseRequest.Level))
|
||||||
|
|
|
@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
percent = 100.0 * currentMs / totalMs;
|
percent = 100.0 * currentMs / totalMs;
|
||||||
|
|
||||||
transcodingPosition = val;
|
transcodingPosition = TimeSpan.FromMilliseconds(currentMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
|
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
|
@ -100,6 +100,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
"scale_vaapi",
|
"scale_vaapi",
|
||||||
"deinterlace_vaapi",
|
"deinterlace_vaapi",
|
||||||
"tonemap_vaapi",
|
"tonemap_vaapi",
|
||||||
|
"procamp_vaapi",
|
||||||
"overlay_vaapi",
|
"overlay_vaapi",
|
||||||
"hwupload_vaapi"
|
"hwupload_vaapi"
|
||||||
};
|
};
|
||||||
|
|
|
@ -310,5 +310,12 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
/// <value>The color primaries.</value>
|
/// <value>The color primaries.</value>
|
||||||
[JsonPropertyName("color_primaries")]
|
[JsonPropertyName("color_primaries")]
|
||||||
public string ColorPrimaries { get; set; }
|
public string ColorPrimaries { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the side_data_list.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The side_data_list.</value>
|
||||||
|
[JsonPropertyName("side_data_list")]
|
||||||
|
public IReadOnlyList<MediaStreamInfoSideData> SideDataList { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Probing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class MediaStreamInfoSideData.
|
||||||
|
/// </summary>
|
||||||
|
public class MediaStreamInfoSideData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the SideDataType.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The SideDataType.</value>
|
||||||
|
[JsonPropertyName("side_data_type")]
|
||||||
|
public string? SideDataType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the DvVersionMajor.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The DvVersionMajor.</value>
|
||||||
|
[JsonPropertyName("dv_version_major")]
|
||||||
|
public int? DvVersionMajor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the DvVersionMinor.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The DvVersionMinor.</value>
|
||||||
|
[JsonPropertyName("dv_version_minor")]
|
||||||
|
public int? DvVersionMinor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the DvProfile.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The DvProfile.</value>
|
||||||
|
[JsonPropertyName("dv_profile")]
|
||||||
|
public int? DvProfile { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the DvLevel.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The DvLevel.</value>
|
||||||
|
[JsonPropertyName("dv_level")]
|
||||||
|
public int? DvLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the RpuPresentFlag.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The RpuPresentFlag.</value>
|
||||||
|
[JsonPropertyName("rpu_present_flag")]
|
||||||
|
public int? RpuPresentFlag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ElPresentFlag.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The ElPresentFlag.</value>
|
||||||
|
[JsonPropertyName("el_present_flag")]
|
||||||
|
public int? ElPresentFlag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the BlPresentFlag.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The BlPresentFlag.</value>
|
||||||
|
[JsonPropertyName("bl_present_flag")]
|
||||||
|
public int? BlPresentFlag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the DvBlSignalCompatibilityId.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The DvBlSignalCompatibilityId.</value>
|
||||||
|
[JsonPropertyName("dv_bl_signal_compatibility_id")]
|
||||||
|
public int? DvBlSignalCompatibilityId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -841,6 +841,27 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
{
|
{
|
||||||
stream.ColorPrimaries = streamInfo.ColorPrimaries;
|
stream.ColorPrimaries = streamInfo.ColorPrimaries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (streamInfo.SideDataList != null)
|
||||||
|
{
|
||||||
|
foreach (var data in streamInfo.SideDataList)
|
||||||
|
{
|
||||||
|
// Parse Dolby Vision metadata from side_data
|
||||||
|
if (string.Equals(data.SideDataType, "DOVI configuration record", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
stream.DvVersionMajor = data.DvVersionMajor;
|
||||||
|
stream.DvVersionMinor = data.DvVersionMinor;
|
||||||
|
stream.DvProfile = data.DvProfile;
|
||||||
|
stream.DvLevel = data.DvLevel;
|
||||||
|
stream.RpuPresentFlag = data.RpuPresentFlag;
|
||||||
|
stream.ElPresentFlag = data.ElPresentFlag;
|
||||||
|
stream.BlPresentFlag = data.BlPresentFlag;
|
||||||
|
stream.DvBlSignalCompatibilityId = data.DvBlSignalCompatibilityId;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
54
MediaBrowser.MediaEncoding/Subtitles/AssWriter.cs
Normal file
54
MediaBrowser.MediaEncoding/Subtitles/AssWriter.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ASS subtitle writer.
|
||||||
|
/// </summary>
|
||||||
|
public class AssWriter : ISubtitleWriter
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
|
{
|
||||||
|
var trackEvents = info.TrackEvents;
|
||||||
|
var timeFormat = @"hh\:mm\:ss\.ff";
|
||||||
|
|
||||||
|
// Write ASS header
|
||||||
|
writer.WriteLine("[Script Info]");
|
||||||
|
writer.WriteLine("Title: Jellyfin transcoded ASS subtitle");
|
||||||
|
writer.WriteLine("ScriptType: v4.00+");
|
||||||
|
writer.WriteLine();
|
||||||
|
writer.WriteLine("[V4+ Styles]");
|
||||||
|
writer.WriteLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding");
|
||||||
|
writer.WriteLine("Style: Default,Arial,20,&H00FFFFFF,&H00FFFFFF,&H19333333,&H910E0807,0,0,0,0,100,100,0,0,0,1,0,2,10,10,10,1");
|
||||||
|
writer.WriteLine();
|
||||||
|
writer.WriteLine("[Events]");
|
||||||
|
writer.WriteLine("Format: Layer, Start, End, Style, Text");
|
||||||
|
|
||||||
|
for (int i = 0; i < trackEvents.Count; i++)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var trackEvent = trackEvents[i];
|
||||||
|
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
|
||||||
|
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
|
||||||
|
var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
writer.WriteLine(
|
||||||
|
"Dialogue: 0,{0},{1},Default,{2}",
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
MediaBrowser.MediaEncoding/Subtitles/SsaWriter.cs
Normal file
54
MediaBrowser.MediaEncoding/Subtitles/SsaWriter.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SSA subtitle writer.
|
||||||
|
/// </summary>
|
||||||
|
public class SsaWriter : ISubtitleWriter
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
|
{
|
||||||
|
var trackEvents = info.TrackEvents;
|
||||||
|
var timeFormat = @"hh\:mm\:ss\.ff";
|
||||||
|
|
||||||
|
// Write SSA header
|
||||||
|
writer.WriteLine("[Script Info]");
|
||||||
|
writer.WriteLine("Title: Jellyfin transcoded SSA subtitle");
|
||||||
|
writer.WriteLine("ScriptType: v4.00");
|
||||||
|
writer.WriteLine();
|
||||||
|
writer.WriteLine("[V4 Styles]");
|
||||||
|
writer.WriteLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
|
||||||
|
writer.WriteLine("Style: Default,Arial,20,&H00FFFFFF,&H00FFFFFF,&H19333333,&H19333333,0,0,0,1,0,2,10,10,10,0,1");
|
||||||
|
writer.WriteLine();
|
||||||
|
writer.WriteLine("[Events]");
|
||||||
|
writer.WriteLine("Format: Layer, Start, End, Style, Text");
|
||||||
|
|
||||||
|
for (int i = 0; i < trackEvents.Count; i++)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var trackEvent = trackEvents[i];
|
||||||
|
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
|
||||||
|
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
|
||||||
|
var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
writer.WriteLine(
|
||||||
|
"Dialogue: 0,{0},{1},Default,{2}",
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -283,6 +283,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
|
|
||||||
private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value)
|
private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value)
|
||||||
{
|
{
|
||||||
|
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
value = new AssWriter();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(format))
|
if (string.IsNullOrEmpty(format))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(format));
|
throw new ArgumentNullException(nameof(format));
|
||||||
|
@ -294,12 +300,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase) || string.Equals(format, SubtitleFormat.SUBRIP, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
value = new SrtWriter();
|
value = new SrtWriter();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
value = new SsaWriter();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
value = new VttWriter();
|
value = new VttWriter();
|
||||||
|
|
|
@ -26,6 +26,8 @@ namespace MediaBrowser.Model.Configuration
|
||||||
TonemappingThreshold = 0.8;
|
TonemappingThreshold = 0.8;
|
||||||
TonemappingPeak = 100;
|
TonemappingPeak = 100;
|
||||||
TonemappingParam = 0;
|
TonemappingParam = 0;
|
||||||
|
VppTonemappingBrightness = 0;
|
||||||
|
VppTonemappingContrast = 1.2;
|
||||||
H264Crf = 23;
|
H264Crf = 23;
|
||||||
H265Crf = 28;
|
H265Crf = 28;
|
||||||
DeinterlaceDoubleRate = false;
|
DeinterlaceDoubleRate = false;
|
||||||
|
@ -89,6 +91,10 @@ namespace MediaBrowser.Model.Configuration
|
||||||
|
|
||||||
public double TonemappingParam { get; set; }
|
public double TonemappingParam { get; set; }
|
||||||
|
|
||||||
|
public double VppTonemappingBrightness { get; set; }
|
||||||
|
|
||||||
|
public double VppTonemappingContrast { get; set; }
|
||||||
|
|
||||||
public int H264Crf { get; set; }
|
public int H264Crf { get; set; }
|
||||||
|
|
||||||
public int H265Crf { get; set; }
|
public int H265Crf { get; set; }
|
||||||
|
|
|
@ -16,6 +16,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? videoBitDepth,
|
int? videoBitDepth,
|
||||||
int? videoBitrate,
|
int? videoBitrate,
|
||||||
string? videoProfile,
|
string? videoProfile,
|
||||||
|
string? videoRangeType,
|
||||||
double? videoLevel,
|
double? videoLevel,
|
||||||
float? videoFramerate,
|
float? videoFramerate,
|
||||||
int? packetLength,
|
int? packetLength,
|
||||||
|
@ -42,6 +43,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return IsConditionSatisfied(condition, videoLevel);
|
return IsConditionSatisfied(condition, videoLevel);
|
||||||
case ProfileConditionValue.VideoProfile:
|
case ProfileConditionValue.VideoProfile:
|
||||||
return IsConditionSatisfied(condition, videoProfile);
|
return IsConditionSatisfied(condition, videoProfile);
|
||||||
|
case ProfileConditionValue.VideoRangeType:
|
||||||
|
return IsConditionSatisfied(condition, videoRangeType);
|
||||||
case ProfileConditionValue.VideoCodecTag:
|
case ProfileConditionValue.VideoCodecTag:
|
||||||
return IsConditionSatisfied(condition, videoCodecTag);
|
return IsConditionSatisfied(condition, videoCodecTag);
|
||||||
case ProfileConditionValue.PacketLength:
|
case ProfileConditionValue.PacketLength:
|
||||||
|
|
|
@ -128,6 +128,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
bool isDirectStream,
|
bool isDirectStream,
|
||||||
long? runtimeTicks,
|
long? runtimeTicks,
|
||||||
string videoProfile,
|
string videoProfile,
|
||||||
|
string videoRangeType,
|
||||||
double? videoLevel,
|
double? videoLevel,
|
||||||
float? videoFramerate,
|
float? videoFramerate,
|
||||||
int? packetLength,
|
int? packetLength,
|
||||||
|
@ -176,6 +177,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
bitDepth,
|
bitDepth,
|
||||||
videoBitrate,
|
videoBitrate,
|
||||||
videoProfile,
|
videoProfile,
|
||||||
|
videoRangeType,
|
||||||
videoLevel,
|
videoLevel,
|
||||||
videoFramerate,
|
videoFramerate,
|
||||||
packetLength,
|
packetLength,
|
||||||
|
|
|
@ -423,6 +423,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
/// <param name="bitDepth">The bit depth.</param>
|
/// <param name="bitDepth">The bit depth.</param>
|
||||||
/// <param name="videoBitrate">The video bitrate.</param>
|
/// <param name="videoBitrate">The video bitrate.</param>
|
||||||
/// <param name="videoProfile">The video profile.</param>
|
/// <param name="videoProfile">The video profile.</param>
|
||||||
|
/// <param name="videoRangeType">The video range type.</param>
|
||||||
/// <param name="videoLevel">The video level.</param>
|
/// <param name="videoLevel">The video level.</param>
|
||||||
/// <param name="videoFramerate">The video framerate.</param>
|
/// <param name="videoFramerate">The video framerate.</param>
|
||||||
/// <param name="packetLength">The packet length.</param>
|
/// <param name="packetLength">The packet length.</param>
|
||||||
|
@ -444,6 +445,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? bitDepth,
|
int? bitDepth,
|
||||||
int? videoBitrate,
|
int? videoBitrate,
|
||||||
string videoProfile,
|
string videoProfile,
|
||||||
|
string videoRangeType,
|
||||||
double? videoLevel,
|
double? videoLevel,
|
||||||
float? videoFramerate,
|
float? videoFramerate,
|
||||||
int? packetLength,
|
int? packetLength,
|
||||||
|
@ -483,7 +485,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
var anyOff = false;
|
var anyOff = false;
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
||||||
{
|
{
|
||||||
anyOff = true;
|
anyOff = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
IsAvc = 20,
|
IsAvc = 20,
|
||||||
IsInterlaced = 21,
|
IsInterlaced = 21,
|
||||||
AudioSampleRate = 22,
|
AudioSampleRate = 22,
|
||||||
AudioBitDepth = 23
|
AudioBitDepth = 23,
|
||||||
|
VideoRangeType = 24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,6 +221,9 @@ namespace MediaBrowser.Model.Dlna
|
||||||
case ProfileConditionValue.VideoProfile:
|
case ProfileConditionValue.VideoProfile:
|
||||||
return TranscodeReason.VideoProfileNotSupported;
|
return TranscodeReason.VideoProfileNotSupported;
|
||||||
|
|
||||||
|
case ProfileConditionValue.VideoRangeType:
|
||||||
|
return TranscodeReason.VideoRangeTypeNotSupported;
|
||||||
|
|
||||||
case ProfileConditionValue.VideoTimestamp:
|
case ProfileConditionValue.VideoTimestamp:
|
||||||
// TODO
|
// TODO
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -748,9 +751,9 @@ namespace MediaBrowser.Model.Dlna
|
||||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||||
.Where(i => i.Type == CodecType.Video &&
|
.Where(i => i.Type == CodecType.Video &&
|
||||||
i.ContainsAnyCodec(videoCodec, container) &&
|
i.ContainsAnyCodec(videoCodec, container) &&
|
||||||
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
|
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
|
i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
|
||||||
|
|
||||||
// An empty appliedVideoConditions means that the codec has no conditions for the current video stream
|
// An empty appliedVideoConditions means that the codec has no conditions for the current video stream
|
||||||
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
|
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
|
||||||
|
@ -834,6 +837,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? videoBitrate = videoStream?.BitRate;
|
int? videoBitrate = videoStream?.BitRate;
|
||||||
double? videoLevel = videoStream?.Level;
|
double? videoLevel = videoStream?.Level;
|
||||||
string videoProfile = videoStream?.Profile;
|
string videoProfile = videoStream?.Profile;
|
||||||
|
string videoRangeType = videoStream?.VideoRangeType;
|
||||||
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
|
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
|
||||||
bool? isAnamorphic = videoStream?.IsAnamorphic;
|
bool? isAnamorphic = videoStream?.IsAnamorphic;
|
||||||
bool? isInterlaced = videoStream?.IsInterlaced;
|
bool? isInterlaced = videoStream?.IsInterlaced;
|
||||||
|
@ -850,7 +854,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||||
.Where(i => i.Type == CodecType.Video &&
|
.Where(i => i.Type == CodecType.Video &&
|
||||||
i.ContainsAnyCodec(videoCodec, container) &&
|
i.ContainsAnyCodec(videoCodec, container) &&
|
||||||
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
|
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
|
||||||
var isFirstAppliedCodecProfile = true;
|
var isFirstAppliedCodecProfile = true;
|
||||||
foreach (var i in appliedVideoConditions)
|
foreach (var i in appliedVideoConditions)
|
||||||
{
|
{
|
||||||
|
@ -1081,6 +1085,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? videoBitrate = videoStream?.BitRate;
|
int? videoBitrate = videoStream?.BitRate;
|
||||||
double? videoLevel = videoStream?.Level;
|
double? videoLevel = videoStream?.Level;
|
||||||
string videoProfile = videoStream?.Profile;
|
string videoProfile = videoStream?.Profile;
|
||||||
|
string videoRangeType = videoStream?.VideoRangeType;
|
||||||
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
|
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
|
||||||
bool? isAnamorphic = videoStream?.IsAnamorphic;
|
bool? isAnamorphic = videoStream?.IsAnamorphic;
|
||||||
bool? isInterlaced = videoStream?.IsInterlaced;
|
bool? isInterlaced = videoStream?.IsInterlaced;
|
||||||
|
@ -1098,7 +1103,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
|
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
|
||||||
|
|
||||||
var checkVideoConditions = (ProfileCondition[] conditions) =>
|
var checkVideoConditions = (ProfileCondition[] conditions) =>
|
||||||
conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
|
conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
|
||||||
|
|
||||||
// Check container conditions
|
// Check container conditions
|
||||||
var containerProfileReasons = AggregateFailureConditions(
|
var containerProfileReasons = AggregateFailureConditions(
|
||||||
|
@ -1852,6 +1857,38 @@ namespace MediaBrowser.Model.Dlna
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ProfileConditionValue.VideoRangeType:
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(qualifier))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// change from split by | to comma
|
||||||
|
// strip spaces to avoid having to encode
|
||||||
|
var values = value
|
||||||
|
.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
|
||||||
|
if (condition.Condition == ProfileConditionType.Equals)
|
||||||
|
{
|
||||||
|
item.SetOption(qualifier, "rangetype", string.Join(',', values));
|
||||||
|
}
|
||||||
|
else if (condition.Condition == ProfileConditionType.EqualsAny)
|
||||||
|
{
|
||||||
|
var currentValue = item.GetOption(qualifier, "rangetype");
|
||||||
|
if (!string.IsNullOrEmpty(currentValue) && values.Any(v => string.Equals(v, currentValue, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
item.SetOption(qualifier, "rangetype", currentValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.SetOption(qualifier, "rangetype", string.Join(',', values));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ProfileConditionValue.Height:
|
case ProfileConditionValue.Height:
|
||||||
{
|
{
|
||||||
if (!enableNonQualifiedConditions)
|
if (!enableNonQualifiedConditions)
|
||||||
|
|
|
@ -280,6 +280,29 @@ namespace MediaBrowser.Model.Dlna
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the target video range type that will be in the output stream.
|
||||||
|
/// </summary>
|
||||||
|
public string TargetVideoRangeType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsDirectStream)
|
||||||
|
{
|
||||||
|
return TargetVideoStream?.VideoRangeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetVideoCodecs = TargetVideoCodec;
|
||||||
|
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
|
||||||
|
if (!string.IsNullOrEmpty(videoCodec))
|
||||||
|
{
|
||||||
|
return GetOption(videoCodec, "rangetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
return TargetVideoStream?.VideoRangeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the target video codec tag.
|
/// Gets the target video codec tag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -72,6 +72,54 @@ namespace MediaBrowser.Model.Entities
|
||||||
/// <value>The color primaries.</value>
|
/// <value>The color primaries.</value>
|
||||||
public string ColorPrimaries { get; set; }
|
public string ColorPrimaries { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Dolby Vision version major.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The Dolby Vision version major.</value>
|
||||||
|
public int? DvVersionMajor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Dolby Vision version minor.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The Dolby Vision version minor.</value>
|
||||||
|
public int? DvVersionMinor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Dolby Vision profile.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The Dolby Vision profile.</value>
|
||||||
|
public int? DvProfile { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Dolby Vision level.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The Dolby Vision level.</value>
|
||||||
|
public int? DvLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Dolby Vision rpu present flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The Dolby Vision rpu present flag.</value>
|
||||||
|
public int? RpuPresentFlag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Dolby Vision el present flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The Dolby Vision el present flag.</value>
|
||||||
|
public int? ElPresentFlag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Dolby Vision bl present flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The Dolby Vision bl present flag.</value>
|
||||||
|
public int? BlPresentFlag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Dolby Vision bl signal compatibility id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The Dolby Vision bl signal compatibility id.</value>
|
||||||
|
public int? DvBlSignalCompatibilityId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the comment.
|
/// Gets or sets the comment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -104,32 +152,64 @@ namespace MediaBrowser.Model.Entities
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Type != MediaStreamType.Video)
|
var (videoRange, _) = GetVideoColorRange();
|
||||||
|
|
||||||
|
return videoRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the video range type.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The video range type.</value>
|
||||||
|
public string VideoRangeType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var (_, videoRangeType) = GetVideoColorRange();
|
||||||
|
|
||||||
|
return videoRangeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the video dovi title.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The video dovi title.</value>
|
||||||
|
public string VideoDoViTitle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var dvProfile = DvProfile;
|
||||||
|
var rpuPresentFlag = RpuPresentFlag == 1;
|
||||||
|
var blPresentFlag = BlPresentFlag == 1;
|
||||||
|
var dvBlCompatId = DvBlSignalCompatibilityId;
|
||||||
|
|
||||||
|
if (rpuPresentFlag
|
||||||
|
&& blPresentFlag
|
||||||
|
&& (dvProfile == 4
|
||||||
|
|| dvProfile == 5
|
||||||
|
|| dvProfile == 7
|
||||||
|
|| dvProfile == 8
|
||||||
|
|| dvProfile == 9))
|
||||||
{
|
{
|
||||||
return null;
|
var title = "DV Profile " + dvProfile;
|
||||||
|
|
||||||
|
if (dvBlCompatId > 0)
|
||||||
|
{
|
||||||
|
title += "." + dvBlCompatId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dvBlCompatId switch
|
||||||
|
{
|
||||||
|
1 => title + " (HDR10)",
|
||||||
|
2 => title + " (SDR)",
|
||||||
|
4 => title + " (HLG)",
|
||||||
|
_ => title
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var colorTransfer = ColorTransfer;
|
return null;
|
||||||
|
|
||||||
if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "HDR";
|
|
||||||
}
|
|
||||||
|
|
||||||
// For some Dolby Vision files, no color transfer is provided, so check the codec
|
|
||||||
|
|
||||||
var codecTag = CodecTag;
|
|
||||||
|
|
||||||
if (string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "HDR";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "SDR";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,5 +651,45 @@ namespace MediaBrowser.Model.Entities
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (string VideoRange, string VideoRangeType) GetVideoColorRange()
|
||||||
|
{
|
||||||
|
if (Type != MediaStreamType.Video)
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var colorTransfer = ColorTransfer;
|
||||||
|
|
||||||
|
if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ("HDR", "HDR10");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ("HDR", "HLG");
|
||||||
|
}
|
||||||
|
|
||||||
|
var codecTag = CodecTag;
|
||||||
|
var dvProfile = DvProfile;
|
||||||
|
var rpuPresentFlag = RpuPresentFlag == 1;
|
||||||
|
var blPresentFlag = BlPresentFlag == 1;
|
||||||
|
var dvBlCompatId = DvBlSignalCompatibilityId;
|
||||||
|
|
||||||
|
var isDoViHDRProfile = dvProfile == 5 || dvProfile == 7 || dvProfile == 8;
|
||||||
|
var isDoViHDRFlag = rpuPresentFlag && blPresentFlag && (dvBlCompatId == 0 || dvBlCompatId == 1 || dvBlCompatId == 4);
|
||||||
|
|
||||||
|
if ((isDoViHDRProfile && isDoViHDRFlag)
|
||||||
|
|| string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ("HDR", "DOVI");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ("SDR", "SDR");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="6.0.4" />
|
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace MediaBrowser.Model.MediaInfo
|
||||||
public static class SubtitleFormat
|
public static class SubtitleFormat
|
||||||
{
|
{
|
||||||
public const string SRT = "srt";
|
public const string SRT = "srt";
|
||||||
|
public const string SUBRIP = "subrip";
|
||||||
public const string SSA = "ssa";
|
public const string SSA = "ssa";
|
||||||
public const string ASS = "ass";
|
public const string ASS = "ass";
|
||||||
public const string VTT = "vtt";
|
public const string VTT = "vtt";
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Querying
|
namespace MediaBrowser.Model.Querying
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -7,6 +5,9 @@ namespace MediaBrowser.Model.Querying
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ItemSortBy
|
public static class ItemSortBy
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The aired episode order.
|
||||||
|
/// </summary>
|
||||||
public const string AiredEpisodeOrder = "AiredEpisodeOrder";
|
public const string AiredEpisodeOrder = "AiredEpisodeOrder";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -44,6 +45,9 @@ namespace MediaBrowser.Model.Querying
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string PremiereDate = "PremiereDate";
|
public const string PremiereDate = "PremiereDate";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The start date.
|
||||||
|
/// </summary>
|
||||||
public const string StartDate = "StartDate";
|
public const string StartDate = "StartDate";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -51,6 +55,9 @@ namespace MediaBrowser.Model.Querying
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string SortName = "SortName";
|
public const string SortName = "SortName";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name.
|
||||||
|
/// </summary>
|
||||||
public const string Name = "Name";
|
public const string Name = "Name";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -83,28 +90,69 @@ namespace MediaBrowser.Model.Querying
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string CriticRating = "CriticRating";
|
public const string CriticRating = "CriticRating";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IsFolder boolean.
|
||||||
|
/// </summary>
|
||||||
public const string IsFolder = "IsFolder";
|
public const string IsFolder = "IsFolder";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IsUnplayed boolean.
|
||||||
|
/// </summary>
|
||||||
public const string IsUnplayed = "IsUnplayed";
|
public const string IsUnplayed = "IsUnplayed";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IsPlayed boolean.
|
||||||
|
/// </summary>
|
||||||
public const string IsPlayed = "IsPlayed";
|
public const string IsPlayed = "IsPlayed";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The series sort.
|
||||||
|
/// </summary>
|
||||||
public const string SeriesSortName = "SeriesSortName";
|
public const string SeriesSortName = "SeriesSortName";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The video bitrate.
|
||||||
|
/// </summary>
|
||||||
public const string VideoBitRate = "VideoBitRate";
|
public const string VideoBitRate = "VideoBitRate";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The air time.
|
||||||
|
/// </summary>
|
||||||
public const string AirTime = "AirTime";
|
public const string AirTime = "AirTime";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The studio.
|
||||||
|
/// </summary>
|
||||||
public const string Studio = "Studio";
|
public const string Studio = "Studio";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IsFavouriteOrLiked boolean.
|
||||||
|
/// </summary>
|
||||||
public const string IsFavoriteOrLiked = "IsFavoriteOrLiked";
|
public const string IsFavoriteOrLiked = "IsFavoriteOrLiked";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last content added date.
|
||||||
|
/// </summary>
|
||||||
public const string DateLastContentAdded = "DateLastContentAdded";
|
public const string DateLastContentAdded = "DateLastContentAdded";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The series last played date.
|
||||||
|
/// </summary>
|
||||||
public const string SeriesDatePlayed = "SeriesDatePlayed";
|
public const string SeriesDatePlayed = "SeriesDatePlayed";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The parent index number.
|
||||||
|
/// </summary>
|
||||||
public const string ParentIndexNumber = "ParentIndexNumber";
|
public const string ParentIndexNumber = "ParentIndexNumber";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The index number.
|
||||||
|
/// </summary>
|
||||||
public const string IndexNumber = "IndexNumber";
|
public const string IndexNumber = "IndexNumber";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The similarity score.
|
||||||
|
/// </summary>
|
||||||
|
public const string SimilarityScore = "SimilarityScore";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ namespace MediaBrowser.Model.Session
|
||||||
|
|
||||||
// Video Constraints
|
// Video Constraints
|
||||||
VideoProfileNotSupported = 1 << 6,
|
VideoProfileNotSupported = 1 << 6,
|
||||||
|
VideoRangeTypeNotSupported = 1 << 24,
|
||||||
VideoLevelNotSupported = 1 << 7,
|
VideoLevelNotSupported = 1 << 7,
|
||||||
VideoResolutionNotSupported = 1 << 8,
|
VideoResolutionNotSupported = 1 << 8,
|
||||||
VideoBitDepthNotSupported = 1 << 9,
|
VideoBitDepthNotSupported = 1 << 9,
|
||||||
|
|
|
@ -4,6 +4,7 @@ using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.MediaInfo
|
namespace MediaBrowser.Providers.MediaInfo
|
||||||
{
|
{
|
||||||
|
@ -15,16 +16,19 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AudioResolver"/> class for external audio file processing.
|
/// Initializes a new instance of the <see cref="AudioResolver"/> class for external audio file processing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="localizationManager">The localization manager.</param>
|
/// <param name="localizationManager">The localization manager.</param>
|
||||||
/// <param name="mediaEncoder">The media encoder.</param>
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
/// <param name="fileSystem">The file system.</param>
|
/// <param name="fileSystem">The file system.</param>
|
||||||
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
|
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
|
||||||
public AudioResolver(
|
public AudioResolver(
|
||||||
|
ILogger<AudioResolver> logger,
|
||||||
ILocalizationManager localizationManager,
|
ILocalizationManager localizationManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
NamingOptions namingOptions)
|
NamingOptions namingOptions)
|
||||||
: base(
|
: base(
|
||||||
|
logger,
|
||||||
localizationManager,
|
localizationManager,
|
||||||
mediaEncoder,
|
mediaEncoder,
|
||||||
fileSystem,
|
fileSystem,
|
||||||
|
|
|
@ -47,7 +47,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
||||||
|
|
||||||
public FFProbeProvider(
|
public FFProbeProvider(
|
||||||
ILogger<FFProbeProvider> logger,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IItemRepository itemRepo,
|
IItemRepository itemRepo,
|
||||||
|
@ -59,11 +58,12 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
IChapterManager chapterManager,
|
IChapterManager chapterManager,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
NamingOptions namingOptions)
|
NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = loggerFactory.CreateLogger<FFProbeProvider>();
|
||||||
_audioResolver = new AudioResolver(localization, mediaEncoder, fileSystem, namingOptions);
|
_audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
|
||||||
_subtitleResolver = new SubtitleResolver(localization, mediaEncoder, fileSystem, namingOptions);
|
_subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
|
||||||
_videoProber = new FFProbeVideoInfo(
|
_videoProber = new FFProbeVideoInfo(
|
||||||
_logger,
|
_logger,
|
||||||
mediaSourceManager,
|
mediaSourceManager,
|
||||||
|
|
|
@ -15,6 +15,7 @@ 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 Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.MediaInfo
|
namespace MediaBrowser.Providers.MediaInfo
|
||||||
{
|
{
|
||||||
|
@ -33,6 +34,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -48,18 +50,21 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MediaInfoResolver"/> class.
|
/// Initializes a new instance of the <see cref="MediaInfoResolver"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="localizationManager">The localization manager.</param>
|
/// <param name="localizationManager">The localization manager.</param>
|
||||||
/// <param name="mediaEncoder">The media encoder.</param>
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
/// <param name="fileSystem">The file system.</param>
|
/// <param name="fileSystem">The file system.</param>
|
||||||
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
|
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
|
||||||
/// <param name="type">The <see cref="DlnaProfileType"/> of the parsed file.</param>
|
/// <param name="type">The <see cref="DlnaProfileType"/> of the parsed file.</param>
|
||||||
protected MediaInfoResolver(
|
protected MediaInfoResolver(
|
||||||
|
ILogger logger,
|
||||||
ILocalizationManager localizationManager,
|
ILocalizationManager localizationManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
NamingOptions namingOptions,
|
NamingOptions namingOptions,
|
||||||
DlnaProfileType type)
|
DlnaProfileType type)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_namingOptions = namingOptions;
|
_namingOptions = namingOptions;
|
||||||
|
@ -101,34 +106,43 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
{
|
{
|
||||||
if (!pathInfo.Path.AsSpan().EndsWith(".strm", StringComparison.OrdinalIgnoreCase))
|
if (!pathInfo.Path.AsSpan().EndsWith(".strm", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
|
try
|
||||||
|
|
||||||
if (mediaInfo.MediaStreams.Count == 1)
|
|
||||||
{
|
{
|
||||||
MediaStream mediaStream = mediaInfo.MediaStreams[0];
|
var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
|
if (mediaInfo.MediaStreams.Count == 1)
|
||||||
|| (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
|
|
||||||
{
|
{
|
||||||
mediaStream.Index = startIndex++;
|
MediaStream mediaStream = mediaInfo.MediaStreams[0];
|
||||||
mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
|
|
||||||
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
|
|
||||||
|
|
||||||
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
|
|
||||||
{
|
|
||||||
if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
|
if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
|
||||||
|| (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
|
|| (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
|
||||||
{
|
{
|
||||||
mediaStream.Index = startIndex++;
|
mediaStream.Index = startIndex++;
|
||||||
|
mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
|
||||||
|
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
|
||||||
|
|
||||||
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
|
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
|
||||||
|
{
|
||||||
|
if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
|
||||||
|
|| (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
|
||||||
|
{
|
||||||
|
mediaStream.Index = startIndex++;
|
||||||
|
|
||||||
|
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting external streams from {Path}", pathInfo.Path);
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.MediaInfo
|
namespace MediaBrowser.Providers.MediaInfo
|
||||||
{
|
{
|
||||||
|
@ -15,16 +16,19 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SubtitleResolver"/> class for external subtitle file processing.
|
/// Initializes a new instance of the <see cref="SubtitleResolver"/> class for external subtitle file processing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="localizationManager">The localization manager.</param>
|
/// <param name="localizationManager">The localization manager.</param>
|
||||||
/// <param name="mediaEncoder">The media encoder.</param>
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
/// <param name="fileSystem">The file system.</param>
|
/// <param name="fileSystem">The file system.</param>
|
||||||
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
|
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
|
||||||
public SubtitleResolver(
|
public SubtitleResolver(
|
||||||
|
ILogger<SubtitleResolver> logger,
|
||||||
ILocalizationManager localizationManager,
|
ILocalizationManager localizationManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
NamingOptions namingOptions)
|
NamingOptions namingOptions)
|
||||||
: base(
|
: base(
|
||||||
|
logger,
|
||||||
localizationManager,
|
localizationManager,
|
||||||
mediaEncoder,
|
mediaEncoder,
|
||||||
fileSystem,
|
fileSystem,
|
||||||
|
|
48
debian/conf/jellyfin.service.conf
vendored
48
debian/conf/jellyfin.service.conf
vendored
|
@ -3,5 +3,53 @@
|
||||||
# Use this file to override the user or environment file location.
|
# Use this file to override the user or environment file location.
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
# Alter the user that Jellyfin runs as
|
||||||
#User = jellyfin
|
#User = jellyfin
|
||||||
|
|
||||||
|
# Alter where environment variables are sourced from
|
||||||
#EnvironmentFile = /etc/default/jellyfin
|
#EnvironmentFile = /etc/default/jellyfin
|
||||||
|
|
||||||
|
# Service hardening options
|
||||||
|
# These were added in PR #6953 to solve issue #6952, but some combination of
|
||||||
|
# them causes "restart.sh" functionality to break with the following error:
|
||||||
|
# sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the
|
||||||
|
# 'nosuid' option set or an NFS file system without root privileges?
|
||||||
|
# See issue #7503 for details on the troubleshooting that went into this.
|
||||||
|
# Since these were added for NixOS specifically and are above and beyond
|
||||||
|
# what 99% of systemd units do, they have been moved here as optional
|
||||||
|
# additional flags to set for maximum system security and can be enabled at
|
||||||
|
# the administrator's or package maintainer's discretion.
|
||||||
|
# Uncomment these only if you know what you're doing, and doing so may cause
|
||||||
|
# bugs with in-server Restart and potentially other functionality as well.
|
||||||
|
#NoNewPrivileges=true
|
||||||
|
#SystemCallArchitectures=native
|
||||||
|
#RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||||
|
#RestrictNamespaces=false
|
||||||
|
#RestrictRealtime=true
|
||||||
|
#RestrictSUIDSGID=true
|
||||||
|
#ProtectControlGroups=false
|
||||||
|
#ProtectHostname=true
|
||||||
|
#ProtectKernelLogs=false
|
||||||
|
#ProtectKernelModules=false
|
||||||
|
#ProtectKernelTunables=false
|
||||||
|
#LockPersonality=true
|
||||||
|
#PrivateTmp=false
|
||||||
|
#PrivateDevices=false
|
||||||
|
#PrivateUsers=true
|
||||||
|
#RemoveIPC=true
|
||||||
|
#SystemCallFilter=~@clock
|
||||||
|
#SystemCallFilter=~@aio
|
||||||
|
#SystemCallFilter=~@chown
|
||||||
|
#SystemCallFilter=~@cpu-emulation
|
||||||
|
#SystemCallFilter=~@debug
|
||||||
|
#SystemCallFilter=~@keyring
|
||||||
|
#SystemCallFilter=~@memlock
|
||||||
|
#SystemCallFilter=~@module
|
||||||
|
#SystemCallFilter=~@mount
|
||||||
|
#SystemCallFilter=~@obsolete
|
||||||
|
#SystemCallFilter=~@privileged
|
||||||
|
#SystemCallFilter=~@raw-io
|
||||||
|
#SystemCallFilter=~@reboot
|
||||||
|
#SystemCallFilter=~@setuid
|
||||||
|
#SystemCallFilter=~@swap
|
||||||
|
#SystemCallErrorNumber=EPERM
|
||||||
|
|
33
debian/jellyfin.service
vendored
33
debian/jellyfin.service
vendored
|
@ -13,38 +13,5 @@ Restart = on-failure
|
||||||
TimeoutSec = 15
|
TimeoutSec = 15
|
||||||
SuccessExitStatus=0 143
|
SuccessExitStatus=0 143
|
||||||
|
|
||||||
NoNewPrivileges=true
|
|
||||||
SystemCallArchitectures=native
|
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
|
||||||
RestrictNamespaces=false
|
|
||||||
RestrictRealtime=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
ProtectControlGroups=false
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectKernelLogs=false
|
|
||||||
ProtectKernelModules=false
|
|
||||||
ProtectKernelTunables=false
|
|
||||||
LockPersonality=true
|
|
||||||
PrivateTmp=false
|
|
||||||
PrivateDevices=false
|
|
||||||
PrivateUsers=true
|
|
||||||
RemoveIPC=true
|
|
||||||
SystemCallFilter=~@clock
|
|
||||||
SystemCallFilter=~@aio
|
|
||||||
SystemCallFilter=~@chown
|
|
||||||
SystemCallFilter=~@cpu-emulation
|
|
||||||
SystemCallFilter=~@debug
|
|
||||||
SystemCallFilter=~@keyring
|
|
||||||
SystemCallFilter=~@memlock
|
|
||||||
SystemCallFilter=~@module
|
|
||||||
SystemCallFilter=~@mount
|
|
||||||
SystemCallFilter=~@obsolete
|
|
||||||
SystemCallFilter=~@privileged
|
|
||||||
SystemCallFilter=~@raw-io
|
|
||||||
SystemCallFilter=~@reboot
|
|
||||||
SystemCallFilter=~@setuid
|
|
||||||
SystemCallFilter=~@swap
|
|
||||||
SystemCallErrorNumber=EPERM
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy = multi-user.target
|
WantedBy = multi-user.target
|
||||||
|
|
|
@ -13,7 +13,7 @@ RUN yum update -yq \
|
||||||
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
|
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
|
||||||
|
|
||||||
# Install DotNET SDK
|
# Install DotNET SDK
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM fedora:33
|
FROM fedora:36
|
||||||
# Docker build arguments
|
# Docker build arguments
|
||||||
ARG SOURCE_DIR=/jellyfin
|
ARG SOURCE_DIR=/jellyfin
|
||||||
ARG ARTIFACT_DIR=/dist
|
ARG ARTIFACT_DIR=/dist
|
||||||
|
@ -9,10 +9,10 @@ ENV IS_DOCKER=YES
|
||||||
|
|
||||||
# Prepare Fedora environment
|
# Prepare Fedora environment
|
||||||
RUN dnf update -yq \
|
RUN dnf update -yq \
|
||||||
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget
|
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
|
||||||
|
|
||||||
# Install DotNET SDK
|
# Install DotNET SDK
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|
|
@ -17,7 +17,7 @@ RUN apt-get update -yqq \
|
||||||
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update -yqq \
|
||||||
mmv build-essential lsb-release
|
mmv build-essential lsb-release
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update -yqq \
|
||||||
mmv build-essential lsb-release
|
mmv build-essential lsb-release
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|
|
@ -7,14 +7,13 @@ SRPM := jellyfin-$(subst -,~,$(VERSION))-$(RELEASE)$(shell rpm --eval %dist).
|
||||||
TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
|
TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
|
||||||
|
|
||||||
epel-7-x86_64_repos := https://packages.microsoft.com/rhel/7/prod/
|
epel-7-x86_64_repos := https://packages.microsoft.com/rhel/7/prod/
|
||||||
epel-8-x86_64_repos := https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/
|
|
||||||
fedora_repos := https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/
|
fed_ver := $(shell rpm -E %fedora)
|
||||||
fedora-34-x86_64_repos := $(fedora_repos)
|
# fallback when not running on Fedora
|
||||||
fedora-35-x86_64_repos := $(fedora_repos)
|
fed_ver ?= 36
|
||||||
fedora-34-x86_64_repos := $(fedora_repos)
|
TARGET ?= fedora-$(fed_ver)-x86_64
|
||||||
|
|
||||||
outdir ?= $(PWD)/$(DIR)/
|
outdir ?= $(PWD)/$(DIR)/
|
||||||
TARGET ?= fedora-35-x86_64
|
|
||||||
|
|
||||||
srpm: $(DIR)/$(SRPM)
|
srpm: $(DIR)/$(SRPM)
|
||||||
tarball: $(DIR)/$(TARBALL)
|
tarball: $(DIR)/$(TARBALL)
|
||||||
|
|
|
@ -21,7 +21,7 @@ JELLYFIN_LOG_DIR="/var/log/jellyfin"
|
||||||
JELLYFIN_CACHE_DIR="/var/cache/jellyfin"
|
JELLYFIN_CACHE_DIR="/var/cache/jellyfin"
|
||||||
|
|
||||||
# web client path, installed by the jellyfin-web package
|
# web client path, installed by the jellyfin-web package
|
||||||
JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin-web"
|
# JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin-web"
|
||||||
|
|
||||||
# In-App service control
|
# In-App service control
|
||||||
JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh"
|
JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%global debug_package %{nil}
|
%global debug_package %{nil}
|
||||||
# Set the dotnet runtime
|
# Set the dotnet runtime
|
||||||
%if 0%{?fedora}
|
%if 0%{?fedora}
|
||||||
%global dotnet_runtime fedora-x64
|
%global dotnet_runtime fedora.%{fedora}-x64
|
||||||
%else
|
%else
|
||||||
%global dotnet_runtime centos-x64
|
%global dotnet_runtime centos-x64
|
||||||
%endif
|
%endif
|
||||||
|
@ -10,7 +10,7 @@ Name: jellyfin
|
||||||
Version: 10.8.0
|
Version: 10.8.0
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: The Free Software Media System
|
Summary: The Free Software Media System
|
||||||
License: GPLv3
|
License: GPLv2
|
||||||
URL: https://jellyfin.org
|
URL: https://jellyfin.org
|
||||||
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
|
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
|
||||||
Source0: jellyfin-server-%{version}.tar.gz
|
Source0: jellyfin-server-%{version}.tar.gz
|
||||||
|
@ -25,13 +25,16 @@ Source17: jellyfin-server-lowports.conf
|
||||||
%{?systemd_requires}
|
%{?systemd_requires}
|
||||||
BuildRequires: systemd
|
BuildRequires: systemd
|
||||||
BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel
|
BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel
|
||||||
# Requirements not packaged in main repos
|
# Requirements not packaged in RHEL 7 main repos, added via Makefile
|
||||||
# COPR @dotnet-sig/dotnet or
|
|
||||||
# https://packages.microsoft.com/rhel/7/prod/
|
# https://packages.microsoft.com/rhel/7/prod/
|
||||||
BuildRequires: dotnet-runtime-6.0, dotnet-sdk-6.0
|
BuildRequires: dotnet-runtime-6.0, dotnet-sdk-6.0
|
||||||
Requires: %{name}-server = %{version}-%{release}, %{name}-web = %{version}-%{release}
|
Requires: %{name}-server = %{version}-%{release}, %{name}-web = %{version}-%{release}
|
||||||
# Disable Automatic Dependency Processing
|
|
||||||
AutoReqProv: no
|
# Temporary (hopefully?) fix for https://github.com/jellyfin/jellyfin/issues/7471
|
||||||
|
%if 0%{?fedora} >= 36
|
||||||
|
%global __requires_exclude ^liblttng-ust\\.so\\.0.*$
|
||||||
|
%endif
|
||||||
|
|
||||||
|
|
||||||
%description
|
%description
|
||||||
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
|
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
|
||||||
|
@ -59,54 +62,74 @@ the Jellyfin server to bind to ports 80 and/or 443 for example.
|
||||||
%prep
|
%prep
|
||||||
%autosetup -n jellyfin-server-%{version} -b 0
|
%autosetup -n jellyfin-server-%{version} -b 0
|
||||||
|
|
||||||
|
|
||||||
%build
|
%build
|
||||||
|
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
export PATH=$PATH:/usr/local/bin
|
||||||
|
# cannot use --output due to https://github.com/dotnet/sdk/issues/22220
|
||||||
|
dotnet publish --configuration Release --self-contained --runtime %{dotnet_runtime} \
|
||||||
|
"-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
|
||||||
|
|
||||||
|
|
||||||
%install
|
%install
|
||||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
# Jellyfin files
|
||||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
%{__mkdir} -p %{buildroot}%{_libdir}/jellyfin %{buildroot}%{_bindir}
|
||||||
export PATH=$PATH:/usr/local/bin
|
%{__cp} -r Jellyfin.Server/bin/Release/net6.0/%{dotnet_runtime}/publish/* %{buildroot}%{_libdir}/jellyfin
|
||||||
dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
|
ln -srf %{_libdir}/jellyfin/jellyfin %{buildroot}%{_bindir}/jellyfin
|
||||||
"-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
|
%{__install} -D %{SOURCE14} %{buildroot}%{_libexecdir}/jellyfin/restart.sh
|
||||||
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
|
|
||||||
%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
|
# Jellyfin config
|
||||||
%{__install} -D -m 0644 %{SOURCE17} %{buildroot}%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf
|
%{__install} -D Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
|
||||||
%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
|
%{__install} -D %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/jellyfin
|
||||||
%{__mkdir} -p %{buildroot}%{_bindir}
|
|
||||||
tee %{buildroot}%{_bindir}/jellyfin << EOF
|
# system config
|
||||||
#!/bin/sh
|
%{__install} -D %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/jellyfin.xml
|
||||||
exec %{_libdir}/jellyfin/jellyfin \${@}
|
%{__install} -D %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/jellyfin-sudoers
|
||||||
EOF
|
%{__install} -D %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
|
||||||
|
%{__install} -D %{SOURCE11} %{buildroot}%{_unitdir}/jellyfin.service
|
||||||
|
|
||||||
|
# empty directories
|
||||||
%{__mkdir} -p %{buildroot}%{_sharedstatedir}/jellyfin
|
%{__mkdir} -p %{buildroot}%{_sharedstatedir}/jellyfin
|
||||||
%{__mkdir} -p %{buildroot}%{_sysconfdir}/jellyfin
|
%{__mkdir} -p %{buildroot}%{_sysconfdir}/jellyfin
|
||||||
%{__mkdir} -p %{buildroot}%{_var}/log/jellyfin
|
|
||||||
%{__mkdir} -p %{buildroot}%{_var}/cache/jellyfin
|
%{__mkdir} -p %{buildroot}%{_var}/cache/jellyfin
|
||||||
|
%{__mkdir} -p %{buildroot}%{_var}/log/jellyfin
|
||||||
|
|
||||||
|
# jellyfin-server-lowports subpackage
|
||||||
|
%{__install} -D -m 0644 %{SOURCE17} %{buildroot}%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf
|
||||||
|
|
||||||
%{__install} -D -m 0644 %{SOURCE11} %{buildroot}%{_unitdir}/jellyfin.service
|
|
||||||
%{__install} -D -m 0644 %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/jellyfin
|
|
||||||
%{__install} -D -m 0600 %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/jellyfin-sudoers
|
|
||||||
%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/jellyfin/restart.sh
|
|
||||||
%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/jellyfin.xml
|
|
||||||
|
|
||||||
%files
|
%files
|
||||||
# empty as this is just a meta-package
|
# empty as this is just a meta-package
|
||||||
|
|
||||||
%files server
|
%files server
|
||||||
%attr(755,root,root) %{_bindir}/jellyfin
|
%defattr(644,root,root,755)
|
||||||
%{_libdir}/jellyfin/*
|
|
||||||
|
# Jellyfin files
|
||||||
|
%{_bindir}/jellyfin
|
||||||
# Needs 755 else only root can run it since binary build by dotnet is 722
|
# Needs 755 else only root can run it since binary build by dotnet is 722
|
||||||
|
%attr(755,root,root) %{_libdir}/jellyfin/createdump
|
||||||
%attr(755,root,root) %{_libdir}/jellyfin/jellyfin
|
%attr(755,root,root) %{_libdir}/jellyfin/jellyfin
|
||||||
%{_unitdir}/jellyfin.service
|
%{_libdir}/jellyfin/*
|
||||||
%{_libexecdir}/jellyfin/restart.sh
|
%attr(755,root,root) %{_libexecdir}/jellyfin/restart.sh
|
||||||
%{_prefix}/lib/firewalld/services/jellyfin.xml
|
|
||||||
%attr(755,jellyfin,jellyfin) %dir %{_sysconfdir}/jellyfin
|
# Jellyfin config
|
||||||
|
%config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/jellyfin/logging.json
|
||||||
%config %{_sysconfdir}/sysconfig/jellyfin
|
%config %{_sysconfdir}/sysconfig/jellyfin
|
||||||
|
|
||||||
|
# system config
|
||||||
|
%{_prefix}/lib/firewalld/services/jellyfin.xml
|
||||||
|
%{_unitdir}/jellyfin.service
|
||||||
%config(noreplace) %attr(600,root,root) %{_sysconfdir}/sudoers.d/jellyfin-sudoers
|
%config(noreplace) %attr(600,root,root) %{_sysconfdir}/sudoers.d/jellyfin-sudoers
|
||||||
%config(noreplace) %{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
|
%config(noreplace) %{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
|
||||||
%config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/jellyfin/logging.json
|
|
||||||
|
# empty directories
|
||||||
%attr(750,jellyfin,jellyfin) %dir %{_sharedstatedir}/jellyfin
|
%attr(750,jellyfin,jellyfin) %dir %{_sharedstatedir}/jellyfin
|
||||||
%attr(-,jellyfin,jellyfin) %dir %{_var}/log/jellyfin
|
%attr(755,jellyfin,jellyfin) %dir %{_sysconfdir}/jellyfin
|
||||||
%attr(750,jellyfin,jellyfin) %dir %{_var}/cache/jellyfin
|
%attr(750,jellyfin,jellyfin) %dir %{_var}/cache/jellyfin
|
||||||
%{_datadir}/licenses/jellyfin/LICENSE
|
%attr(-, jellyfin,jellyfin) %dir %{_var}/log/jellyfin
|
||||||
|
|
||||||
|
%license LICENSE
|
||||||
|
|
||||||
|
|
||||||
%files server-lowports
|
%files server-lowports
|
||||||
%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf
|
%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
|
|
@ -75,6 +75,14 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
|
||||||
Assert.Equal(1, res.VideoStream.RefFrames);
|
Assert.Equal(1, res.VideoStream.RefFrames);
|
||||||
Assert.Equal("1/1000", res.VideoStream.TimeBase);
|
Assert.Equal("1/1000", res.VideoStream.TimeBase);
|
||||||
Assert.Equal(MediaStreamType.Video, res.VideoStream.Type);
|
Assert.Equal(MediaStreamType.Video, res.VideoStream.Type);
|
||||||
|
Assert.Equal(1, res.VideoStream.DvVersionMajor);
|
||||||
|
Assert.Equal(0, res.VideoStream.DvVersionMinor);
|
||||||
|
Assert.Equal(5, res.VideoStream.DvProfile);
|
||||||
|
Assert.Equal(6, res.VideoStream.DvLevel);
|
||||||
|
Assert.Equal(1, res.VideoStream.RpuPresentFlag);
|
||||||
|
Assert.Equal(0, res.VideoStream.ElPresentFlag);
|
||||||
|
Assert.Equal(1, res.VideoStream.BlPresentFlag);
|
||||||
|
Assert.Equal(0, res.VideoStream.DvBlSignalCompatibilityId);
|
||||||
|
|
||||||
Assert.Empty(res.Chapters);
|
Assert.Empty(res.Chapters);
|
||||||
Assert.Equal("Just color bars", res.Overview);
|
Assert.Equal("Just color bars", res.Overview);
|
||||||
|
|
|
@ -47,7 +47,20 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"ENCODER": "Lavc57.107.100 libx264",
|
"ENCODER": "Lavc57.107.100 libx264",
|
||||||
"DURATION": "00:00:01.000000000"
|
"DURATION": "00:00:01.000000000"
|
||||||
}
|
},
|
||||||
|
"side_data_list": [
|
||||||
|
{
|
||||||
|
"side_data_type": "DOVI configuration record",
|
||||||
|
"dv_version_major": 1,
|
||||||
|
"dv_version_minor": 0,
|
||||||
|
"dv_profile": 5,
|
||||||
|
"dv_level": 6,
|
||||||
|
"rpu_present_flag": 1,
|
||||||
|
"el_present_flag": 0,
|
||||||
|
"bl_present_flag": 1,
|
||||||
|
"dv_bl_signal_compatibility_id": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"chapters": [
|
"chapters": [
|
||||||
|
|
|
@ -13,6 +13,7 @@ using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Providers.MediaInfo;
|
using MediaBrowser.Providers.MediaInfo;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ public class AudioResolverTests
|
||||||
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex)))
|
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex)))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
_audioResolver = new AudioResolver(localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
_audioResolver = new AudioResolver(Mock.Of<ILogger<AudioResolver>>(), localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
|
@ -18,6 +18,7 @@ using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Providers.MediaInfo;
|
using MediaBrowser.Providers.MediaInfo;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
@ -70,7 +71,7 @@ public class MediaInfoResolverTests
|
||||||
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
|
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
_subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
_subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), _localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -201,7 +202,7 @@ public class MediaInfoResolverTests
|
||||||
var mediaEncoder = Mock.Of<IMediaEncoder>(MockBehavior.Strict);
|
var mediaEncoder = Mock.Of<IMediaEncoder>(MockBehavior.Strict);
|
||||||
var fileSystem = Mock.Of<IFileSystem>();
|
var fileSystem = Mock.Of<IFileSystem>();
|
||||||
|
|
||||||
var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder, fileSystem, new NamingOptions());
|
var subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), _localizationManager, mediaEncoder, fileSystem, new NamingOptions());
|
||||||
|
|
||||||
var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService.Object, false, CancellationToken.None);
|
var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService.Object, false, CancellationToken.None);
|
||||||
|
|
||||||
|
@ -306,7 +307,7 @@ public class MediaInfoResolverTests
|
||||||
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
|
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
var subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), _localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
||||||
|
|
||||||
var directoryService = GetDirectoryServiceForExternalFile(file);
|
var directoryService = GetDirectoryServiceForExternalFile(file);
|
||||||
var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None);
|
var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None);
|
||||||
|
@ -381,7 +382,7 @@ public class MediaInfoResolverTests
|
||||||
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
|
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
var subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), _localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
||||||
|
|
||||||
int startIndex = 1;
|
int startIndex = 1;
|
||||||
var streams = await subtitleResolver.GetExternalStreamsAsync(video, startIndex, directoryService.Object, false, CancellationToken.None);
|
var streams = await subtitleResolver.GetExternalStreamsAsync(video, startIndex, directoryService.Object, false, CancellationToken.None);
|
||||||
|
|
|
@ -13,6 +13,7 @@ using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Providers.MediaInfo;
|
using MediaBrowser.Providers.MediaInfo;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ public class SubtitleResolverTests
|
||||||
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex)))
|
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex)))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
_subtitleResolver = new SubtitleResolver(localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
_subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
|
Loading…
Reference in New Issue
Block a user