mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-08 08:48:48 +01:00
Merge remote-tracking branch 'upstream/master' into search-rebased
# Conflicts: # Emby.Server.Implementations/Library/LibraryManager.cs # Jellyfin.Server.Implementations/Item/PeopleRepository.cs # MediaBrowser.Controller/Library/ILibraryManager.cs # MediaBrowser.Controller/Persistence/IPeopleRepository.cs
This commit is contained in:
@@ -57,6 +57,14 @@ namespace MediaBrowser.Controller.Collections
|
||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||
IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collections accessible to the supplied user that contain the provided item.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="itemId">The item identifier.</param>
|
||||
/// <returns>The collections containing the item.</returns>
|
||||
IEnumerable<BoxSet> GetCollectionsContainingItem(User user, Guid itemId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder where collections are stored.
|
||||
/// </summary>
|
||||
|
||||
@@ -94,6 +94,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
private string _name;
|
||||
|
||||
private string _originalLanguage;
|
||||
|
||||
public const char SlugChar = '-';
|
||||
|
||||
protected BaseItem()
|
||||
@@ -217,7 +219,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
public string OriginalTitle { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string OriginalLanguage { get; set; }
|
||||
public string OriginalLanguage
|
||||
{
|
||||
get => _originalLanguage;
|
||||
set => _originalLanguage = LocalizationManager?.FindLanguageInfo(value)?.TwoLetterISOLanguageName ?? value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
@@ -1564,7 +1570,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the preferred metadata language.
|
||||
/// Gets the preferred metadata country code.
|
||||
/// </summary>
|
||||
/// <returns>System.String.</returns>
|
||||
public string GetPreferredMetadataCountryCode()
|
||||
@@ -1598,6 +1604,15 @@ namespace MediaBrowser.Controller.Entities
|
||||
return lang;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original language of the item, inheriting from parent items if necessary.
|
||||
/// </summary>
|
||||
/// <returns>System.String.</returns>
|
||||
public virtual string GetInheritedOriginalLanguage()
|
||||
{
|
||||
return OriginalLanguage;
|
||||
}
|
||||
|
||||
public virtual bool IsSaveLocalMetadataEnabled()
|
||||
{
|
||||
if (SourceType == SourceType.Channel)
|
||||
|
||||
@@ -153,6 +153,12 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
return 16.0 / 9;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetInheritedOriginalLanguage()
|
||||
{
|
||||
return OriginalLanguage ?? Series?.GetInheritedOriginalLanguage();
|
||||
}
|
||||
|
||||
public override List<string> GetUserDataKeys()
|
||||
{
|
||||
var list = base.GetUserDataKeys();
|
||||
|
||||
@@ -128,6 +128,12 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetInheritedOriginalLanguage()
|
||||
{
|
||||
return OriginalLanguage ?? Series?.GetInheritedOriginalLanguage();
|
||||
}
|
||||
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
if (IndexNumber.HasValue)
|
||||
|
||||
@@ -278,6 +278,17 @@ namespace MediaBrowser.Controller.Entities
|
||||
return linkedVersionCount + localVersionCount + 1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetInheritedOriginalLanguage()
|
||||
{
|
||||
if (ExtraType.GetValueOrDefault() == Model.Entities.ExtraType.Trailer)
|
||||
{
|
||||
return GetOwner()?.GetInheritedOriginalLanguage();
|
||||
}
|
||||
|
||||
return OriginalLanguage ?? GetOwner()?.GetInheritedOriginalLanguage();
|
||||
}
|
||||
|
||||
public override List<string> GetUserDataKeys()
|
||||
{
|
||||
var list = base.GetUserDataKeys();
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Library;
|
||||
|
||||
/// <summary>
|
||||
/// A local similar items provider that supports batch queries across multiple source items.
|
||||
/// Implementations share access filtering and entity loading across all sources for better performance.
|
||||
/// </summary>
|
||||
public interface IBatchLocalSimilarItemsProvider : ISimilarItemsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets similar items for multiple source items in a single batch.
|
||||
/// </summary>
|
||||
/// <param name="sourceItems">The source items to find similar items for.</param>
|
||||
/// <param name="query">The query options.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Per-source-item results keyed by source item ID.</returns>
|
||||
Task<Dictionary<Guid, IReadOnlyList<BaseItem>>> GetBatchSimilarItemsAsync(
|
||||
IReadOnlyList<BaseItem> sourceItems,
|
||||
SimilarItemsQuery query,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -598,12 +598,13 @@ namespace MediaBrowser.Controller.Library
|
||||
IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the people names per item for a batch of item IDs in a single DB round-trip.
|
||||
/// Gets distinct people names for multiple items.
|
||||
/// </summary>
|
||||
/// <param name="itemIds">The item IDs to look up.</param>
|
||||
/// <param name="personTypes">Optional person types to include. Empty for all.</param>
|
||||
/// <returns>Dictionary keyed by item id; values are the per-item people names. Items with no people are absent.</returns>
|
||||
IReadOnlyDictionary<Guid, IReadOnlyList<string>> GetPeopleNamesByItem(IReadOnlyList<Guid> itemIds, IReadOnlyList<string> personTypes);
|
||||
/// <param name="itemIds">The item IDs.</param>
|
||||
/// <param name="personTypes">The person types to include.</param>
|
||||
/// <param name="limit">Maximum number of names.</param>
|
||||
/// <returns>The distinct people names.</returns>
|
||||
IReadOnlyList<string> GetPeopleNamesByItems(IReadOnlyList<Guid> itemIds, IReadOnlyList<string> personTypes, int limit);
|
||||
|
||||
/// <summary>
|
||||
/// Queries the items.
|
||||
|
||||
@@ -6,6 +6,7 @@ using Jellyfin.Database.Implementations.Entities;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace MediaBrowser.Controller.Library;
|
||||
|
||||
@@ -47,4 +48,23 @@ public interface ISimilarItemsManager
|
||||
int? limit,
|
||||
LibraryOptions? libraryOptions,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Builds movie recommendations for a user: a mix of similar-items and person-based categories,
|
||||
/// scheduled round-robin and capped to <paramref name="categoryLimit"/>.
|
||||
/// </summary>
|
||||
/// <param name="user">The user the recommendations are for. May be <see langword="null"/> for anonymous access.</param>
|
||||
/// <param name="parentId">The library/folder to localize the search to. Pass <see cref="Guid.Empty"/> to use the root.</param>
|
||||
/// <param name="categoryLimit">Maximum number of recommendation categories to return.</param>
|
||||
/// <param name="itemLimit">Maximum number of items per category.</param>
|
||||
/// <param name="dtoOptions">DTO options used when querying the library.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The list of recommendation categories, ordered by <see cref="RecommendationType"/>.</returns>
|
||||
Task<IReadOnlyList<SimilarItemsRecommendation>> GetMovieRecommendationsAsync(
|
||||
User? user,
|
||||
Guid parentId,
|
||||
int categoryLimit,
|
||||
int itemLimit,
|
||||
DtoOptions dtoOptions,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace MediaBrowser.Controller.Library;
|
||||
|
||||
/// <summary>
|
||||
/// A recommendation category derived from a baseline item, holding similar items prior to DTO conversion.
|
||||
/// </summary>
|
||||
public sealed class SimilarItemsRecommendation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the display name of the baseline item the recommendation is based on.
|
||||
/// </summary>
|
||||
public required string BaselineItemName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an identifier for the recommendation category.
|
||||
/// </summary>
|
||||
public required Guid CategoryId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recommendation type.
|
||||
/// </summary>
|
||||
public required RecommendationType RecommendationType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the similar items for the baseline, ordered by relevance.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<BaseItem> Items { get; init; }
|
||||
}
|
||||
@@ -86,6 +86,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
|
||||
private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
|
||||
private readonly Version _minFFmpegReadrateCatchupOption = new Version(8, 0);
|
||||
private readonly Version _minFFmpegNoiseBsfDrop = new Version(5, 0);
|
||||
|
||||
private static readonly string[] _videoProfilesH264 =
|
||||
[
|
||||
@@ -1547,20 +1548,61 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
|
||||
{
|
||||
var bitStreamArgs = string.Empty;
|
||||
var filters = new List<string>();
|
||||
|
||||
var noiseFilter = GetCopiedAudioTrimBsf(state);
|
||||
if (!string.IsNullOrEmpty(noiseFilter))
|
||||
{
|
||||
filters.Add(noiseFilter);
|
||||
}
|
||||
|
||||
var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
|
||||
|
||||
// Apply aac_adtstoasc bitstream filter when media source is in mpegts.
|
||||
if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
|
||||
&& (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
|
||||
|| string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase))
|
||||
&& IsAAC(state.AudioStream))
|
||||
{
|
||||
bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
|
||||
bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
|
||||
filters.Add("aac_adtstoasc");
|
||||
}
|
||||
|
||||
return bitStreamArgs;
|
||||
return filters.Count == 0
|
||||
? string.Empty
|
||||
: " -bsf:a " + string.Join(',', filters);
|
||||
}
|
||||
|
||||
// When video is transcoded, accurate_seek (the default) trims video to the
|
||||
// exact seek point via decoder-side frame discard. But stream-copied audio
|
||||
// bypasses the decoder, so it starts from the nearest keyframe — potentially
|
||||
// seconds before the target. Use the noise bsf to drop copied audio packets
|
||||
// before the seek target, achieving the same trim precision without
|
||||
// re-encoding. The noise bsf's drop= parameter requires ffmpeg >= 5.0.
|
||||
// Important: make sure not to use it with wtv because it breaks seeking
|
||||
private string GetCopiedAudioTrimBsf(EncodingJobInfo state)
|
||||
{
|
||||
if (state.TranscodingType is not TranscodingJobType.Hls
|
||||
|| !state.IsVideoRequest
|
||||
|| IsCopyCodec(state.OutputVideoCodec)
|
||||
|| !IsCopyCodec(state.OutputAudioCodec)
|
||||
|| string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
|
||||
|| _mediaEncoder.EncoderVersion < _minFFmpegNoiseBsfDrop)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var startTicks = state.BaseRequest.StartTimeTicks ?? 0;
|
||||
if (startTicks <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var seekSeconds = startTicks / (double)TimeSpan.TicksPerSecond;
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"noise=drop='lt(pts*tb\\,{0:F3})'",
|
||||
seekSeconds);
|
||||
}
|
||||
|
||||
public static string GetSegmentFileExtension(string segmentContainer)
|
||||
@@ -2014,11 +2056,15 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
args += keyFrameArg + gopArg;
|
||||
}
|
||||
|
||||
// global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
|
||||
// The in-band Parameter Sets generated by the AMD HEVC VA-API encoder is inconsistent
|
||||
// with the extradata generated by ffmpeg, causing decoding failures when using hvc1.
|
||||
if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
&& _mediaEncoder.IsVaapiDeviceAmd)
|
||||
{
|
||||
args += " -flags:v -global_header";
|
||||
// Extracting the extradata from the in-band PS to bypass the issue.
|
||||
// This can be removed once the issue is resolved in libva or Mesa.
|
||||
// Transcoding is unavoidable here, so using BSF will not conflict with BSF in remuxing.
|
||||
args += " -flags:v -global_header -bsf:v extract_extradata=remove=0";
|
||||
}
|
||||
|
||||
return args;
|
||||
@@ -3002,23 +3048,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
|
||||
seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekTick));
|
||||
|
||||
if (state.IsVideoRequest)
|
||||
{
|
||||
// If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the nearest
|
||||
// keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking to
|
||||
// avoid A/V sync issues which cause playback issues on some devices.
|
||||
// When remuxing video, the segment start times correspond to key frames in the source stream, so this
|
||||
// option shouldn't change the seeked point that much.
|
||||
// Important: make sure not to use it with wtv because it breaks seeking
|
||||
if (state.TranscodingType is TranscodingJobType.Hls
|
||||
&& string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)
|
||||
&& (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec))
|
||||
&& !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
seekParam += " -noaccurate_seek";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seekParam;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using LinkedChildType = MediaBrowser.Controller.Entities.LinkedChildType;
|
||||
|
||||
@@ -29,8 +30,9 @@ public interface ILinkedChildrenService
|
||||
/// Gets parent IDs that reference the specified child with LinkedChildType.Manual.
|
||||
/// </summary>
|
||||
/// <param name="childId">The child item ID.</param>
|
||||
/// <param name="parentType">Optional parent item type filter.</param>
|
||||
/// <returns>List of parent IDs that reference the child.</returns>
|
||||
IReadOnlyList<Guid> GetManualLinkedParentIds(Guid childId);
|
||||
IReadOnlyList<Guid> GetManualLinkedParentIds(Guid childId, BaseItemKind? parentType = null);
|
||||
|
||||
/// <summary>
|
||||
/// Updates LinkedChildren references from one child to another.
|
||||
|
||||
@@ -34,12 +34,11 @@ public interface IPeopleRepository
|
||||
IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the people names per item for a batch of item IDs, preserving per-item list order.
|
||||
/// One database round-trip for the whole batch; grouped by item id in memory.
|
||||
/// Items with no people are omitted from the returned dictionary.
|
||||
/// Gets distinct people names for multiple items efficiently by querying from the mapping table.
|
||||
/// </summary>
|
||||
/// <param name="itemIds">The item IDs to get people for.</param>
|
||||
/// <param name="personTypes">Optional person types to include (e.g. "Actor", "Director"). Empty for all.</param>
|
||||
/// <returns>Dictionary keyed by item id; values are the per-item people names.</returns>
|
||||
IReadOnlyDictionary<Guid, IReadOnlyList<string>> GetPeopleNamesByItem(IReadOnlyList<Guid> itemIds, IReadOnlyList<string> personTypes);
|
||||
/// <param name="personTypes">The person types to include (e.g. "Actor", "Director").</param>
|
||||
/// <param name="limit">Maximum number of names to return.</param>
|
||||
/// <returns>The distinct people names.</returns>
|
||||
IReadOnlyList<string> GetPeopleNamesByItems(IReadOnlyList<Guid> itemIds, IReadOnlyList<string> personTypes, int limit);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user