mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-16 16:18:06 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bbdfc0d49 | ||
|
|
3c3ebe8344 | ||
|
|
8c4d23435e | ||
|
|
9e4befa52e | ||
|
|
b3efae71c0 | ||
|
|
70f4f2e8c2 | ||
|
|
cd2f2ca178 | ||
|
|
8095d39954 | ||
|
|
e1ac30ba17 | ||
|
|
3b94cfa837 | ||
|
|
2fe13f54ea | ||
|
|
eb6a6d319b | ||
|
|
b74c9cae1b | ||
|
|
24d482b36b | ||
|
|
fff4477a93 | ||
|
|
9810d22d96 | ||
|
|
5027e3cd53 | ||
|
|
9645955629 | ||
|
|
078ee1f2de | ||
|
|
236c7649dd | ||
|
|
c1e52df0b7 | ||
|
|
be949af59e | ||
|
|
122da8f447 | ||
|
|
486c7fa51e | ||
|
|
6709c80f0a | ||
|
|
3f3145600c | ||
|
|
bc613b8344 |
@@ -36,7 +36,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Naming</PackageId>
|
||||
<VersionPrefix>10.9.9</VersionPrefix>
|
||||
<VersionPrefix>10.9.11</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.TV
|
||||
}
|
||||
|
||||
string? presentationUniqueKey = null;
|
||||
int? limit = request.Limit;
|
||||
int? limit = null;
|
||||
if (!request.SeriesId.IsNullOrEmpty())
|
||||
{
|
||||
if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
|
||||
|
||||
@@ -2089,6 +2089,8 @@ public class ImageController : BaseJellyfinApiController
|
||||
Response.Headers.Append(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
|
||||
Response.Headers.Append(HeaderNames.Vary, HeaderNames.Accept);
|
||||
|
||||
Response.Headers.ContentDisposition = "attachment";
|
||||
|
||||
if (disableCaching)
|
||||
{
|
||||
Response.Headers.Append(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
|
||||
|
||||
@@ -233,6 +233,8 @@ public class PluginsController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
Response.Headers.ContentDisposition = "attachment";
|
||||
|
||||
imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath);
|
||||
return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath));
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ public class TrickplayController : BaseJellyfinApiController
|
||||
var path = _trickplayManager.GetTrickplayTilePath(item, width, index);
|
||||
if (System.IO.File.Exists(path))
|
||||
{
|
||||
Response.Headers.ContentDisposition = "attachment";
|
||||
return PhysicalFile(path, MediaTypeNames.Image.Jpeg);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Data</PackageId>
|
||||
<VersionPrefix>10.9.9</VersionPrefix>
|
||||
<VersionPrefix>10.9.11</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Common</PackageId>
|
||||
<VersionPrefix>10.9.9</VersionPrefix>
|
||||
<VersionPrefix>10.9.11</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1180,28 +1180,29 @@ namespace MediaBrowser.Controller.Entities
|
||||
return info;
|
||||
}
|
||||
|
||||
private string GetMediaSourceName(BaseItem item)
|
||||
internal string GetMediaSourceName(BaseItem item)
|
||||
{
|
||||
var terms = new List<string>();
|
||||
|
||||
var path = item.Path;
|
||||
if (item.IsFileProtocol && !string.IsNullOrEmpty(path))
|
||||
{
|
||||
var displayName = System.IO.Path.GetFileNameWithoutExtension(path);
|
||||
if (HasLocalAlternateVersions)
|
||||
{
|
||||
var displayName = System.IO.Path.GetFileNameWithoutExtension(path)
|
||||
.Replace(System.IO.Path.GetFileName(ContainingFolderPath), string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.TrimStart(new char[] { ' ', '-' });
|
||||
|
||||
if (!string.IsNullOrEmpty(displayName))
|
||||
var containingFolderName = System.IO.Path.GetFileName(ContainingFolderPath);
|
||||
if (displayName.Length > containingFolderName.Length && displayName.StartsWith(containingFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
terms.Add(displayName);
|
||||
var name = displayName.AsSpan(containingFolderName.Length).TrimStart([' ', '-']);
|
||||
if (!name.IsWhiteSpace())
|
||||
{
|
||||
terms.Add(name.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (terms.Count == 0)
|
||||
{
|
||||
var displayName = System.IO.Path.GetFileNameWithoutExtension(path);
|
||||
terms.Add(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,10 +180,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
}
|
||||
|
||||
public string FindSeriesPresentationUniqueKey()
|
||||
{
|
||||
var series = Series;
|
||||
return series is null ? null : series.PresentationUniqueKey;
|
||||
}
|
||||
=> Series?.PresentationUniqueKey;
|
||||
|
||||
public string FindSeasonName()
|
||||
{
|
||||
|
||||
@@ -430,8 +430,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
InternalItemsQuery query,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
var user = query.User;
|
||||
|
||||
// This must be the last filter
|
||||
if (!query.AdjacentTo.IsNullOrEmpty())
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Controller</PackageId>
|
||||
<VersionPrefix>10.9.9</VersionPrefix>
|
||||
<VersionPrefix>10.9.11</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1313,7 +1313,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
// Apply aac_adtstoasc bitstream filter when media source is in mpegts.
|
||||
if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
|
||||
&& (string.Equals(mediaSourceContainer, "mpegts", StringComparison.OrdinalIgnoreCase)
|
||||
&& (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
@@ -2005,7 +2005,26 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
|
||||
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
|
||||
profile = WhiteSpaceRegex().Replace(profile, string.Empty);
|
||||
profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
|
||||
|
||||
var videoProfiles = Array.Empty<string>();
|
||||
if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
videoProfiles = _videoProfilesH264;
|
||||
}
|
||||
else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
videoProfiles = _videoProfilesH265;
|
||||
}
|
||||
else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
videoProfiles = _videoProfilesAv1;
|
||||
}
|
||||
|
||||
if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = string.Empty;
|
||||
}
|
||||
|
||||
// We only transcode to HEVC 8-bit for now, force Main Profile.
|
||||
if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
@@ -89,7 +89,8 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
string outputPath,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var shouldExtractOneByOne = mediaSource.MediaAttachments.Any(a => a.FileName.Contains('/', StringComparison.OrdinalIgnoreCase) || a.FileName.Contains('\\', StringComparison.OrdinalIgnoreCase));
|
||||
var shouldExtractOneByOne = mediaSource.MediaAttachments.Any(a => !string.IsNullOrEmpty(a.FileName)
|
||||
&& (a.FileName.Contains('/', StringComparison.OrdinalIgnoreCase) || a.FileName.Contains('\\', StringComparison.OrdinalIgnoreCase)));
|
||||
if (shouldExtractOneByOne)
|
||||
{
|
||||
var attachmentIndexes = mediaSource.MediaAttachments.Select(a => a.Index);
|
||||
@@ -283,7 +284,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
|
||||
if (extractableAttachmentIds.Count > 0)
|
||||
{
|
||||
await CacheAllAttachmentsInternal(mediaPath, inputFile, mediaSource, extractableAttachmentIds, cancellationToken).ConfigureAwait(false);
|
||||
await CacheAllAttachmentsInternal(mediaPath, _mediaEncoder.GetInputArgument(inputFile, mediaSource), mediaSource, extractableAttachmentIds, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -322,7 +323,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
|
||||
processArgs += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" -i \"{0}\" -t 0 -f null null",
|
||||
" -i {0} -t 0 -f null null",
|
||||
inputFile);
|
||||
|
||||
int exitCode;
|
||||
|
||||
@@ -168,6 +168,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
private readonly string _encoderPath;
|
||||
|
||||
private readonly Version _minFFmpegMultiThreadedCli = new Version(7, 0);
|
||||
|
||||
public EncoderValidator(ILogger logger, string encoderPath)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -477,8 +479,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly Version _minFFmpegMultiThreadedCli = new Version(7, 0);
|
||||
|
||||
public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyDesc))
|
||||
|
||||
@@ -625,7 +625,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
try
|
||||
{
|
||||
return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, targetFormat, cancellationToken).ConfigureAwait(false);
|
||||
return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, targetFormat, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
@@ -637,7 +637,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
}
|
||||
|
||||
return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, targetFormat, cancellationToken).ConfigureAwait(false);
|
||||
return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, targetFormat, isAudio, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string GetImageResolutionParameter()
|
||||
@@ -663,7 +663,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return imageResolutionParameter;
|
||||
}
|
||||
|
||||
private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, ImageFormat? targetFormat, CancellationToken cancellationToken)
|
||||
private async Task<string> ExtractImageInternal(
|
||||
string inputPath,
|
||||
string container,
|
||||
MediaStream videoStream,
|
||||
int? imageStreamIndex,
|
||||
Video3DFormat? threedFormat,
|
||||
TimeSpan? offset,
|
||||
bool useIFrame,
|
||||
ImageFormat? targetFormat,
|
||||
bool isAudio,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(inputPath);
|
||||
|
||||
@@ -722,7 +732,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
var vf = string.Join(',', filters);
|
||||
var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
|
||||
var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2}{5} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads, GetImageResolutionParameter());
|
||||
var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2}{5} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads, isAudio ? string.Empty : GetImageResolutionParameter());
|
||||
|
||||
if (offset.HasValue)
|
||||
{
|
||||
@@ -1197,7 +1207,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
|
||||
// Generate concat configuration entries for each file and write to file
|
||||
using StreamWriter sw = new StreamWriter(concatFilePath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(concatFilePath));
|
||||
using StreamWriter sw = new FormattingStreamWriter(concatFilePath, CultureInfo.InvariantCulture);
|
||||
foreach (var path in files)
|
||||
{
|
||||
var mediaInfoResult = GetMediaInfo(
|
||||
|
||||
@@ -501,11 +501,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
List<MediaStream> subtitleStreams,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputPath = mediaSource.Path;
|
||||
var inputPath = _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource);
|
||||
var outputPaths = new List<string>();
|
||||
var args = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"-i \"{0}\" -copyts",
|
||||
"-i {0} -copyts",
|
||||
inputPath);
|
||||
|
||||
foreach (var subtitleStream in subtitleStreams)
|
||||
@@ -676,7 +676,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
var processArgs = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"-i \"{0}\" -copyts -map 0:{1} -an -vn -c:s {2} \"{3}\"",
|
||||
"-i {0} -copyts -map 0:{1} -an -vn -c:s {2} \"{3}\"",
|
||||
inputPath,
|
||||
subtitleStreamIndex,
|
||||
outputCodec,
|
||||
|
||||
@@ -858,7 +858,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
audioStream = directAudioStream;
|
||||
playlistItem.AudioStreamIndex = audioStream.Index;
|
||||
playlistItem.AudioCodecs = new[] { audioStream.Codec };
|
||||
playlistItem.AudioCodecs = audioCodecs = new[] { audioStream.Codec };
|
||||
|
||||
// Copy matching audio codec options
|
||||
playlistItem.AudioSampleRate = audioStream.SampleRate;
|
||||
@@ -897,18 +897,18 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Video &&
|
||||
i.ContainsAnyCodec(videoStream?.Codec, container) &&
|
||||
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;
|
||||
i.ContainsAnyCodec(videoCodecs, container) &&
|
||||
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)))
|
||||
// Reverse codec profiles for backward compatibility - first codec profile has higher priority
|
||||
.Reverse();
|
||||
|
||||
foreach (var i in appliedVideoConditions)
|
||||
{
|
||||
var transcodingVideoCodecs = ContainerProfile.SplitValue(videoCodec);
|
||||
foreach (var transcodingVideoCodec in transcodingVideoCodecs)
|
||||
foreach (var transcodingVideoCodec in videoCodecs)
|
||||
{
|
||||
if (i.ContainsAnyCodec(transcodingVideoCodec, container))
|
||||
{
|
||||
ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, isFirstAppliedCodecProfile);
|
||||
isFirstAppliedCodecProfile = false;
|
||||
ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, true);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -929,18 +929,18 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
var appliedAudioConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.VideoAudio &&
|
||||
i.ContainsAnyCodec(audioStream?.Codec, container) &&
|
||||
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
|
||||
isFirstAppliedCodecProfile = true;
|
||||
i.ContainsAnyCodec(audioCodecs, container) &&
|
||||
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)))
|
||||
// Reverse codec profiles for backward compatibility - first codec profile has higher priority
|
||||
.Reverse();
|
||||
|
||||
foreach (var codecProfile in appliedAudioConditions)
|
||||
{
|
||||
var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec);
|
||||
foreach (var transcodingAudioCodec in transcodingAudioCodecs)
|
||||
foreach (var transcodingAudioCodec in audioCodecs)
|
||||
{
|
||||
if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container))
|
||||
{
|
||||
ApplyTranscodingConditions(playlistItem, codecProfile.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile);
|
||||
isFirstAppliedCodecProfile = false;
|
||||
ApplyTranscodingConditions(playlistItem, codecProfile.Conditions, transcodingAudioCodec, true, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Model</PackageId>
|
||||
<VersionPrefix>10.9.9</VersionPrefix>
|
||||
<VersionPrefix>10.9.11</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -211,8 +211,12 @@ namespace MediaBrowser.Providers.TV
|
||||
}
|
||||
else if (existingSeason.IsVirtualItem)
|
||||
{
|
||||
existingSeason.IsVirtualItem = false;
|
||||
await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
|
||||
var episodeCount = seriesChildren.OfType<Episode>().Count(e => e.ParentIndexNumber == seasonNumber && !e.IsMissingEpisode);
|
||||
if (episodeCount > 0)
|
||||
{
|
||||
existingSeason.IsVirtualItem = false;
|
||||
await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("10.9.9")]
|
||||
[assembly: AssemblyFileVersion("10.9.9")]
|
||||
[assembly: AssemblyVersion("10.9.11")]
|
||||
[assembly: AssemblyFileVersion("10.9.11")]
|
||||
|
||||
@@ -102,7 +102,8 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
"astc",
|
||||
"ktx",
|
||||
"pkm",
|
||||
"wbmp"
|
||||
"wbmp",
|
||||
"avif"
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
38
src/Jellyfin.Extensions/FormattingStreamWriter.cs
Normal file
38
src/Jellyfin.Extensions/FormattingStreamWriter.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Jellyfin.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// A custom StreamWriter which supports setting a IFormatProvider.
|
||||
/// </summary>
|
||||
public class FormattingStreamWriter : StreamWriter
|
||||
{
|
||||
private readonly IFormatProvider _formatProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FormattingStreamWriter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to write to.</param>
|
||||
/// <param name="formatProvider">The format provider to use.</param>
|
||||
public FormattingStreamWriter(Stream stream, IFormatProvider formatProvider)
|
||||
: base(stream)
|
||||
{
|
||||
_formatProvider = formatProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FormattingStreamWriter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="path">The complete file path to write to.</param>
|
||||
/// <param name="formatProvider">The format provider to use.</param>
|
||||
public FormattingStreamWriter(string path, IFormatProvider formatProvider)
|
||||
: base(path)
|
||||
{
|
||||
_formatProvider = formatProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IFormatProvider FormatProvider
|
||||
=> _formatProvider;
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Extensions</PackageId>
|
||||
<VersionPrefix>10.9.9</VersionPrefix>
|
||||
<VersionPrefix>10.9.11</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace Jellyfin.LiveTv.Listings
|
||||
Overview = program.Description,
|
||||
ProductionYear = program.CopyrightDate?.Year,
|
||||
SeasonNumber = program.Episode.Series,
|
||||
IsSeries = program.Episode.Series is not null,
|
||||
IsSeries = program.Episode.Episode is not null,
|
||||
IsRepeat = program.IsPreviouslyShown && !program.IsNew,
|
||||
IsPremiere = program.Premiere is not null,
|
||||
IsKids = programCategories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Controller.Tests.Entities;
|
||||
@@ -14,4 +17,30 @@ public class BaseItemTests
|
||||
[InlineData("1test 2", "0000000001test 0000000002")]
|
||||
public void BaseItem_ModifySortChunks_Valid(string input, string expected)
|
||||
=> Assert.Equal(expected, BaseItem.ModifySortChunks(input));
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Movies/Ted/Ted.mp4", "/Movies/Ted/Ted - Unrated Edition.mp4", "Ted", "Unrated Edition")]
|
||||
[InlineData("/Movies/Deadpool 2 (2018)/Deadpool 2 (2018).mkv", "/Movies/Deadpool 2 (2018)/Deadpool 2 (2018) - Super Duper Cut.mkv", "Deadpool 2 (2018)", "Super Duper Cut")]
|
||||
public void GetMediaSourceName_Valid(string primaryPath, string altPath, string name, string altName)
|
||||
{
|
||||
var mediaSourceManager = new Mock<IMediaSourceManager>();
|
||||
mediaSourceManager.Setup(x => x.GetPathProtocol(It.IsAny<string>()))
|
||||
.Returns((string x) => MediaProtocol.File);
|
||||
BaseItem.MediaSourceManager = mediaSourceManager.Object;
|
||||
|
||||
var video = new Video()
|
||||
{
|
||||
Path = primaryPath
|
||||
};
|
||||
|
||||
var videoAlt = new Video()
|
||||
{
|
||||
Path = altPath,
|
||||
};
|
||||
|
||||
video.LocalAlternateVersions = [videoAlt.Path];
|
||||
|
||||
Assert.Equal(name, video.GetMediaSourceName(video));
|
||||
Assert.Equal(altName, video.GetMediaSourceName(videoAlt));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Extensions.Tests;
|
||||
|
||||
public static class FormattingStreamWriterTests
|
||||
{
|
||||
[Fact]
|
||||
public static void Shuffle_Valid_Correct()
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE", false);
|
||||
using (var ms = new MemoryStream())
|
||||
using (var txt = new FormattingStreamWriter(ms, CultureInfo.InvariantCulture))
|
||||
{
|
||||
txt.Write("{0}", 3.14159);
|
||||
txt.Close();
|
||||
Assert.Equal("3.14159", Encoding.UTF8.GetString(ms.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user