aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-09-22 17:56:54 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-09-22 17:56:54 -0400
commit1afb28b48797ee53442823cfd395e07d219e8ec3 (patch)
tree694d2d8a3449a6d8cb46fddeb73ed1f96be634d7 /MediaBrowser.Server.Implementations
parentac201a6cdb22ff158aaca1378361b03b2127686d (diff)
add cinema mode feature
Diffstat (limited to 'MediaBrowser.Server.Implementations')
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs40
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs136
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectManager.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs361
-rw-r--r--MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs35
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs8
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json3
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/server.json46
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj2
12 files changed, 614 insertions, 38 deletions
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
index 1d95dbc1b..8c510afd2 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
@@ -332,7 +332,7 @@ namespace MediaBrowser.Server.Implementations.Channels
{
return new ITaskTrigger[]
{
- new IntervalTrigger{ Interval = TimeSpan.FromHours(6)},
+ new IntervalTrigger{ Interval = TimeSpan.FromHours(3)},
};
}
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index dfd24a248..12aa670b3 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -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 :
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs
new file mode 100644
index 000000000..b067271c5
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs
@@ -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);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
index 374d04f1d..a1b88a65f 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
@@ -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
diff --git a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
new file mode 100644
index 000000000..b5e449eae
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
@@ -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"
+ }
+ };
+ }
+ }
+
+}
diff --git a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index 7b58dd7c4..e902b939b 100644
--- a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -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;
}
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index ad5eac033..6283ceb2a 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -1193,14 +1193,43 @@ 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&lt;IEnumerable&lt;IntroInfo&gt;&gt;.</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>
/// <returns>IEnumerable{System.String}.</returns>
@@ -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);
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs
index b483f7c42..662a9e87c 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs
@@ -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);
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 215cff22f..51835c1ba 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -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;
}
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
index ab4eba16a..c8b4cbb46 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
@@ -569,5 +569,6 @@
"MediaInfoStreamTypeVideo": "Video",
"MediaInfoStreamTypeSubtitle": "Subtitle",
"MediaInfoStreamTypeEmbeddedImage": "Embedded Image",
- "MediaInfoRefFrames": "Ref frames"
+ "MediaInfoRefFrames": "Ref frames",
+ "TabPlayback": "Playback"
}
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json
index ab7bcf37e..e1b3b5ea1 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/server.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json
@@ -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"
}
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index d7c06e2f4..dbecd0a92 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -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" />