mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-21 01:24:44 +01:00
add cinema mode feature
This commit is contained in:
@@ -332,7 +332,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
{
|
||||
return new ITaskTrigger[]
|
||||
{
|
||||
new IntervalTrigger{ Interval = TimeSpan.FromHours(6)},
|
||||
new IntervalTrigger{ Interval = TimeSpan.FromHours(3)},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
{
|
||||
get
|
||||
{
|
||||
return TimeSpan.FromHours(12);
|
||||
return TimeSpan.FromHours(6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +663,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
|
||||
private async Task<IEnumerable<ChannelItemInfo>> GetLatestItems(ISupportsLatestMedia indexable, IChannel channel, string userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheLength = TimeSpan.FromHours(12);
|
||||
var cacheLength = CacheLength;
|
||||
var cachePath = GetChannelDataCachePath(channel, userId, "channelmanager-latest", null, false);
|
||||
|
||||
try
|
||||
@@ -720,7 +720,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken)
|
||||
public async Task<QueryResult<BaseItem>> GetAllMediaInternal(AllChannelMediaQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = string.IsNullOrWhiteSpace(query.UserId)
|
||||
? null
|
||||
@@ -798,19 +798,43 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
var internalItems = await Task.WhenAll(itemTasks).ConfigureAwait(false);
|
||||
await RefreshIfNeeded(internalItems, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var returnItemArray = internalItems.Select(i => _dtoService.GetBaseItemDto(i, query.Fields, user))
|
||||
.ToArray();
|
||||
var returnItemArray = internalItems.ToArray();
|
||||
|
||||
return new QueryResult<BaseItemDto>
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
TotalRecordCount = totalCount,
|
||||
Items = returnItemArray
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = string.IsNullOrWhiteSpace(query.UserId)
|
||||
? null
|
||||
: _userManager.GetUserById(query.UserId);
|
||||
|
||||
var internalResult = await GetAllMediaInternal(query, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Get everything
|
||||
var fields = Enum.GetNames(typeof(ItemFields))
|
||||
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
|
||||
.ToList();
|
||||
|
||||
var returnItems = internalResult.Items.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
||||
.ToArray();
|
||||
|
||||
var result = new QueryResult<BaseItemDto>
|
||||
{
|
||||
Items = returnItems,
|
||||
TotalRecordCount = internalResult.TotalRecordCount
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ChannelItemResult> GetAllItems(IIndexableChannel indexable, IChannel channel, string userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheLength = TimeSpan.FromHours(12);
|
||||
var cacheLength = CacheLength;
|
||||
var cachePath = GetChannelDataCachePath(channel, userId, "channelmanager-allitems", null, false);
|
||||
|
||||
try
|
||||
@@ -1199,7 +1223,6 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
item.Genres = info.Genres;
|
||||
item.Studios = info.Studios;
|
||||
item.CommunityRating = info.CommunityRating;
|
||||
item.OfficialRating = info.OfficialRating;
|
||||
item.Overview = info.Overview;
|
||||
item.IndexNumber = info.IndexNumber;
|
||||
item.ParentIndexNumber = info.ParentIndexNumber;
|
||||
@@ -1207,6 +1230,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
item.PremiereDate = info.PremiereDate;
|
||||
item.ProductionYear = info.ProductionYear;
|
||||
item.ProviderIds = info.ProviderIds;
|
||||
item.OfficialRating = info.OfficialRating;
|
||||
|
||||
item.DateCreated = info.DateCreated.HasValue ?
|
||||
info.DateCreated.Value :
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Channels;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Channels
|
||||
{
|
||||
public class ChannelPostScanTask : ILibraryPostScanTask
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger)
|
||||
{
|
||||
_channelManager = channelManager;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var users = _userManager.Users
|
||||
.Select(i => i.Id.ToString("N"))
|
||||
.ToList();
|
||||
|
||||
var numComplete = 0;
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
double percentPerUser = 1;
|
||||
percentPerUser /= users.Count;
|
||||
var startingPercent = numComplete * percentPerUser * 100;
|
||||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(p => progress.Report(startingPercent + (percentPerUser * p)));
|
||||
|
||||
await DownloadContent(user, cancellationToken, innerProgress).ConfigureAwait(false);
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= users.Count;
|
||||
progress.Report(percent * 100);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
private async Task DownloadContent(string user, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
var channels = await _channelManager.GetChannelsInternal(new ChannelQuery
|
||||
{
|
||||
UserId = user
|
||||
|
||||
}, cancellationToken);
|
||||
|
||||
var numComplete = 0;
|
||||
|
||||
foreach (var channel in channels.Items)
|
||||
{
|
||||
try
|
||||
{
|
||||
await GetAllItems(user, channel.Id.ToString("N"), null, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting channel content", ex);
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= channels.Items.Length;
|
||||
progress.Report(percent * 100);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
|
||||
}
|
||||
|
||||
private async Task GetAllItems(string user, string channelId, string folderId, bool recursive, CancellationToken cancellationToken)
|
||||
{
|
||||
var folderItems = new List<string>();
|
||||
|
||||
var result = await _channelManager.GetChannelItemsInternal(new ChannelItemQuery
|
||||
{
|
||||
ChannelId = channelId,
|
||||
UserId = user,
|
||||
FolderId = folderId
|
||||
|
||||
}, cancellationToken);
|
||||
|
||||
folderItems.AddRange(result.Items.Where(i => i.IsFolder).Select(i => i.Id.ToString("N")));
|
||||
|
||||
var totalRetrieved = result.Items.Length;
|
||||
var totalCount = result.TotalRecordCount;
|
||||
|
||||
while (totalRetrieved < totalCount)
|
||||
{
|
||||
result = await _channelManager.GetChannelItemsInternal(new ChannelItemQuery
|
||||
{
|
||||
ChannelId = channelId,
|
||||
UserId = user,
|
||||
StartIndex = totalRetrieved,
|
||||
FolderId = folderId
|
||||
|
||||
}, cancellationToken);
|
||||
|
||||
folderItems.AddRange(result.Items.Where(i => i.IsFolder).Select(i => i.Id.ToString("N")));
|
||||
|
||||
totalRetrieved += result.Items.Length;
|
||||
totalCount = result.TotalRecordCount;
|
||||
}
|
||||
|
||||
if (recursive)
|
||||
{
|
||||
foreach (var folder in folderItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
await GetAllItems(user, channelId, folder, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting channel content", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,11 +402,11 @@ namespace MediaBrowser.Server.Implementations.Connect
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(query.Name))
|
||||
{
|
||||
url = url + "?nameoremail=" + WebUtility.UrlEncode(query.Name);
|
||||
url = url + "?name=" + WebUtility.UrlEncode(query.Name);
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(query.Email))
|
||||
{
|
||||
url = url + "?nameoremail=" + WebUtility.UrlEncode(query.Email);
|
||||
url = url + "?name=" + WebUtility.UrlEncode(query.Email);
|
||||
}
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Security;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Localization;
|
||||
using MediaBrowser.Model.Channels;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Intros
|
||||
{
|
||||
public class DefaultIntroProvider : IIntroProvider
|
||||
{
|
||||
private readonly ISecurityManager _security;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IConfigurationManager _serverConfig;
|
||||
|
||||
public DefaultIntroProvider(ISecurityManager security, IChannelManager channelManager, ILocalizationManager localization, IConfigurationManager serverConfig)
|
||||
{
|
||||
_security = security;
|
||||
_channelManager = channelManager;
|
||||
_localization = localization;
|
||||
_serverConfig = serverConfig;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, User user)
|
||||
{
|
||||
var config = GetOptions();
|
||||
|
||||
if (item is Movie)
|
||||
{
|
||||
if (!config.EnableIntrosForMovies)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
}
|
||||
else if (item is Episode)
|
||||
{
|
||||
if (!config.EnableIntrosForEpisodes)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
|
||||
if (!IsSupporter)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
|
||||
var ratingLevel = string.IsNullOrWhiteSpace(item.OfficialRating)
|
||||
? (int?)null
|
||||
: _localization.GetRatingLevel(item.OfficialRating);
|
||||
|
||||
var libaryItems = user.RootFolder.GetRecursiveChildren(user, false)
|
||||
.ToList();
|
||||
|
||||
var random = new Random(Environment.TickCount + Guid.NewGuid().GetHashCode());
|
||||
|
||||
var candidates = new List<ItemWithTrailer>();
|
||||
|
||||
if (config.EnableIntrosFromMoviesInLibrary)
|
||||
{
|
||||
var itemsWithTrailers = libaryItems
|
||||
.Where(i =>
|
||||
{
|
||||
var hasTrailers = i as IHasTrailers;
|
||||
|
||||
if (hasTrailers != null && hasTrailers.LocalTrailerIds.Count > 0)
|
||||
{
|
||||
if (i is Movie)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
candidates.AddRange(itemsWithTrailers.Select(i => new ItemWithTrailer
|
||||
{
|
||||
Item = i,
|
||||
Type = ItemWithTrailerType.ItemWithTrailer,
|
||||
User = user,
|
||||
WatchingItem = item,
|
||||
Random = random
|
||||
}));
|
||||
}
|
||||
|
||||
if (config.EnableIntrosFromUpcomingTrailers)
|
||||
{
|
||||
var channelTrailers = await _channelManager.GetAllMediaInternal(new AllChannelMediaQuery
|
||||
{
|
||||
ContentTypes = new[] { ChannelMediaContentType.Trailer },
|
||||
UserId = user.Id.ToString("N")
|
||||
|
||||
}, CancellationToken.None);
|
||||
|
||||
candidates.AddRange(channelTrailers.Items.Select(i => new ItemWithTrailer
|
||||
{
|
||||
Item = i,
|
||||
Type = ItemWithTrailerType.ChannelTrailer,
|
||||
User = user,
|
||||
WatchingItem = item,
|
||||
Random = random
|
||||
}));
|
||||
|
||||
candidates.AddRange(libaryItems.Where(i => i is Trailer).Select(i => new ItemWithTrailer
|
||||
{
|
||||
Item = i,
|
||||
Type = ItemWithTrailerType.LibraryTrailer,
|
||||
User = user,
|
||||
WatchingItem = item,
|
||||
Random = random
|
||||
}));
|
||||
}
|
||||
|
||||
var customIntros = config.EnableCustomIntro ?
|
||||
GetCustomIntros(item) :
|
||||
new List<IntroInfo>();
|
||||
|
||||
var trailerLimit = 2;
|
||||
if (customIntros.Count > 0)
|
||||
{
|
||||
trailerLimit--;
|
||||
}
|
||||
|
||||
// Avoid implicitly captured closure
|
||||
var currentUser = user;
|
||||
return candidates.Where(i =>
|
||||
{
|
||||
if (config.EnableIntrosParentalControl && !FilterByParentalRating(ratingLevel, i.Item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.EnableIntrosForWatchedContent && i.IsPlayed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.OrderByDescending(i => i.Score)
|
||||
.ThenBy(i => Guid.NewGuid())
|
||||
.ThenByDescending(i => (i.IsPlayed ? 0 : 1))
|
||||
.Select(i => i.IntroInfo)
|
||||
.Take(trailerLimit)
|
||||
.Concat(customIntros.Take(1));
|
||||
}
|
||||
|
||||
private CinemaModeConfiguration GetOptions()
|
||||
{
|
||||
return _serverConfig.GetConfiguration<CinemaModeConfiguration>("cinemamode");
|
||||
}
|
||||
|
||||
private List<IntroInfo> GetCustomIntros(BaseItem item)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
|
||||
private bool FilterByParentalRating(int? ratingLevel, BaseItem item)
|
||||
{
|
||||
// Only content rated same or lower
|
||||
if (ratingLevel.HasValue)
|
||||
{
|
||||
var level = string.IsNullOrWhiteSpace(item.OfficialRating)
|
||||
? (int?)null
|
||||
: _localization.GetRatingLevel(item.OfficialRating);
|
||||
|
||||
return level.HasValue && level.Value <= ratingLevel.Value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static int GetSimiliarityScore(BaseItem item1, BaseItem item2, Random random)
|
||||
{
|
||||
var points = 0;
|
||||
|
||||
if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
points += 10;
|
||||
}
|
||||
|
||||
// Find common genres
|
||||
points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
|
||||
|
||||
// Find common tags
|
||||
points += GetTags(item1).Where(i => GetTags(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
|
||||
|
||||
// Find common keywords
|
||||
points += GetKeywords(item1).Where(i => GetKeywords(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
|
||||
|
||||
// Find common studios
|
||||
points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5);
|
||||
|
||||
var item2PeopleNames = item2.People.Select(i => i.Name)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
points += item1.People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
|
||||
{
|
||||
if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
|
||||
// Add some randomization so that you're not always seeing the same ones for a given movie
|
||||
points += random.Next(0, 50);
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetTags(BaseItem item)
|
||||
{
|
||||
var hasTags = item as IHasTags;
|
||||
if (hasTags != null)
|
||||
{
|
||||
return hasTags.Tags;
|
||||
}
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetKeywords(BaseItem item)
|
||||
{
|
||||
var hasTags = item as IHasKeywords;
|
||||
if (hasTags != null)
|
||||
{
|
||||
return hasTags.Keywords;
|
||||
}
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAllIntroFiles()
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
private bool IsSupporter
|
||||
{
|
||||
get { return _security.IsMBSupporter; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Default"; }
|
||||
}
|
||||
|
||||
internal class ItemWithTrailer
|
||||
{
|
||||
internal BaseItem Item;
|
||||
internal ItemWithTrailerType Type;
|
||||
internal User User;
|
||||
internal BaseItem WatchingItem;
|
||||
internal Random Random;
|
||||
|
||||
private bool? _isPlayed;
|
||||
public bool IsPlayed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isPlayed.HasValue)
|
||||
{
|
||||
_isPlayed = Item.IsPlayed(User);
|
||||
}
|
||||
return _isPlayed.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private int? _score;
|
||||
public int Score
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_score.HasValue)
|
||||
{
|
||||
_score = GetSimiliarityScore(WatchingItem, Item, Random);
|
||||
}
|
||||
return _score.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public IntroInfo IntroInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
var id = Item.Id;
|
||||
|
||||
if (Type == ItemWithTrailerType.ItemWithTrailer)
|
||||
{
|
||||
var hasTrailers = Item as IHasTrailers;
|
||||
|
||||
if (hasTrailers != null)
|
||||
{
|
||||
id = hasTrailers.LocalTrailerIds.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
return new IntroInfo
|
||||
{
|
||||
ItemId = id
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal enum ItemWithTrailerType
|
||||
{
|
||||
LibraryTrailer,
|
||||
ChannelTrailer,
|
||||
ItemWithTrailer
|
||||
}
|
||||
}
|
||||
|
||||
public class CinemaModeConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
ConfigurationType = typeof(CinemaModeConfiguration),
|
||||
Key = "cinemamode"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -95,8 +95,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't misidentify xbmc trailers as a movie
|
||||
if (filename.IndexOf(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (BaseItem.ExtraSuffixes.Any(i => filename.IndexOf(i.Key, StringComparison.OrdinalIgnoreCase) != -1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1193,13 +1193,42 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>IEnumerable{System.String}.</returns>
|
||||
public IEnumerable<Video> GetIntros(BaseItem item, User user)
|
||||
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
|
||||
{
|
||||
return IntroProviders.SelectMany(i => i.GetIntros(item, user))
|
||||
var tasks = IntroProviders
|
||||
.OrderBy(i => (i.GetType().Name.IndexOf("Default", StringComparison.OrdinalIgnoreCase) == -1 ? 1 : 0))
|
||||
.Take(1)
|
||||
.Select(i => GetIntros(i, item, user));
|
||||
|
||||
var items = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
return items
|
||||
.SelectMany(i => i.ToArray())
|
||||
.Select(ResolveIntro)
|
||||
.Where(i => i != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the intros.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task<IEnumerable<IntroInfo>>.</returns>
|
||||
private async Task<IEnumerable<IntroInfo>> GetIntros(IIntroProvider provider, BaseItem item, User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await provider.GetIntros(item, user).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting intros", ex);
|
||||
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all intro files.
|
||||
/// </summary>
|
||||
@@ -1487,7 +1516,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
|
||||
var item = GetItemById(id) as UserView;
|
||||
|
||||
if (item == null ||
|
||||
if (item == null ||
|
||||
!string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
@@ -41,9 +43,15 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
||||
}
|
||||
|
||||
// Support xbmc local trailer convention, but only when looking for local trailers (hence the parent == null check)
|
||||
if (args.Parent == null && _fileSystem.GetFileNameWithoutExtension(args.Path).EndsWith(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase))
|
||||
if (args.Parent == null)
|
||||
{
|
||||
return base.Resolve(args);
|
||||
var nameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(args.Path);
|
||||
var suffix = BaseItem.ExtraSuffixes.First(i => i.Value == ExtraType.Trailer);
|
||||
|
||||
if (nameWithoutExtension.EndsWith(suffix.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return base.Resolve(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,8 +111,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
|
||||
}
|
||||
|
||||
var filename = Path.GetFileName(args.Path);
|
||||
// Don't misidentify xbmc trailers as a movie
|
||||
if (filename.IndexOf(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
// Don't misidentify extras or trailers
|
||||
if (BaseItem.ExtraSuffixes.Any(i => filename.IndexOf(i.Key, StringComparison.OrdinalIgnoreCase) != -1))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -229,8 +229,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't misidentify xbmc trailers as a movie
|
||||
if (filename.IndexOf(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
// Don't misidentify extras or trailers as a movie
|
||||
if (BaseItem.ExtraSuffixes.Any(i => filename.IndexOf(i.Key, StringComparison.OrdinalIgnoreCase) != -1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -569,5 +569,6 @@
|
||||
"MediaInfoStreamTypeVideo": "Video",
|
||||
"MediaInfoStreamTypeSubtitle": "Subtitle",
|
||||
"MediaInfoStreamTypeEmbeddedImage": "Embedded Image",
|
||||
"MediaInfoRefFrames": "Ref frames"
|
||||
"MediaInfoRefFrames": "Ref frames",
|
||||
"TabPlayback": "Playback"
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
"ButtonAutoScroll": "Auto-scroll",
|
||||
"LabelImageSavingConvention": "Image saving convention:",
|
||||
"LabelImageSavingConventionHelp": "Media Browser recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.",
|
||||
"OptionImageSavingCompatible": "Compatible - Media Browser/Xbmc/Plex",
|
||||
"OptionImageSavingCompatible": "Compatible - Media Browser/Kodi/Plex",
|
||||
"OptionImageSavingStandard": "Standard - MB2",
|
||||
"ButtonSignIn": "Sign In",
|
||||
"TitleSignIn": "Sign In",
|
||||
@@ -883,22 +883,22 @@
|
||||
"OptionLatestTvRecordings": "Latest recordings",
|
||||
"LabelProtocolInfo": "Protocol info:",
|
||||
"LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.",
|
||||
"TabXbmcMetadata": "Xbmc",
|
||||
"HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.",
|
||||
"LabelXbmcMetadataUser": "Add user watch data to nfo's for:",
|
||||
"LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.",
|
||||
"LabelXbmcMetadataDateFormat": "Release date format:",
|
||||
"LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.",
|
||||
"LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files",
|
||||
"LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.",
|
||||
"LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution",
|
||||
"LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.",
|
||||
"LabelXbmcMetadataEnablePathSubstitutionHelp2": "See path substitution.",
|
||||
"TabKodiMetadata": "Kodi",
|
||||
"HeaderKodiMetadataHelp": "Media Browser includes native support for Kodi Nfo metadata and images. To enable or disable Kodi metadata, use the Advanced tab to configure options for your media types.",
|
||||
"LabelKodiMetadataUser": "Add user watch data to nfo's for:",
|
||||
"LabelKodiMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Kodi.",
|
||||
"LabelKodiMetadataDateFormat": "Release date format:",
|
||||
"LabelKodiMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.",
|
||||
"LabelKodiMetadataSaveImagePaths": "Save image paths within nfo files",
|
||||
"LabelKodiMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Kodi guidelines.",
|
||||
"LabelKodiMetadataEnablePathSubstitution": "Enable path substitution",
|
||||
"LabelKodiMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.",
|
||||
"LabelKodiMetadataEnablePathSubstitutionHelp2": "See path substitution.",
|
||||
"LabelGroupChannelsIntoViews": "Display the following channels directly within my views:",
|
||||
"LabelGroupChannelsIntoViewsHelp": "If enabled, these channels will be displayed directly alongside other views. If disabled, they'll be displayed within a separate Channels view.",
|
||||
"LabelDisplayCollectionsView": "Display a collections view to show movie collections",
|
||||
"LabelXbmcMetadataEnableExtraThumbs": "Copy extrafanart into extrathumbs",
|
||||
"LabelXbmcMetadataEnableExtraThumbsHelp": "When downloading images they can be saved into both extrafanart and extrathumbs for maximum Xbmc skin compatibility.",
|
||||
"LabelKodiMetadataEnableExtraThumbs": "Copy extrafanart into extrathumbs",
|
||||
"LabelKodiMetadataEnableExtraThumbsHelp": "When downloading images they can be saved into both extrafanart and extrathumbs for maximum Kodi skin compatibility.",
|
||||
"TabServices": "Services",
|
||||
"TabLogs": "Logs",
|
||||
"HeaderServerLogFiles": "Server log files:",
|
||||
@@ -1179,5 +1179,21 @@
|
||||
"OptionExternallyDownloaded": "External download",
|
||||
"OptionHlsSegmentedSubtitles": "Hls segmented subtitles",
|
||||
"LabelSubtitleFormatHelp": "Example: srt",
|
||||
"ButtonLearnMore": "Learn more"
|
||||
"ButtonLearnMore": "Learn more",
|
||||
"TabPlayback": "Playback",
|
||||
"HeaderTrailersAndExtras": "Trailers & Extras",
|
||||
"OptionFindTrailers": "Find trailers from the internet automatically",
|
||||
"HeaderLanguagePreferences": "Language Preferences",
|
||||
"TabCinemaMode": "Cinema Mode",
|
||||
"TitlePlayback": "Playback",
|
||||
"LabelEnableCinemaModeFor": "Enable cinema mode for:",
|
||||
"CinemaModeConfigurationHelp": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
|
||||
"LabelEnableTheFollowingIntros": "Enable the following types of intros:",
|
||||
"OptionTrailersFromMyMovies": "Trailers from movies in my library",
|
||||
"OptionUpcomingMoviesInTheaters": "Trailers from upcoming movies",
|
||||
"LabelLimitIntrosToUnwatchedContent": "Only use trailers from unwatched content",
|
||||
"LabelEnableIntroParentalControl": "Enable smart parental control",
|
||||
"LabelEnableIntroParentalControlHelp": "Intros will only used from content with a parental rating equal to or less than the content being watched.",
|
||||
"LabelEnableTheFollowingIntrosHelp": "Trailers from existing movies requires setup of local trailers. Theater trailers require installation of the Trailer channel plugin.",
|
||||
"ButtonThisFeatureRequiresSupporter": "This feature requires an active supporter membership"
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
<Compile Include="Channels\ChannelImageProvider.cs" />
|
||||
<Compile Include="Channels\ChannelItemImageProvider.cs" />
|
||||
<Compile Include="Channels\ChannelManager.cs" />
|
||||
<Compile Include="Channels\ChannelPostScanTask.cs" />
|
||||
<Compile Include="Channels\RefreshChannelsScheduledTask.cs" />
|
||||
<Compile Include="Collections\CollectionManager.cs" />
|
||||
<Compile Include="Collections\CollectionsDynamicFolder.cs" />
|
||||
@@ -173,6 +174,7 @@
|
||||
<Compile Include="HttpServer\SocketSharp\WebSocketSharpRequest.cs" />
|
||||
<Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" />
|
||||
<Compile Include="HttpServer\ThrottledStream.cs" />
|
||||
<Compile Include="Intros\DefaultIntroProvider.cs" />
|
||||
<Compile Include="IO\LibraryMonitor.cs" />
|
||||
<Compile Include="Library\CoreResolutionIgnoreRule.cs" />
|
||||
<Compile Include="Library\LibraryManager.cs" />
|
||||
|
||||
Reference in New Issue
Block a user