This commit is contained in:
TheMelmacian 2024-04-25 09:15:45 -04:00 committed by GitHub
commit 6ac7d5e304
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 305 additions and 56 deletions

View File

@ -177,6 +177,8 @@
- [Chris-Codes-It](https://github.com/Chris-Codes-It)
- [Pithaya](https://github.com/Pithaya)
- [Çağrı Sakaoğlu](https://github.com/ilovepilav)
- [TheMelmacian](https://github.com/TheMelmacian)
_ [Barasingha](https://github.com/MaVdbussche)
- [Barasingha](https://github.com/MaVdbussche)
- [Gauvino](https://github.com/Gauvino)
- [felix920506](https://github.com/felix920506)

View File

@ -408,6 +408,10 @@ namespace Emby.Server.Implementations.Data
// resume
"create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)",
// used to effectively link items when building item tree
"create index if not exists idx_TypedBaseItemsOwnerId on TypedBaseItems(OwnerId)",
"create index if not exists idx_TypedBaseItemsPrimaryVersionId on TypedBaseItems(PrimaryVersionId)",
// items by name
"create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)",
"create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)",
@ -497,7 +501,7 @@ namespace Emby.Server.Implementations.Data
AddColumn(connection, "TypedBaseItems", "ExternalId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ShowId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "OwnerId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "OwnerId", "GUID", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Width", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Height", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Size", "BIGINT", existingColumnNames);
@ -3868,41 +3872,9 @@ namespace Emby.Server.Implementations.Data
}
}
if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)");
statement?.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)");
statement?.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)");
statement?.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)");
statement?.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
}
if (query.HasSubtitles.HasValue)
{
if (query.HasSubtitles.Value)
{
whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' limit 1) not null)");
}
else
{
whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' limit 1) is null)");
}
}
// if no filters are present for MediaStreams the method returns (1=1)
// which can be added to the where clause without changing the result and no null or empty check is needed
whereClauses.Add(GetMediaStreamsFiltersSubquery(query, statement));
if (query.HasChapterImages.HasValue)
{
@ -4316,6 +4288,115 @@ namespace Emby.Server.Implementations.Data
return whereClauses;
}
private string GetMediaStreamsFiltersSubquery(InternalItemsQuery query, SqliteCommand? statement)
{
var mediaStreamsQuery = @"
WITH RECURSIVE items AS (
SELECT
tbi.guid AS CollectionId,
tbi.""type"" AS CollectionType,
0 AS ""Level"",
tbi.Guid AS ItemId,
tbi.""type"" AS ItemType,
tbi.PresentationUniqueKey as ItemPresentationUniqueKey,
tbi.PrimaryVersionId as ItemPrimaryVersionId,
null AS CollectionParent
FROM TypedBaseItems tbi
WHERE tbi.guid = A.Guid
UNION ALL
SELECT
CollectionId,
CollectionType,
""Level"" + 1 AS ""Level"",
child.Guid AS ItemId,
child.""type"" AS ItemType,
child.PresentationUniqueKey AS ItemPresentationUniqueKey,
child.PrimaryVersionId as ItemPrimaryVersionId,
i.ItemId AS CollectionParent
FROM TypedBaseItems child
JOIN items i
ON child.ParentId = i.ItemId
OR (child.PrimaryVersionId = i.ItemPresentationUniqueKey AND i.ItemPrimaryVersionId IS NULL)
OR (child.OwnerId = i.ItemId AND child.ExtraType IS NULL)
)
";
var mediaStreamsFilters = new List<string>();
if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
{
mediaStreamsFilters.Add("SELECT CASE WHEN NOT EXISTS (select 1 from items join mediastreams ms ON ms.ItemId = items.ItemId AND ms.StreamType = 'Audio' AND ms.Language = @HasNoAudioTrackWithLanguage LIMIT 1) THEN TRUE ELSE FALSE END AS StreamFilterMatches");
statement?.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
{
mediaStreamsFilters.Add("SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM items join mediastreams ms ON ms.ItemId = items.ItemId AND ms.StreamType = 'Subtitle' AND ms.IsExternal = 0 AND ms.Language = @HasNoInternalSubtitleTrackWithLanguage LIMIT 1) THEN TRUE ELSE FALSE END AS StreamFilterMatches");
statement?.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
{
mediaStreamsFilters.Add("SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM items join mediastreams ms ON ms.ItemId = items.ItemId AND ms.StreamType = 'Subtitle' AND ms.IsExternal = 1 AND ms.Language = @HasNoExternalSubtitleTrackWithLanguage LIMIT 1) THEN TRUE ELSE FALSE END AS StreamFilterMatches");
statement?.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
{
mediaStreamsFilters.Add("SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM items join mediastreams ms ON ms.ItemId = items.ItemId AND ms.StreamType = 'Subtitle' AND ms.Language = @HasNoSubtitleTrackWithLanguage LIMIT 1) THEN TRUE ELSE FALSE END AS StreamFilterMatches");
statement?.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
}
if (query.HasSubtitles.HasValue)
{
if (query.HasSubtitles.Value)
{
mediaStreamsFilters.Add("SELECT CASE WHEN EXISTS (SELECT 1 FROM items JOIN mediastreams ms ON ms.ItemId = items.ItemId AND ms.StreamType = 'Subtitle' limit 1) THEN TRUE ELSE FALSE END AS StreamFilterMatches");
}
else
{
mediaStreamsFilters.Add("SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM items JOIN mediastreams ms ON ms.ItemId = items.ItemId AND ms.StreamType = 'Subtitle' limit 1) THEN TRUE ELSE FALSE END AS StreamFilterMatches");
}
}
if (query.AudioLanguage.Length > 0)
{
var languages = string.Join(", ", query.AudioLanguage.Select((lang, index) => $"@AudioLanguage_{index}"));
var undefinedLanguage = query.AudioLanguage.Contains("und") ? "or ms.Language is null" : string.Empty; // language with null value is handled as unddefined
mediaStreamsFilters.Add("SELECT CASE WHEN EXISTS (SELECT 1 FROM items JOIN MediaStreams ms ON ms.ItemId = items.ItemId AND ms.StreamType = 'Audio' AND (ms.Language in (" + languages + ") " + undefinedLanguage + ") limit 1) THEN TRUE ELSE FALSE END AS StreamFilterMatches");
var i = 0;
foreach (var lang in query.AudioLanguage)
{
statement?.TryBind($"@AudioLanguage_{i++}", lang);
}
}
if (query.SubtitleLanguage.Length > 0)
{
var languages = string.Join(", ", query.SubtitleLanguage.Select((lang, index) => $"@SubtitleLanguage_{index}"));
var undefinedLanguage = query.SubtitleLanguage.Contains("und") ? "or ms.Language is null" : string.Empty; // language with null value is handled as unddefined
mediaStreamsFilters.Add("SELECT CASE WHEN EXISTS (SELECT 1 FROM items JOIN MediaStreams ms ON ms.ItemId = items.ItemId AND ms.StreamType = 'Subtitle' AND (ms.Language in (" + languages + ") " + undefinedLanguage + ") limit 1) THEN TRUE ELSE FALSE END AS StreamFilterMatches");
var i = 0;
foreach (var lang in query.SubtitleLanguage)
{
statement?.TryBind($"@SubtitleLanguage_{i++}", lang);
}
}
if (mediaStreamsFilters.Count > 0)
{
return "(NOT EXISTS (" + mediaStreamsQuery + " SELECT 1 FROM (" + string.Join(" UNION ", mediaStreamsFilters) + ") WHERE StreamFilterMatches = FALSE LIMIT 1))";
}
else
{
return "(1=1)"; // -> resolves always to true; can be added to the where clause without null or empty check
}
}
/// <summary>
/// Formats a where clause for the specified provider.
/// </summary>
@ -4903,7 +4984,9 @@ AND Type = @InternalPersonType)");
NameContains = query.NameContains,
SearchTerm = query.SearchTerm,
SimilarTo = query.SimilarTo,
ExcludeItemIds = query.ExcludeItemIds
ExcludeItemIds = query.ExcludeItemIds,
AudioLanguage = query.AudioLanguage,
SubtitleLanguage = query.SubtitleLanguage
};
var outerWhereClauses = GetWhereClauses(outerQuery, null);

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
@ -8,6 +9,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@ -89,6 +91,8 @@ public class FilterController : BaseJellyfinApiController
}
var itemList = folder.GetItemList(query);
var mediaStreams = GetMediaStreams(itemList, BaseItemKind.Movie, BaseItemKind.Episode).ToList();
return new QueryFiltersLegacy
{
Years = itemList.Select(i => i.ProductionYear ?? -1)
@ -113,6 +117,20 @@ public class FilterController : BaseJellyfinApiController
.Where(i => !string.IsNullOrWhiteSpace(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
.Order()
.ToArray(),
AudioLanguages = mediaStreams
.Where(mediaStream => mediaStream.Type == MediaStreamType.Audio)
.Select(mediaStream => string.IsNullOrWhiteSpace(mediaStream.Language) ? "und" : mediaStream.Language)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Order()
.ToArray(),
SubtitleLanguages = mediaStreams
.Where(mediaStream => mediaStream.Type == MediaStreamType.Subtitle)
.Select(mediaStream => string.IsNullOrWhiteSpace(mediaStream.Language) ? "und" : mediaStream.Language)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Order()
.ToArray()
};
}
@ -215,4 +233,32 @@ public class FilterController : BaseJellyfinApiController
return filters;
}
/// <summary>
/// Extracts the MediaStreams from the list of BaseItems.
/// If an item is a Folder, MediaStreams are searched in the list of it's child items.
/// </summary>
/// <param name="itemList">The list of items for which the MediaStreams should be xtracted.</param>
/// <param name="types">Optional list of item kinds for which the MediaStreams should be extracted.</param>
/// <returns>A list of all MediaStreams which are linked to the provided items.</returns>
private IEnumerable<MediaStream> GetMediaStreams(IEnumerable<BaseItem> itemList, params BaseItemKind[] types)
{
return itemList
.Where(item => item.IsFolder || types.Length == 0 || types.Contains(item.GetBaseItemKind()))
.SelectMany(item =>
{
if (item.IsFolder)
{
return GetMediaStreams(((Folder)item).Children, types);
}
else if (item is Video)
{
return ((Video)item).GetAllLinkedMediaStreams();
}
else
{
return item.GetMediaStreams();
}
});
}
}

View File

@ -151,6 +151,8 @@ public class ItemsController : BaseJellyfinApiController
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
/// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
/// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
/// <param name="audioLanguages">Optional. If specified, results will be filtered based on audio language. This allows multiple, comma delimited values.</param>
/// <param name="subtitleLanguages">Optional. If specified, results will be filtered based on subtitale language. This allows multiple, comma delimited values.</param>
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
@ -240,6 +242,8 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] string? nameLessThan,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] audioLanguages,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] subtitleLanguages,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
@ -368,6 +372,8 @@ public class ItemsController : BaseJellyfinApiController
MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(),
MinPremiereDate = minPremiereDate?.ToUniversalTime(),
MaxPremiereDate = maxPremiereDate?.ToUniversalTime(),
AudioLanguage = audioLanguages,
SubtitleLanguage = subtitleLanguages,
};
if (ids.Length != 0 || !string.IsNullOrWhiteSpace(searchTerm))
@ -608,6 +614,8 @@ public class ItemsController : BaseJellyfinApiController
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
/// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
/// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
/// <param name="audioLanguages">Optional. If specified, results will be filtered based on audio language. This allows multiple, comma delimited values.</param>
/// <param name="subtitleLanguages">Optional. If specified, results will be filtered based on subtitale language. This allows multiple, comma delimited values.</param>
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
@ -699,6 +707,8 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] string? nameLessThan,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] audioLanguages,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] subtitleLanguages,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
=> GetItems(
@ -785,6 +795,8 @@ public class ItemsController : BaseJellyfinApiController
nameLessThan,
studioIds,
genreIds,
audioLanguages,
subtitleLanguages,
enableTotalRecordCount,
enableImages);

View File

@ -112,6 +112,8 @@ public class TrailersController : BaseJellyfinApiController
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
/// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
/// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
/// <param name="audioLanguages">Optional. If specified, results will be filtered based on audio language. This allows multiple, comma delimited values.</param>
/// <param name="subtitleLanguages">Optional. If specified, results will be filtered based on subtitale language. This allows multiple, comma delimited values.</param>
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the trailers.</returns>
@ -200,6 +202,8 @@ public class TrailersController : BaseJellyfinApiController
[FromQuery] string? nameLessThan,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] audioLanguages,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] subtitleLanguages,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
@ -290,6 +294,8 @@ public class TrailersController : BaseJellyfinApiController
nameLessThan,
studioIds,
genreIds,
audioLanguages,
subtitleLanguages,
enableTotalRecordCount,
enableImages);
}

View File

@ -44,7 +44,8 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.FixPlaylistOwner),
typeof(Routines.MigrateRatingLevels),
typeof(Routines.AddDefaultCastReceivers),
typeof(Routines.UpdateDefaultPluginRepository)
typeof(Routines.UpdateDefaultPluginRepository),
typeof(Routines.ChangeTypeOfTypedBaseItemsOwnerIdToGuid)
};
/// <summary>

View File

@ -0,0 +1,96 @@
using System;
using System.Globalization;
using System.IO;
using Emby.Server.Implementations.Data;
using MediaBrowser.Controller;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Migrations.Routines
{
/// <summary>
/// Change type of TypedBaseItems.OwnerId to GUID so database can use index when compared to guid column.
/// </summary>
internal class ChangeTypeOfTypedBaseItemsOwnerIdToGuid : IMigrationRoutine
{
private const string DbFilename = "library.db";
private readonly ILogger<ChangeTypeOfTypedBaseItemsOwnerIdToGuid> _logger;
private readonly IServerApplicationPaths _paths;
public ChangeTypeOfTypedBaseItemsOwnerIdToGuid(ILogger<ChangeTypeOfTypedBaseItemsOwnerIdToGuid> logger, IServerApplicationPaths paths)
{
_logger = logger;
_paths = paths;
}
/// <inheritdoc/>
public Guid Id => Guid.Parse("{9ACD7444-568D-4E20-89A1-B0E0D94023AC}");
/// <inheritdoc/>
public string Name => "ChangeTypeOfTypedBaseItemsOwnerIdToGuid";
/// <inheritdoc/>
public bool PerformOnNewInstall => false;
/// <inheritdoc/>
public void Perform()
{
var dataPath = _paths.DataPath;
var dbPath = Path.Combine(dataPath, DbFilename);
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
{
// Back up the database before column is changed
for (int i = 1; ; i++)
{
var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
if (!File.Exists(bakPath))
{
try
{
File.Copy(dbPath, bakPath);
_logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
throw;
}
}
}
// Determine the type of TypedBaseItems.OwnerId
_logger.LogInformation("Determine type of column TypedBaseItems.OwnerId.");
var result = connection.Query("SELECT type FROM pragma_table_info('TypedBaseItems') WHERE name = 'OwnerId'").GetEnumerator();
if (result.MoveNext())
{
var row = result.Current;
// If the type is TEXT change it to GUID so the database can use indexes when OwnerId is compared to guid column
if (row.TryGetString(0, out var columnType) && columnType != "GUID")
{
_logger.LogInformation("Type of column TypedBaseItems.OwnerId is {ColumnType} -> changing to GUID.", columnType);
// use separate connections for alter table commands to prevent sqlite "database table is locked" exception
using (var alterTableConnection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
using (var transaction = alterTableConnection.BeginTransaction())
{
alterTableConnection.Execute("DROP INDEX IF EXISTS idx_TypedBaseItemsOwnerId");
alterTableConnection.Execute("ALTER TABLE TypedBaseItems RENAME COLUMN OwnerId TO OwnerId_OLD");
alterTableConnection.Execute("ALTER TABLE TypedBaseItems ADD COLUMN OwnerId GUID NULL");
alterTableConnection.Execute("UPDATE TypedBaseItems SET OwnerId = OwnerId_OLD WHERE OwnerId_OLD IS NOT NULL");
alterTableConnection.Execute("ALTER TABLE TypedBaseItems DROP COLUMN OwnerId_OLD");
alterTableConnection.Execute("CREATE INDEX idx_TypedBaseItemsOwnerId ON TypedBaseItems(OwnerId)");
transaction.Commit();
}
}
else
{
// Do nothing if the column is already of type GUID
_logger.LogInformation("Type of column TypedBaseItems.OwnerId is GUID -> no migration necessary");
}
}
}
}
}
}

View File

@ -825,12 +825,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
if (query.HasSubtitles.HasValue)
{
Logger.LogDebug("Query requires post-filtering due to HasSubtitles");
return true;
}
if (query.HasTrailer.HasValue)
{
Logger.LogDebug("Query requires post-filtering due to HasTrailer");

View File

@ -51,6 +51,8 @@ namespace MediaBrowser.Controller.Entities
TrailerTypes = Array.Empty<TrailerType>();
VideoTypes = Array.Empty<VideoType>();
Years = Array.Empty<int>();
AudioLanguage = Array.Empty<string>();
SubtitleLanguage = Array.Empty<string>();
}
public InternalItemsQuery(User? user)
@ -358,6 +360,10 @@ namespace MediaBrowser.Controller.Entities
public string? SeriesTimerId { get; set; }
public string[] AudioLanguage { get; set; }
public string[] SubtitleLanguage { get; set; }
public void SetUser(User user)
{
MaxParentalRating = user.MaxParentalAgeRating;

View File

@ -683,18 +683,6 @@ namespace MediaBrowser.Controller.Entities
}
}
if (query.HasSubtitles.HasValue)
{
var val = query.HasSubtitles.Value;
var video = item as Video;
if (video is null || val != video.HasSubtitles)
{
return false;
}
}
if (query.HasParentalRating.HasValue)
{
var val = query.HasParentalRating.Value;

View File

@ -379,6 +379,15 @@ namespace MediaBrowser.Controller.Entities
.OrderBy(i => i.SortName);
}
/// <summary>
/// Gets the MediaStreams for this Video and all alternate Versions (linked and local).
/// </summary>
/// <returns>A list of all MediaStreams which are linked to this Video.</returns>
public IEnumerable<MediaStream> GetAllLinkedMediaStreams()
{
return GetAllItemsForMediaSources().SelectMany(item => item.Item.GetMediaStreams());
}
/// <summary>
/// Gets the additional parts.
/// </summary>

View File

@ -13,6 +13,8 @@ namespace MediaBrowser.Model.Querying
Tags = Array.Empty<string>();
OfficialRatings = Array.Empty<string>();
Years = Array.Empty<int>();
AudioLanguages = Array.Empty<string>();
SubtitleLanguages = Array.Empty<string>();
}
public string[] Genres { get; set; }
@ -22,5 +24,9 @@ namespace MediaBrowser.Model.Querying
public string[] OfficialRatings { get; set; }
public int[] Years { get; set; }
public string[] AudioLanguages { get; set; }
public string[] SubtitleLanguages { get; set; }
}
}