Merge branch 'jellyfin:master' into master

pull/8137/head
Negulici-R. Barnabas 2 years ago committed by GitHub
commit 1e41636e30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -157,6 +157,7 @@
- [jonas-resch](https://github.com/jonas-resch)
- [vgambier](https://github.com/vgambier)
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
- [RealGreenDragon](https://github.com/RealGreenDragon)
# Emby Contributors
@ -225,3 +226,4 @@
- [gnuyent](https://github.com/gnuyent)
- [Matthew Jones](https://github.com/matthew-jones-uk)
- [Jakob Kukla](https://github.com/jakobkukla)
- [Utku Özdemir](https://github.com/utkuozdemir)

@ -464,7 +464,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
var isPhotosCollection = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)
|| string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase);
if (!isPhotosCollection && result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name));

@ -120,5 +120,8 @@
"Forced": "강제하기",
"Default": "기본 설정",
"TaskOptimizeDatabaseDescription": "데이터베이스를 압축하고 사용 가능한 공간을 늘립니다. 라이브러리를 검색한 후 이 작업을 실행하거나 데이터베이스 수정같은 비슷한 작업을 수행하면 성능이 향상될 수 있습니다.",
"TaskOptimizeDatabase": "데이터베이스 최적화"
"TaskOptimizeDatabase": "데이터베이스 최적화",
"TaskKeyframeExtractorDescription": "비디오 파일에서 키프레임을 추출하여 더 정확한 HLS 재생 목록을 만듭니다. 이 작업은 오랫동안 진행될 수 있습니다.",
"TaskKeyframeExtractor": "키프레임 추출",
"External": "외부"
}

@ -43,9 +43,9 @@ namespace Emby.Server.Implementations.TV
}
string presentationUniqueKey = null;
if (!string.IsNullOrEmpty(query.SeriesId))
if (query.SeriesId.HasValue && !query.SeriesId.Value.Equals(default))
{
if (_libraryManager.GetItemById(query.SeriesId) is Series series)
if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series)
{
presentationUniqueKey = GetUniqueSeriesKey(series);
}
@ -93,9 +93,9 @@ namespace Emby.Server.Implementations.TV
string presentationUniqueKey = null;
int? limit = null;
if (!string.IsNullOrEmpty(request.SeriesId))
if (request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default))
{
if (_libraryManager.GetItemById(request.SeriesId) is Series series)
if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
{
presentationUniqueKey = GetUniqueSeriesKey(series);
limit = 1;
@ -153,7 +153,7 @@ namespace Emby.Server.Implementations.TV
// If viewing all next up for all series, remove first episodes
// But if that returns empty, keep those first episodes (avoid completely empty view)
var alwaysEnableFirstEpisode = !string.IsNullOrEmpty(request.SeriesId);
var alwaysEnableFirstEpisode = request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default);
var anyFound = false;
return allNextUp

@ -1,6 +1,7 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@ -9,6 +10,7 @@ using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@ -32,6 +34,7 @@ namespace Jellyfin.Api.Controllers
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
private readonly IDtoService _dtoService;
private readonly IAuthorizationContext _authContext;
private readonly ILogger<ItemsController> _logger;
private readonly ISessionManager _sessionManager;
@ -42,6 +45,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
public ItemsController(
@ -49,6 +53,7 @@ namespace Jellyfin.Api.Controllers
ILibraryManager libraryManager,
ILocalizationManager localization,
IDtoService dtoService,
IAuthorizationContext authContext,
ILogger<ItemsController> logger,
ISessionManager sessionManager)
{
@ -56,6 +61,7 @@ namespace Jellyfin.Api.Controllers
_libraryManager = libraryManager;
_localization = localization;
_dtoService = dtoService;
_authContext = authContext;
_logger = logger;
_sessionManager = sessionManager;
}
@ -63,7 +69,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Gets items based on a query.
/// </summary>
/// <param name="userId">The user id supplied as query parameter.</param>
/// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param>
/// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
/// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
/// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
@ -151,15 +157,15 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
[HttpGet("Items")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetItems(
[FromQuery] Guid userId,
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetItems(
[FromQuery] Guid? userId,
[FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong,
[FromQuery] bool? hasThemeVideo,
[FromQuery] bool? hasSubtitles,
[FromQuery] bool? hasSpecialFeature,
[FromQuery] bool? hasTrailer,
[FromQuery] string? adjacentTo,
[FromQuery] Guid? adjacentTo,
[FromQuery] int? parentIndexNumber,
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,
@ -238,7 +244,19 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
// if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
var user = !auth.IsApiKey && userId.HasValue && !userId.Value.Equals(default)
? _userManager.GetUserById(userId.Value)
: null;
// beyond this point, we're either using an api key or we have a valid user
if (!auth.IsApiKey && user is null)
{
return BadRequest("userId is required");
}
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@ -270,30 +288,39 @@ namespace Jellyfin.Api.Controllers
includeItemTypes = new[] { BaseItemKind.Playlist };
}
var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
var enabledChannels = auth.IsApiKey
? Array.Empty<Guid>()
: user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
bool isInEnabledFolder = Array.IndexOf(user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
// api keys are always enabled for all folders
bool isInEnabledFolder = auth.IsApiKey
|| Array.IndexOf(user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
// Assume all folders inside an EnabledChannel are enabled
|| Array.IndexOf(enabledChannels, item.Id) != -1
// Assume all items inside an EnabledChannel are enabled
|| Array.IndexOf(enabledChannels, item.ChannelId) != -1;
var collectionFolders = _libraryManager.GetCollectionFolders(item);
foreach (var collectionFolder in collectionFolders)
if (!isInEnabledFolder)
{
if (user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
var collectionFolders = _libraryManager.GetCollectionFolders(item);
foreach (var collectionFolder in collectionFolders)
{
isInEnabledFolder = true;
// api keys never enter this block, so user is never null
if (user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
{
isInEnabledFolder = true;
}
}
}
// api keys are always enabled for all folders, so user is never null
if (item is not UserRootFolder
&& !isInEnabledFolder
&& !user.HasPermission(PermissionKind.EnableAllFolders)
&& !user!.HasPermission(PermissionKind.EnableAllFolders)
&& !user.HasPermission(PermissionKind.EnableAllChannels)
&& !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user.Username, item.Name);
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
}
@ -606,7 +633,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
[HttpGet("Users/{userId}/Items")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetItemsByUserId(
public Task<ActionResult<QueryResult<BaseItemDto>>> GetItemsByUserId(
[FromRoute] Guid userId,
[FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong,
@ -614,7 +641,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasSubtitles,
[FromQuery] bool? hasSpecialFeature,
[FromQuery] bool? hasTrailer,
[FromQuery] string? adjacentTo,
[FromQuery] Guid? adjacentTo,
[FromQuery] int? parentIndexNumber,
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,

@ -79,7 +79,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[Description("Gets search hints based on a search term")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<SearchHintResult> Get(
public ActionResult<SearchHintResult> GetSearchHints(
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] Guid? userId,
@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers
IndexNumber = item.IndexNumber,
ParentIndexNumber = item.ParentIndexNumber,
Id = item.Id,
Type = item.GetClientTypeName(),
Type = item.GetBaseItemKind(),
MediaType = item.MediaType,
MatchedTerm = hintInfo.MatchedTerm,
RunTimeTicks = item.RunTimeTicks,
@ -149,8 +149,10 @@ namespace Jellyfin.Api.Controllers
EndDate = item.EndDate
};
// legacy
#pragma warning disable CS0618
// Kept for compatibility with older clients
result.ItemId = result.Id;
#pragma warning restore CS0618
if (item.IsFolder)
{

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
@ -31,7 +32,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Finds movies and trailers similar to a given trailer.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param>
/// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
/// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
/// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
@ -118,15 +119,15 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the trailers.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
[FromQuery] Guid userId,
public Task<ActionResult<QueryResult<BaseItemDto>>> GetTrailers(
[FromQuery] Guid? userId,
[FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong,
[FromQuery] bool? hasThemeVideo,
[FromQuery] bool? hasSubtitles,
[FromQuery] bool? hasSpecialFeature,
[FromQuery] bool? hasTrailer,
[FromQuery] string? adjacentTo,
[FromQuery] Guid? adjacentTo,
[FromQuery] int? parentIndexNumber,
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,

@ -77,7 +77,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? seriesId,
[FromQuery] Guid? seriesId,
[FromQuery] Guid? parentId,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
@ -206,7 +206,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? season,
[FromQuery] Guid? seasonId,
[FromQuery] bool? isMissing,
[FromQuery] string? adjacentTo,
[FromQuery] Guid? adjacentTo,
[FromQuery] Guid? startItemId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
@ -278,9 +278,9 @@ namespace Jellyfin.Api.Controllers
}
// This must be the last filter
if (!string.IsNullOrEmpty(adjacentTo))
if (adjacentTo.HasValue && !adjacentTo.Value.Equals(default))
{
episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo).ToList();
episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList();
}
if (string.Equals(sortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
@ -326,7 +326,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? isSpecialSeason,
[FromQuery] bool? isMissing,
[FromQuery] string? adjacentTo,
[FromQuery] Guid? adjacentTo,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,

@ -860,7 +860,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
if (!string.IsNullOrEmpty(query.AdjacentTo))
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
Logger.LogDebug("Query requires post-filtering due to AdjacentTo");
return true;
@ -1029,9 +1029,9 @@ namespace MediaBrowser.Controller.Entities
#pragma warning restore CA1309
// This must be the last filter
if (!string.IsNullOrEmpty(query.AdjacentTo))
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo);
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting);

@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Entities
public Guid[] ExcludeItemIds { get; set; }
public string? AdjacentTo { get; set; }
public Guid? AdjacentTo { get; set; }
public string[] PersonTypes { get; set; }

@ -433,9 +433,9 @@ namespace MediaBrowser.Controller.Entities
var user = query.User;
// This must be the last filter
if (!string.IsNullOrEmpty(query.AdjacentTo))
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
items = FilterForAdjacency(items.ToList(), query.AdjacentTo);
items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return SortAndPage(items, totalRecordLimit, query, libraryManager, true);
@ -985,10 +985,9 @@ namespace MediaBrowser.Controller.Entities
return _userViewManager.GetUserSubView(parent.Id, type, localizationKey, sortName);
}
public static IEnumerable<BaseItem> FilterForAdjacency(List<BaseItem> list, string adjacentToId)
public static IEnumerable<BaseItem> FilterForAdjacency(List<BaseItem> list, Guid adjacentTo)
{
var adjacentToIdGuid = new Guid(adjacentToId);
var adjacentToItem = list.FirstOrDefault(i => i.Id.Equals(adjacentToIdGuid));
var adjacentToItem = list.FirstOrDefault(i => i.Id.Equals(adjacentTo));
var index = list.IndexOf(adjacentToItem);
@ -1005,7 +1004,7 @@ namespace MediaBrowser.Controller.Entities
nextId = list[index + 1].Id;
}
return list.Where(i => i.Id.Equals(previousId) || i.Id.Equals(nextId) || i.Id.Equals(adjacentToIdGuid));
return list.Where(i => i.Id.Equals(previousId) || i.Id.Equals(nextId) || i.Id.Equals(adjacentTo));
}
}
}

@ -574,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw;
}
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
if (!ranToCompletion)
{

@ -33,7 +33,7 @@ namespace MediaBrowser.Model.Querying
/// Gets or sets the series id.
/// </summary>
/// <value>The series id.</value>
public string SeriesId { get; set; }
public Guid? SeriesId { get; set; }
/// <summary>
/// Gets or sets the start index. Use for paging.

@ -1,8 +1,6 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.Search
{
@ -11,12 +9,28 @@ namespace MediaBrowser.Model.Search
/// </summary>
public class SearchHint
{
/// <summary>
/// Initializes a new instance of the <see cref="SearchHint" /> class.
/// </summary>
public SearchHint()
{
Name = string.Empty;
MatchedTerm = string.Empty;
MediaType = string.Empty;
Artists = Array.Empty<string>();
}
/// <summary>
/// Gets or sets the item id.
/// </summary>
/// <value>The item id.</value>
[Obsolete("Use Id instead")]
public Guid ItemId { get; set; }
/// <summary>
/// Gets or sets the item id.
/// </summary>
/// <value>The item id.</value>
public Guid Id { get; set; }
/// <summary>
@ -53,38 +67,42 @@ namespace MediaBrowser.Model.Search
/// Gets or sets the image tag.
/// </summary>
/// <value>The image tag.</value>
public string PrimaryImageTag { get; set; }
public string? PrimaryImageTag { get; set; }
/// <summary>
/// Gets or sets the thumb image tag.
/// </summary>
/// <value>The thumb image tag.</value>
public string ThumbImageTag { get; set; }
public string? ThumbImageTag { get; set; }
/// <summary>
/// Gets or sets the thumb image item identifier.
/// </summary>
/// <value>The thumb image item identifier.</value>
public string ThumbImageItemId { get; set; }
public string? ThumbImageItemId { get; set; }
/// <summary>
/// Gets or sets the backdrop image tag.
/// </summary>
/// <value>The backdrop image tag.</value>
public string BackdropImageTag { get; set; }
public string? BackdropImageTag { get; set; }
/// <summary>
/// Gets or sets the backdrop image item identifier.
/// </summary>
/// <value>The backdrop image item identifier.</value>
public string BackdropImageItemId { get; set; }
public string? BackdropImageItemId { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public string Type { get; set; }
public BaseItemKind Type { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is folder.
/// </summary>
/// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
public bool? IsFolder { get; set; }
/// <summary>
@ -99,31 +117,47 @@ namespace MediaBrowser.Model.Search
/// <value>The type of the media.</value>
public string MediaType { get; set; }
/// <summary>
/// Gets or sets the start date.
/// </summary>
/// <value>The start date.</value>
public DateTime? StartDate { get; set; }
/// <summary>
/// Gets or sets the end date.
/// </summary>
/// <value>The end date.</value>
public DateTime? EndDate { get; set; }
/// <summary>
/// Gets or sets the series.
/// </summary>
/// <value>The series.</value>
public string Series { get; set; }
public string? Series { get; set; }
public string Status { get; set; }
/// <summary>
/// Gets or sets the status.
/// </summary>
/// <value>The status.</value>
public string? Status { get; set; }
/// <summary>
/// Gets or sets the album.
/// </summary>
/// <value>The album.</value>
public string Album { get; set; }
public string? Album { get; set; }
public Guid AlbumId { get; set; }
/// <summary>
/// Gets or sets the album id.
/// </summary>
/// <value>The album id.</value>
public Guid? AlbumId { get; set; }
/// <summary>
/// Gets or sets the album artist.
/// </summary>
/// <value>The album artist.</value>
public string AlbumArtist { get; set; }
public string? AlbumArtist { get; set; }
/// <summary>
/// Gets or sets the artists.
@ -147,13 +181,13 @@ namespace MediaBrowser.Model.Search
/// Gets or sets the channel identifier.
/// </summary>
/// <value>The channel identifier.</value>
public Guid ChannelId { get; set; }
public Guid? ChannelId { get; set; }
/// <summary>
/// Gets or sets the name of the channel.
/// </summary>
/// <value>The name of the channel.</value>
public string ChannelName { get; set; }
public string? ChannelName { get; set; }
/// <summary>
/// Gets or sets the primary image aspect ratio.

Loading…
Cancel
Save