mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-16 08:08:16 +00:00
Compare commits
35 Commits
v10.10.6
...
release-10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aefb9b2cff | ||
|
|
abc51e89a3 | ||
|
|
c929229030 | ||
|
|
f625665cb1 | ||
|
|
79f3ce5325 | ||
|
|
6938fac73e | ||
|
|
cafa0a1e10 | ||
|
|
24242a510d | ||
|
|
2d95245405 | ||
|
|
784f6c5eff | ||
|
|
3ee7194706 | ||
|
|
a4ab5e5a14 | ||
|
|
1fff472569 | ||
|
|
91ca81eca7 | ||
|
|
78d9fa72e0 | ||
|
|
9eb2044eae | ||
|
|
7ea09a8637 | ||
|
|
069b518ab0 | ||
|
|
ae6a7acf14 | ||
|
|
64e0f9099a | ||
|
|
4f94d23011 | ||
|
|
fcdef875a2 | ||
|
|
2da4a2d753 | ||
|
|
9ae68057a7 | ||
|
|
767a5e6193 | ||
|
|
346f36bc09 | ||
|
|
1daf761aec | ||
|
|
a70a09fdb3 | ||
|
|
888838adbc | ||
|
|
72911501d3 | ||
|
|
1af7b6d348 | ||
|
|
317d7a9f4f | ||
|
|
9d9c6fe5eb | ||
|
|
ba28e3041d | ||
|
|
34f3ed0a4d |
@@ -22,7 +22,7 @@
|
||||
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
|
||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||
<PackageVersion Include="libse" Version="4.0.8" />
|
||||
<PackageVersion Include="LrcParser" Version="2024.0728.2" />
|
||||
<PackageVersion Include="LrcParser" Version="2025.228.1" />
|
||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.11" />
|
||||
@@ -80,7 +80,7 @@
|
||||
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.1" />
|
||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageVersion Include="z440.atl.core" Version="6.16.0" />
|
||||
<PackageVersion Include="z440.atl.core" Version="6.20.0" />
|
||||
<PackageVersion Include="TMDbLib" Version="2.2.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Naming</PackageId>
|
||||
<VersionPrefix>10.10.6</VersionPrefix>
|
||||
<VersionPrefix>10.10.7</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -454,6 +454,7 @@ namespace Emby.Server.Implementations.Library
|
||||
foreach (var child in children)
|
||||
{
|
||||
_itemRepository.DeleteItem(child.Id);
|
||||
_cache.TryRemove(child.Id, out _);
|
||||
}
|
||||
|
||||
_cache.TryRemove(item.Id, out _);
|
||||
@@ -2626,15 +2627,6 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
episode.ParentIndexNumber = season.IndexNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
Anime series don't generally have a season in their file name, however,
|
||||
TVDb needs a season to correctly get the metadata.
|
||||
Hence, a null season needs to be filled with something. */
|
||||
// FIXME perhaps this would be better for TVDb parser to ask for season 1 if no season is specified
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
|
||||
if (episode.ParentIndexNumber.HasValue)
|
||||
{
|
||||
|
||||
@@ -286,8 +286,10 @@ namespace Emby.Server.Implementations.Localization
|
||||
}
|
||||
|
||||
// Fairly common for some users to have "Rated R" in their rating field
|
||||
rating = rating.Replace("Rated :", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
rating = rating.Replace("Rated :", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("Rated:", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Trim();
|
||||
|
||||
// Use rating system matching the language
|
||||
if (!string.IsNullOrEmpty(countryCode))
|
||||
|
||||
@@ -116,6 +116,7 @@ public partial class AudioNormalizationTask : IScheduledTask
|
||||
{
|
||||
a.LUFS = await CalculateLUFSAsync(
|
||||
string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
|
||||
OperatingSystem.IsWindows(), // Wait for process to exit on Windows before we try deleting the concat file
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
@@ -142,7 +143,10 @@ public partial class AudioNormalizationTask : IScheduledTask
|
||||
continue;
|
||||
}
|
||||
|
||||
t.LUFS = await CalculateLUFSAsync(string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)), cancellationToken).ConfigureAwait(false);
|
||||
t.LUFS = await CalculateLUFSAsync(
|
||||
string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)),
|
||||
false,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_itemRepository.SaveItems(tracks, cancellationToken);
|
||||
@@ -162,7 +166,7 @@ public partial class AudioNormalizationTask : IScheduledTask
|
||||
];
|
||||
}
|
||||
|
||||
private async Task<float?> CalculateLUFSAsync(string inputArgs, CancellationToken cancellationToken)
|
||||
private async Task<float?> CalculateLUFSAsync(string inputArgs, bool waitForExit, CancellationToken cancellationToken)
|
||||
{
|
||||
var args = $"-hide_banner {inputArgs} -af ebur128=framelog=verbose -f null -";
|
||||
|
||||
@@ -189,18 +193,28 @@ public partial class AudioNormalizationTask : IScheduledTask
|
||||
}
|
||||
|
||||
using var reader = process.StandardError;
|
||||
float? lufs = null;
|
||||
await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
|
||||
{
|
||||
Match match = LUFSRegex().Match(line);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
|
||||
lufs = float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogError("Failed to find LUFS value in output");
|
||||
return null;
|
||||
if (lufs is null)
|
||||
{
|
||||
_logger.LogError("Failed to find LUFS value in output");
|
||||
}
|
||||
|
||||
if (waitForExit)
|
||||
{
|
||||
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return lufs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,18 +92,18 @@ public class AudioController : BaseJellyfinApiController
|
||||
[ProducesAudioFile]
|
||||
public async Task<ActionResult> GetAudioStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -114,7 +114,7 @@ public class AudioController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -133,8 +133,8 @@ public class AudioController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -259,18 +259,18 @@ public class AudioController : BaseJellyfinApiController
|
||||
[ProducesAudioFile]
|
||||
public async Task<ActionResult> GetAudioStreamByContainer(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -281,7 +281,7 @@ public class AudioController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -300,8 +300,8 @@ public class AudioController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
||||
@@ -166,18 +166,18 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[ProducesPlaylistFile]
|
||||
public async Task<ActionResult> GetLiveHlsStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -188,7 +188,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -207,8 +207,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -415,12 +415,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery, Required] string mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -431,7 +431,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -452,8 +452,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -591,12 +591,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery, Required] string mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -608,7 +608,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -627,8 +627,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -761,12 +761,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -777,7 +777,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -798,8 +798,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -933,12 +933,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -950,7 +950,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -969,8 +969,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -1106,7 +1106,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromRoute, Required] int segmentId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||
[FromQuery, Required] long runtimeTicks,
|
||||
[FromQuery, Required] long actualSegmentLengthTicks,
|
||||
[FromQuery] bool? @static,
|
||||
@@ -1114,12 +1114,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -1130,7 +1130,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -1151,8 +1151,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -1291,7 +1291,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromRoute, Required] int segmentId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||
[FromQuery, Required] long runtimeTicks,
|
||||
[FromQuery, Required] long actualSegmentLengthTicks,
|
||||
[FromQuery] bool? @static,
|
||||
@@ -1299,12 +1299,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -1316,7 +1316,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -1335,8 +1335,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
||||
@@ -1186,7 +1186,9 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesVideoFile]
|
||||
public ActionResult GetLiveStreamFile([FromRoute, Required] string streamId, [FromRoute, Required] string container)
|
||||
public ActionResult GetLiveStreamFile(
|
||||
[FromRoute, Required] string streamId,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container)
|
||||
{
|
||||
var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId);
|
||||
if (liveStreamInfo is null)
|
||||
|
||||
@@ -102,13 +102,13 @@ public class UniversalAudioController : BaseJellyfinApiController
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] int? transcodingAudioChannels,
|
||||
[FromQuery] int? maxStreamingBitrate,
|
||||
[FromQuery] int? audioBitRate,
|
||||
[FromQuery] long? startTimeTicks,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? transcodingContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? transcodingContainer,
|
||||
[FromQuery] MediaStreamProtocol? transcodingProtocol,
|
||||
[FromQuery] int? maxAudioSampleRate,
|
||||
[FromQuery] int? maxAudioBitDepth,
|
||||
|
||||
@@ -315,18 +315,18 @@ public class VideosController : BaseJellyfinApiController
|
||||
[ProducesVideoFile]
|
||||
public async Task<ActionResult> GetVideoStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -337,7 +337,7 @@ public class VideosController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -358,8 +358,8 @@ public class VideosController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -556,18 +556,18 @@ public class VideosController : BaseJellyfinApiController
|
||||
[ProducesVideoFile]
|
||||
public Task<ActionResult> GetVideoStreamByContainer(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -578,7 +578,7 @@ public class VideosController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -599,8 +599,8 @@ public class VideosController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Data</PackageId>
|
||||
<VersionPrefix>10.10.6</VersionPrefix>
|
||||
<VersionPrefix>10.10.7</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -118,15 +118,15 @@ namespace Jellyfin.Server.Extensions
|
||||
// https://github.com/dotnet/aspnetcore/blob/master/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
|
||||
// Enable debug logging on Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware to help investigate issues.
|
||||
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
|
||||
|
||||
if (config.KnownProxies.Length == 0)
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.None;
|
||||
options.KnownNetworks.Clear();
|
||||
options.KnownProxies.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
|
||||
AddProxyAddresses(config, config.KnownProxies, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Common</PackageId>
|
||||
<VersionPrefix>10.10.6</VersionPrefix>
|
||||
<VersionPrefix>10.10.7</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1200,6 +1200,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.Is4K.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.IsHD.HasValue)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -152,16 +152,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PrimaryVersionId))
|
||||
{
|
||||
var item = LibraryManager.GetItemById(PrimaryVersionId);
|
||||
if (item is Video video)
|
||||
{
|
||||
return video.MediaSourceCount;
|
||||
}
|
||||
}
|
||||
|
||||
return LinkedAlternateVersions.Length + LocalAlternateVersions.Length + 1;
|
||||
return GetMediaSourceCount(new HashSet<Guid>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,5 +541,25 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private int GetMediaSourceCount(HashSet<Guid> callstack)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PrimaryVersionId))
|
||||
{
|
||||
var item = LibraryManager.GetItemById(PrimaryVersionId);
|
||||
if (item is Video video)
|
||||
{
|
||||
if (callstack.Contains(video.Id))
|
||||
{
|
||||
return video.LinkedAlternateVersions.Length + video.LocalAlternateVersions.Length + 1;
|
||||
}
|
||||
|
||||
callstack.Add(video.Id);
|
||||
return video.GetMediaSourceCount(callstack);
|
||||
}
|
||||
}
|
||||
|
||||
return LinkedAlternateVersions.Length + LocalAlternateVersions.Length + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Controller</PackageId>
|
||||
<VersionPrefix>10.10.6</VersionPrefix>
|
||||
<VersionPrefix>10.10.7</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -35,7 +35,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
/// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
|
||||
/// This should matches all common valid codecs.
|
||||
/// </summary>
|
||||
public const string ValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
|
||||
public const string ContainerValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
|
||||
|
||||
/// <summary>
|
||||
/// The level validation regex.
|
||||
/// This regular expression matches strings representing a double.
|
||||
/// </summary>
|
||||
public const string LevelValidationRegex = @"-?[0-9]+(?:\.[0-9]+)?";
|
||||
|
||||
private const string _defaultMjpegEncoder = "mjpeg";
|
||||
|
||||
@@ -75,7 +81,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
|
||||
private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
|
||||
|
||||
private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled);
|
||||
private static readonly Regex _containerValidationRegex = new(ContainerValidationRegex, RegexOptions.Compiled);
|
||||
|
||||
private static readonly string[] _videoProfilesH264 =
|
||||
[
|
||||
@@ -450,7 +456,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return GetMjpegEncoder(state, encodingOptions);
|
||||
}
|
||||
|
||||
if (_validationRegex.IsMatch(codec))
|
||||
if (_containerValidationRegex.IsMatch(codec))
|
||||
{
|
||||
return codec.ToLowerInvariant();
|
||||
}
|
||||
@@ -491,7 +497,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
public static string GetInputFormat(string container)
|
||||
{
|
||||
if (string.IsNullOrEmpty(container) || !_validationRegex.IsMatch(container))
|
||||
if (string.IsNullOrEmpty(container) || !_containerValidationRegex.IsMatch(container))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -709,7 +715,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
var codec = state.OutputAudioCodec;
|
||||
|
||||
if (!_validationRegex.IsMatch(codec))
|
||||
if (!_containerValidationRegex.IsMatch(codec))
|
||||
{
|
||||
codec = "aac";
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
@@ -586,6 +587,7 @@ namespace MediaBrowser.Model.Dto
|
||||
/// Gets or sets the type of the media.
|
||||
/// </summary>
|
||||
/// <value>The type of the media.</value>
|
||||
[DefaultValue(MediaType.Unknown)]
|
||||
public MediaType MediaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable disable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -34,6 +35,7 @@ namespace MediaBrowser.Model.Dto
|
||||
/// Gets or sets the type.
|
||||
/// </summary>
|
||||
/// <value>The type.</value>
|
||||
[DefaultValue(PersonKind.Unknown)]
|
||||
public PersonKind Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -157,6 +157,7 @@ namespace MediaBrowser.Model.Entities
|
||||
/// Gets the video range.
|
||||
/// </summary>
|
||||
/// <value>The video range.</value>
|
||||
[DefaultValue(VideoRange.Unknown)]
|
||||
public VideoRange VideoRange
|
||||
{
|
||||
get
|
||||
@@ -171,6 +172,7 @@ namespace MediaBrowser.Model.Entities
|
||||
/// Gets the video range type.
|
||||
/// </summary>
|
||||
/// <value>The video range type.</value>
|
||||
[DefaultValue(VideoRangeType.Unknown)]
|
||||
public VideoRangeType VideoRangeType
|
||||
{
|
||||
get
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Model</PackageId>
|
||||
<VersionPrefix>10.10.6</VersionPrefix>
|
||||
<VersionPrefix>10.10.7</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
namespace MediaBrowser.Model.MediaSegments;
|
||||
@@ -21,6 +22,7 @@ public class MediaSegmentDto
|
||||
/// <summary>
|
||||
/// Gets or sets the type of content this segment defines.
|
||||
/// </summary>
|
||||
[DefaultValue(MediaSegmentType.Unknown)]
|
||||
public MediaSegmentType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
namespace MediaBrowser.Model.Search
|
||||
@@ -115,6 +116,7 @@ namespace MediaBrowser.Model.Search
|
||||
/// Gets or sets the type of the media.
|
||||
/// </summary>
|
||||
/// <value>The type of the media.</value>
|
||||
[DefaultValue(MediaType.Unknown)]
|
||||
public MediaType MediaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1010,7 +1010,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
if (replaceData || !target.PremiereDate.HasValue)
|
||||
if (replaceData || !target.PremiereDate.HasValue || (IsYearOnlyDate(target.PremiereDate.Value) && source.PremiereDate.HasValue))
|
||||
{
|
||||
target.PremiereDate = source.PremiereDate;
|
||||
}
|
||||
@@ -1142,6 +1142,8 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsYearOnlyDate(DateTime date) => date.Month == 1 && date.Day == 1;
|
||||
|
||||
private static void MergePeople(List<PersonInfo> source, List<PersonInfo> target)
|
||||
{
|
||||
if (target is null)
|
||||
@@ -1149,13 +1151,24 @@ namespace MediaBrowser.Providers.Manager
|
||||
target = new List<PersonInfo>();
|
||||
}
|
||||
|
||||
foreach (var person in target)
|
||||
{
|
||||
var normalizedName = person.Name.RemoveDiacritics();
|
||||
var personInSource = source.FirstOrDefault(i => string.Equals(i.Name.RemoveDiacritics(), normalizedName, StringComparison.OrdinalIgnoreCase));
|
||||
var sourceByName = source.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase);
|
||||
var targetByName = target.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (personInSource is not null)
|
||||
foreach (var name in targetByName.Select(g => g.Key))
|
||||
{
|
||||
var targetPeople = targetByName[name].ToArray();
|
||||
var sourcePeople = sourceByName[name].ToArray();
|
||||
|
||||
if (sourcePeople.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < targetPeople.Length; i++)
|
||||
{
|
||||
var person = targetPeople[i];
|
||||
var personInSource = i < sourcePeople.Length ? sourcePeople[i] : sourcePeople[0];
|
||||
|
||||
foreach (var providerId in personInSource.ProviderIds)
|
||||
{
|
||||
person.ProviderIds.TryAdd(providerId.Key, providerId.Value);
|
||||
@@ -1165,6 +1178,16 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
person.ImageUrl = personInSource.ImageUrl;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(personInSource.Role) && string.IsNullOrWhiteSpace(person.Role))
|
||||
{
|
||||
person.Role = personInSource.Role;
|
||||
}
|
||||
|
||||
if (personInSource.SortOrder.HasValue && !person.SortOrder.HasValue)
|
||||
{
|
||||
person.SortOrder = personInSource.SortOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,11 +170,15 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
_logger.LogWarning("File {File} only has ID3v1 tags, some fields may be truncated", audio.Path);
|
||||
}
|
||||
|
||||
track.Title = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title;
|
||||
track.Album = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album;
|
||||
track.Year = track.Year is null or 0 ? mediaInfo.ProductionYear : track.Year;
|
||||
track.TrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber;
|
||||
track.DiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber;
|
||||
// We should never use the property setter of the ATL.Track class.
|
||||
// That setter is meant for its own tag parser and external editor usage and will have unwanted side effects
|
||||
// For example, setting the Year property will also set the Date property, which is not what we want here.
|
||||
// To properly handle fallback values, we make a clone of those fields when valid.
|
||||
var trackTitle = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title;
|
||||
var trackAlbum = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album;
|
||||
var trackYear = track.Year is null or 0 ? mediaInfo.ProductionYear : track.Year;
|
||||
var trackTrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber;
|
||||
var trackDiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber;
|
||||
|
||||
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
|
||||
{
|
||||
@@ -271,22 +275,22 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
}
|
||||
|
||||
if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(track.Title))
|
||||
if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(trackTitle))
|
||||
{
|
||||
audio.Name = track.Title;
|
||||
audio.Name = trackTitle;
|
||||
}
|
||||
|
||||
if (options.ReplaceAllMetadata)
|
||||
{
|
||||
audio.Album = track.Album;
|
||||
audio.IndexNumber = track.TrackNumber;
|
||||
audio.ParentIndexNumber = track.DiscNumber;
|
||||
audio.Album = trackAlbum;
|
||||
audio.IndexNumber = trackTrackNumber;
|
||||
audio.ParentIndexNumber = trackDiscNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.Album ??= track.Album;
|
||||
audio.IndexNumber ??= track.TrackNumber;
|
||||
audio.ParentIndexNumber ??= track.DiscNumber;
|
||||
audio.Album ??= trackAlbum;
|
||||
audio.IndexNumber ??= trackTrackNumber;
|
||||
audio.ParentIndexNumber ??= trackDiscNumber;
|
||||
}
|
||||
|
||||
if (track.Date.HasValue)
|
||||
@@ -294,11 +298,12 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
audio.PremiereDate = track.Date;
|
||||
}
|
||||
|
||||
if (track.Year.HasValue)
|
||||
if (trackYear.HasValue)
|
||||
{
|
||||
var year = track.Year.Value;
|
||||
var year = trackYear.Value;
|
||||
audio.ProductionYear = year;
|
||||
|
||||
// ATL library handles such fallback this with its own internal logic, but we also need to handle it here for the ffprobe fallbacks.
|
||||
if (!audio.PremiereDate.HasValue)
|
||||
{
|
||||
try
|
||||
@@ -307,7 +312,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, track.Year);
|
||||
_logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, trackYear);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +55,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId)
|
||||
&& !string.IsNullOrEmpty(seriesImdbId)
|
||||
&& info.IndexNumber.HasValue
|
||||
&& info.ParentIndexNumber.HasValue)
|
||||
&& info.IndexNumber.HasValue)
|
||||
{
|
||||
result.HasMetadata = await _omdbProvider.FetchEpisodeData(
|
||||
result,
|
||||
info.IndexNumber.Value,
|
||||
info.ParentIndexNumber.Value,
|
||||
info.ParentIndexNumber ?? 1,
|
||||
info.GetProviderId(MetadataProvider.Imdb),
|
||||
seriesImdbId,
|
||||
info.MetadataLanguage,
|
||||
|
||||
@@ -63,10 +63,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var seasonNumber = episode.ParentIndexNumber;
|
||||
var seasonNumber = episode.ParentIndexNumber ?? 1;
|
||||
var episodeNumber = episode.IndexNumber;
|
||||
|
||||
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
|
||||
if (!episodeNumber.HasValue)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
|
||||
// TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
|
||||
var episodeResult = await _tmdbClientManager
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var stills = episodeResult?.Images?.Stills;
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// The search query must either provide an episode number or date
|
||||
if (!searchInfo.IndexNumber.HasValue || !searchInfo.ParentIndexNumber.HasValue)
|
||||
if (!searchInfo.IndexNumber.HasValue)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
@@ -96,10 +96,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
return metadataResult;
|
||||
}
|
||||
|
||||
var seasonNumber = info.ParentIndexNumber;
|
||||
var seasonNumber = info.ParentIndexNumber ?? 1;
|
||||
var episodeNumber = info.IndexNumber;
|
||||
|
||||
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
|
||||
if (!episodeNumber.HasValue)
|
||||
{
|
||||
return metadataResult;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
List<TvEpisode>? result = null;
|
||||
for (int? episode = startindex; episode <= endindex; episode++)
|
||||
{
|
||||
var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false);
|
||||
var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false);
|
||||
if (episodeInfo is not null)
|
||||
{
|
||||
(result ??= new List<TvEpisode>()).Add(episodeInfo);
|
||||
@@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
else
|
||||
{
|
||||
episodeResult = await _tmdbClientManager
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("10.10.6")]
|
||||
[assembly: AssemblyFileVersion("10.10.6")]
|
||||
[assembly: AssemblyVersion("10.10.7")]
|
||||
[assembly: AssemblyFileVersion("10.10.7")]
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Extensions</PackageId>
|
||||
<VersionPrefix>10.10.6</VersionPrefix>
|
||||
<VersionPrefix>10.10.7</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -141,8 +141,10 @@ namespace Jellyfin.Providers.Tests.Manager
|
||||
{ "ProductionYear", 1, 2 },
|
||||
{ "CommunityRating", 1.0f, 2.0f },
|
||||
{ "CriticRating", 1.0f, 2.0f },
|
||||
{ "EndDate", DateTime.UnixEpoch, DateTime.Now },
|
||||
{ "PremiereDate", DateTime.UnixEpoch, DateTime.Now },
|
||||
{ "EndDate", DateTime.UnixEpoch, DateTime.UtcNow },
|
||||
{ "PremiereDate", DateTime.UnixEpoch, DateTime.UtcNow },
|
||||
{ "PremiereDate", new DateTime(1999, 1, 1, 0, 0, 0, DateTimeKind.Utc), DateTime.UtcNow },
|
||||
{ "PremiereDate", new DateTime(2025, 2, 21, 0, 0, 0, DateTimeKind.Utc), DateTime.UtcNow },
|
||||
{ "Video3DFormat", Video3DFormat.HalfSideBySide, Video3DFormat.FullSideBySide }
|
||||
};
|
||||
|
||||
@@ -151,7 +153,15 @@ namespace Jellyfin.Providers.Tests.Manager
|
||||
public void MergeBaseItemData_SimpleField_ReplacesAppropriately(string propName, object oldValue, object newValue)
|
||||
{
|
||||
// Use type Movie to allow testing of Video3DFormat
|
||||
Assert.False(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, newValue, null, false, out _));
|
||||
if (propName.Equals("PremiereDate", StringComparison.Ordinal) && oldValue is DateTime oldDateTime)
|
||||
{
|
||||
bool expectReplaced = oldDateTime.Month == 1 && oldDateTime.Day == 1;
|
||||
Assert.Equal(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, newValue, null, false, out _), expectReplaced);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, newValue, null, false, out _));
|
||||
}
|
||||
|
||||
Assert.True(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, newValue, null, true, out _));
|
||||
Assert.True(TestMergeBaseItemData<Movie, MovieInfo>(propName, null, newValue, null, false, out _));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Localization;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -116,6 +115,10 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
|
||||
[InlineData("TV-MA", "US", 17)]
|
||||
[InlineData("XXX", "asdf", 1000)]
|
||||
[InlineData("Germany: FSK-18", "DE", 18)]
|
||||
[InlineData("Rated : R", "US", 17)]
|
||||
[InlineData("Rated: R", "US", 17)]
|
||||
[InlineData("Rated R", "US", 17)]
|
||||
[InlineData(" PG-13 ", "US", 13)]
|
||||
public async Task GetRatingLevel_GivenValidString_Success(string value, string countryCode, int expectedLevel)
|
||||
{
|
||||
var localizationManager = Setup(new ServerConfiguration()
|
||||
|
||||
Reference in New Issue
Block a user