Przeglądaj źródła

add cinema mode feature

Luke Pulverenti 10 lat temu
rodzic
commit
1afb28b487
55 zmienionych plików z 1014 dodań i 141 usunięć
  1. 5 13
      MediaBrowser.Api/ApiEntryPoint.cs
  2. 1 1
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  3. 1 1
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  4. 2 2
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  5. 23 0
      MediaBrowser.Common/Extensions/BaseExtensions.cs
  6. 11 1
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  7. 8 0
      MediaBrowser.Controller/Channels/IChannelManager.cs
  8. 0 2
      MediaBrowser.Controller/Channels/IChannelMediaItem.cs
  9. 1 0
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  10. 110 11
      MediaBrowser.Controller/Entities/BaseItem.cs
  11. 2 7
      MediaBrowser.Controller/Entities/Extensions.cs
  12. 1 27
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  13. 1 0
      MediaBrowser.Controller/Entities/Video.cs
  14. 8 1
      MediaBrowser.Controller/Library/IIntroProvider.cs
  15. 1 1
      MediaBrowser.Controller/Library/ILibraryManager.cs
  16. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  17. 39 0
      MediaBrowser.Controller/Providers/IExtrasProvider.cs
  18. 6 0
      MediaBrowser.Controller/Providers/ItemLookupInfo.cs
  19. 5 4
      MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
  20. 3 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  21. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  22. 23 0
      MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs
  23. 4 1
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  24. 1 0
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  25. 16 0
      MediaBrowser.Model/Entities/ExtraType.cs
  26. 0 1
      MediaBrowser.Model/Entities/MediaUrl.cs
  27. 2 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  28. 5 5
      MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs
  29. 3 3
      MediaBrowser.Providers/Channels/VideoChannelItemMetadataService.cs
  30. 15 0
      MediaBrowser.Providers/Manager/MetadataService.cs
  31. 2 1
      MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
  32. 0 1
      MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
  33. 9 0
      MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
  34. 4 4
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  35. 23 1
      MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs
  36. 17 1
      MediaBrowser.Providers/Movies/MovieExternalIds.cs
  37. 18 1
      MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
  38. 1 1
      MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
  39. 32 8
      MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
  40. 136 0
      MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs
  41. 2 2
      MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
  42. 361 0
      MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
  43. 1 2
      MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
  44. 32 3
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  45. 10 2
      MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs
  46. 4 4
      MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  47. 2 1
      MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
  48. 31 15
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  49. 2 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  50. 5 1
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  51. 15 6
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  52. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  53. 1 1
      Nuget/MediaBrowser.Common.nuspec
  54. 1 1
      Nuget/MediaBrowser.Model.Signed.nuspec
  55. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 5 - 13
MediaBrowser.Api/ApiEntryPoint.cs

@@ -314,17 +314,16 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <param name="deviceId">The device id.</param>
         /// <param name="deleteFiles">The delete files.</param>
-        /// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
         /// <returns>Task.</returns>
         /// <exception cref="ArgumentNullException">deviceId</exception>
-        internal Task KillTranscodingJobs(string deviceId, Func<string, bool> deleteFiles, bool acquireLock)
+        internal Task KillTranscodingJobs(string deviceId, Func<string, bool> deleteFiles)
         {
             if (string.IsNullOrEmpty(deviceId))
             {
                 throw new ArgumentNullException("deviceId");
             }
 
-            return KillTranscodingJobs(j => string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase), deleteFiles, acquireLock);
+            return KillTranscodingJobs(j => string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase), deleteFiles);
         }
 
         /// <summary>
@@ -332,9 +331,8 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <param name="killJob">The kill job.</param>
         /// <param name="deleteFiles">The delete files.</param>
-        /// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
         /// <returns>Task.</returns>
-        internal async Task KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles, bool acquireLock)
+        internal async Task KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles)
         {
             var jobs = new List<TranscodingJob>();
 
@@ -350,10 +348,7 @@ namespace MediaBrowser.Api
                 return;
             }
 
-            if (acquireLock)
-            {
-                await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
-            }
+            await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
 
             try
             {
@@ -364,10 +359,7 @@ namespace MediaBrowser.Api
             }
             finally
             {
-                if (acquireLock)
-                {
-                    TranscodingStartLock.Release();
-                }
+                TranscodingStartLock.Release();
             }
         }
 

+ 1 - 1
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -139,7 +139,7 @@ namespace MediaBrowser.Api.Playback.Hls
                         // If the playlist doesn't already exist, startup ffmpeg
                         try
                         {
-                            await ApiEntryPoint.Instance.KillTranscodingJobs(j => j.Type == TranscodingJobType.Hls && string.Equals(j.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase), p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase), false).ConfigureAwait(false);
+                            await ApiEntryPoint.Instance.KillTranscodingJobs(j => j.Type == TranscodingJobType.Hls && string.Equals(j.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase), p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false);
 
                             if (currentTranscodingIndex.HasValue)
                             {

+ 1 - 1
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -72,7 +72,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
         public void Delete(StopEncodingProcess request)
         {
-            var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, path => true, true);
+            var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, path => true);
 
             Task.WaitAll(task);
         }

+ 2 - 2
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -566,13 +566,13 @@ namespace MediaBrowser.Api.UserLibrary
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetIntros request)
+        public async Task<object> Get(GetIntros request)
         {
             var user = _userManager.GetUserById(request.UserId);
 
             var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
 
-            var items = _libraryManager.GetIntros(item, user);
+            var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
 
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields))

+ 23 - 0
MediaBrowser.Common/Extensions/BaseExtensions.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Globalization;
 using System.Security.Cryptography;
 using System.Text;
 using System.Text.RegularExpressions;
@@ -54,6 +55,28 @@ namespace MediaBrowser.Common.Extensions
             return sb.ToString();
         }
 
+        /// <summary>
+        /// Removes the accent.
+        /// </summary>
+        /// <param name="text">The text.</param>
+        /// <returns>System.String.</returns>
+        public static string RemoveAccent(this string text)
+        {
+            var normalizedString = text.Normalize(NormalizationForm.FormD);
+            var stringBuilder = new StringBuilder();
+
+            foreach (var c in normalizedString)
+            {
+                var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
+                if (unicodeCategory != UnicodeCategory.NonSpacingMark)
+                {
+                    stringBuilder.Append(c);
+                }
+            }
+
+            return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
+        }
+
         /// <summary>
         /// Gets the M d5.
         /// </summary>

+ 11 - 1
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dto;
@@ -9,7 +10,7 @@ using System.Linq;
 
 namespace MediaBrowser.Controller.Channels
 {
-    public class ChannelVideoItem : Video, IChannelMediaItem
+    public class ChannelVideoItem : Video, IChannelMediaItem, IHasLookupInfo<ChannelItemLookupInfo>
     {
         public string ExternalId { get; set; }
 
@@ -87,5 +88,14 @@ namespace MediaBrowser.Controller.Channels
 
             return list;
         }
+
+        public ChannelItemLookupInfo GetLookupInfo()
+        {
+            var info = GetItemLookupInfo<ChannelItemLookupInfo>();
+
+            info.ContentType = ContentType;
+
+            return info;
+        }
     }
 }

+ 8 - 0
MediaBrowser.Controller/Channels/IChannelManager.cs

@@ -59,6 +59,14 @@ namespace MediaBrowser.Controller.Channels
         /// <returns>Task{QueryResult{BaseItemDto}}.</returns>
         Task<QueryResult<BaseItemDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Gets all media internal.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;QueryResult&lt;BaseItem&gt;&gt;.</returns>
+        Task<QueryResult<BaseItem>> GetAllMediaInternal(AllChannelMediaQuery query, CancellationToken cancellationToken);
+        
         /// <summary>
         /// Gets all media.
         /// </summary>

+ 0 - 2
MediaBrowser.Controller/Channels/IChannelMediaItem.cs

@@ -5,8 +5,6 @@ namespace MediaBrowser.Controller.Channels
 {
     public interface IChannelMediaItem : IChannelItem
     {
-        bool IsInfiniteStream { get; set; }
-
         long? RunTimeTicks { get; set; }
         string MediaType { get; }
 

+ 1 - 0
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -28,6 +28,7 @@ namespace MediaBrowser.Controller.Entities.Audio
         public string Container { get; set; }
         public int? TotalBitrate { get; set; }
         public List<string> Tags { get; set; }
+        public ExtraType ExtraType { get; set; }
 
         public bool IsThemeMedia { get; set; }
 

+ 110 - 11
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -41,16 +41,25 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// The supported image extensions
         /// </summary>
-        public static readonly string[] SupportedImageExtensions = new[] { ".png", ".jpg", ".jpeg", ".tbn" };
+        public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".tbn" };
 
         /// <summary>
         /// The trailer folder name
         /// </summary>
-        public const string TrailerFolderName = "trailers";
-        public const string ThemeSongsFolderName = "theme-music";
-        public const string ThemeSongFilename = "theme";
-        public const string ThemeVideosFolderName = "backdrops";
-        public const string XbmcTrailerFileSuffix = "-trailer";
+        public static string TrailerFolderName = "trailers";
+        public static string ThemeSongsFolderName = "theme-music";
+        public static string ThemeSongFilename = "theme";
+        public static string ThemeVideosFolderName = "backdrops";
+
+        public static List<KeyValuePair<string, ExtraType>> ExtraSuffixes = new List<KeyValuePair<string, ExtraType>>
+        {
+            new KeyValuePair<string,ExtraType>("-trailer", ExtraType.Trailer),
+            new KeyValuePair<string,ExtraType>("-deleted", ExtraType.DeletedScene),
+            new KeyValuePair<string,ExtraType>("-behindthescenes", ExtraType.BehindTheScenes),
+            new KeyValuePair<string,ExtraType>("-interview", ExtraType.Interview),
+            new KeyValuePair<string,ExtraType>("-scene", ExtraType.Scene),
+            new KeyValuePair<string,ExtraType>("-sample", ExtraType.Sample)
+        };
 
         public List<ItemImageInfo> ImageInfos { get; set; }
 
@@ -167,7 +176,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 // Local trailer, special feature, theme video, etc.
                 // An item that belongs to another item but is not part of the Parent-Child tree
-                return !IsFolder && Parent == null;
+                return !IsFolder && Parent == null && LocationType == LocationType.FileSystem;
             }
         }
 
@@ -552,11 +561,24 @@ namespace MediaBrowser.Controller.Entities
                 .Where(i => string.Equals(i.Name, TrailerFolderName, StringComparison.OrdinalIgnoreCase))
                 .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
                 .ToList();
-            
-            // Support plex/xbmc convention
+
+            var extraTypes = new List<ExtraType> { ExtraType.Trailer };
+            var suffixes = ExtraSuffixes.Where(i => extraTypes.Contains(i.Value))
+                .Select(i => i.Key)
+                .ToList();
+
             files.AddRange(fileSystemChildren.OfType<FileInfo>()
-                .Where(i => FileSystem.GetFileNameWithoutExtension(i).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
-                );
+                .Where(i =>
+                {
+                    var nameEithoutExtension = FileSystem.GetFileNameWithoutExtension(i);
+
+                    if (!suffixes.Any(s => nameEithoutExtension.EndsWith(s, StringComparison.OrdinalIgnoreCase)))
+                    {
+                        return false;
+                    }
+
+                    return !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase);
+                }));
 
             return LibraryManager.ResolvePaths<Trailer>(files, directoryService, null).Select(video =>
             {
@@ -568,12 +590,79 @@ namespace MediaBrowser.Controller.Entities
                     video = dbItem;
                 }
 
+                if (video != null)
+                {
+                    video.ExtraType = ExtraType.Trailer;
+                }
+
+                return video;
+
+                // Sort them so that the list can be easily compared for changes
+            }).OrderBy(i => i.Path).ToList();
+        }
+
+        protected IEnumerable<Video> LoadSpecialFeatures(List<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService)
+        {
+            var files = fileSystemChildren.OfType<DirectoryInfo>()
+                .Where(i => string.Equals(i.Name, "extras", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "specials", StringComparison.OrdinalIgnoreCase))
+                .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
+                .ToList();
+
+            var extraTypes = new List<ExtraType> { ExtraType.BehindTheScenes, ExtraType.DeletedScene, ExtraType.Interview, ExtraType.Sample, ExtraType.Scene, ExtraType.Clip };
+            var suffixes = ExtraSuffixes.Where(i => extraTypes.Contains(i.Value))
+                .Select(i => i.Key)
+                .ToList();
+
+            files.AddRange(fileSystemChildren.OfType<FileInfo>()
+                .Where(i =>
+                {
+                    var nameEithoutExtension = FileSystem.GetFileNameWithoutExtension(i);
+
+                    if (!suffixes.Any(s => nameEithoutExtension.EndsWith(s, StringComparison.OrdinalIgnoreCase)))
+                    {
+                        return false;
+                    }
+
+                    return !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase);
+                }));
+
+            return LibraryManager.ResolvePaths<Video>(files, directoryService, null).Select(video =>
+            {
+                // Try to retrieve it from the db. If we don't find it, use the resolved version
+                var dbItem = LibraryManager.GetItemById(video.Id) as Video;
+
+                if (dbItem != null)
+                {
+                    video = dbItem;
+                }
+
+                if (video != null)
+                {
+                    SetExtraTypeFromFilename(video);
+                }
+
                 return video;
 
                 // Sort them so that the list can be easily compared for changes
             }).OrderBy(i => i.Path).ToList();
         }
 
+        private void SetExtraTypeFromFilename(Video item)
+        {
+            var name = System.IO.Path.GetFileNameWithoutExtension(item.Path) ?? string.Empty;
+
+            foreach (var suffix in ExtraSuffixes)
+            {
+                if (name.EndsWith(suffix.Key, StringComparison.OrdinalIgnoreCase))
+                {
+                    item.ExtraType = suffix.Value;
+                    return;
+                }
+            }
+
+            item.ExtraType = ExtraType.Clip;
+        }
+
         /// <summary>
         /// Loads the theme songs.
         /// </summary>
@@ -600,6 +689,11 @@ namespace MediaBrowser.Controller.Entities
                     audio = dbItem;
                 }
 
+                if (audio != null)
+                {
+                    audio.ExtraType = ExtraType.ThemeSong;
+                }
+
                 return audio;
 
                 // Sort them so that the list can be easily compared for changes
@@ -626,6 +720,11 @@ namespace MediaBrowser.Controller.Entities
                     item = dbItem;
                 }
 
+                if (item != null)
+                {
+                    item.ExtraType = ExtraType.ThemeVideo;
+                }
+
                 return item;
 
                 // Sort them so that the list can be easily compared for changes

+ 2 - 7
MediaBrowser.Controller/Entities/Extensions.cs

@@ -25,16 +25,11 @@ namespace MediaBrowser.Controller.Entities
 
             var current = item.RemoteTrailers.FirstOrDefault(i => string.Equals(i.Url, url, StringComparison.OrdinalIgnoreCase));
 
-            if (current != null)
-            {
-                current.IsDirectLink = isDirectLink;
-            }
-            else
+            if (current == null)
             {
                 item.RemoteTrailers.Add(new MediaUrl
                 {
-                    Url = url,
-                    IsDirectLink = isDirectLink
+                    Url = url
                 });
             }
         }

+ 1 - 27
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -125,7 +125,7 @@ namespace MediaBrowser.Controller.Entities.Movies
             return hasChanges;
         }
 
-        private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+        private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
             var newItems = LoadSpecialFeatures(fileSystemChildren, options.DirectoryService).ToList();
             var newItemIds = newItems.Select(i => i.Id).ToList();
@@ -141,32 +141,6 @@ namespace MediaBrowser.Controller.Entities.Movies
             return itemsChanged;
         }
 
-        /// <summary>
-        /// Loads the special features.
-        /// </summary>
-        /// <returns>IEnumerable{Video}.</returns>
-        private IEnumerable<Video> LoadSpecialFeatures(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService)
-        {
-            var files = fileSystemChildren.OfType<DirectoryInfo>()
-                .Where(i => string.Equals(i.Name, "extras", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "specials", StringComparison.OrdinalIgnoreCase))
-                .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
-
-            return LibraryManager.ResolvePaths<Video>(files, directoryService, null).Select(video =>
-            {
-                // Try to retrieve it from the db. If we don't find it, use the resolved version
-                var dbItem = LibraryManager.GetItemById(video.Id) as Video;
-
-                if (dbItem != null)
-                {
-                    video = dbItem;
-                }
-
-                return video;
-
-                // Sort them so that the list can be easily compared for changes
-            }).OrderBy(i => i.Path).ToList();
-        }
-
         protected override bool GetBlockUnratedValue(UserConfiguration config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Movie);

+ 1 - 0
MediaBrowser.Controller/Entities/Video.cs

@@ -41,6 +41,7 @@ namespace MediaBrowser.Controller.Entities
         public string Container { get; set; }
         public int? TotalBitrate { get; set; }
         public string ShortOverview { get; set; }
+        public ExtraType ExtraType { get; set; }
 
         /// <summary>
         /// Gets or sets the timestamp.

+ 8 - 1
MediaBrowser.Controller/Library/IIntroProvider.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using System.Collections.Generic;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Library
 {
@@ -14,12 +15,18 @@ namespace MediaBrowser.Controller.Library
         /// <param name="item">The item.</param>
         /// <param name="user">The user.</param>
         /// <returns>IEnumerable{System.String}.</returns>
-        IEnumerable<IntroInfo> GetIntros(BaseItem item, User user);
+        Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, User user);
 
         /// <summary>
         /// Gets all intro files.
         /// </summary>
         /// <returns>IEnumerable{System.String}.</returns>
         IEnumerable<string> GetAllIntroFiles();
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
     }
 }

+ 1 - 1
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -157,7 +157,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="item">The item.</param>
         /// <param name="user">The user.</param>
         /// <returns>IEnumerable{System.String}.</returns>
-        IEnumerable<Video> GetIntros(BaseItem item, User user);
+        Task<IEnumerable<Video>> GetIntros(BaseItem item, User user);
 
         /// <summary>
         /// Gets all intro files.

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -234,6 +234,7 @@
     <Compile Include="Providers\DirectoryService.cs" />
     <Compile Include="Providers\ICustomMetadataProvider.cs" />
     <Compile Include="Providers\IExternalId.cs" />
+    <Compile Include="Providers\IExtrasProvider.cs" />
     <Compile Include="Providers\IForcedProvider.cs" />
     <Compile Include="Providers\IHasChangeMonitor.cs" />
     <Compile Include="Entities\IHasMetadata.cs" />

+ 39 - 0
MediaBrowser.Controller/Providers/IExtrasProvider.cs

@@ -0,0 +1,39 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public interface IExtrasProvider
+    {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+
+        /// <summary>
+        /// Supportses the specified item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+        bool Supports(IHasMetadata item);
+    }
+
+    public enum ExtraSource
+    {
+        Local = 1,
+        Metadata = 2,
+        Remote = 3
+    }
+
+    public class ExtraInfo
+    {
+        public string Path { get; set; }
+
+        public LocationType LocationType { get; set; }
+
+        public bool IsDownloadable { get; set; }
+
+        public ExtraType ExtraType { get; set; }
+    }
+}

+ 6 - 0
MediaBrowser.Controller/Providers/ItemLookupInfo.cs

@@ -1,6 +1,7 @@
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
@@ -236,4 +237,9 @@ namespace MediaBrowser.Controller.Providers
 
         public int SeasonIndex { get; set; }
     }
+
+    public class ChannelItemLookupInfo : ItemLookupInfo
+    {
+        public ChannelMediaContentType ContentType { get; set; }
+    }
 }

+ 5 - 4
MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs

@@ -158,9 +158,10 @@ namespace MediaBrowser.Controller.Resolvers
             // Normalize
             // Remove whitespace
             filename = filename.Replace("-", string.Empty);
+            filename = filename.Replace(".", string.Empty);
             filename = Regex.Replace(filename, @"\s+", "");
 
-            var prefixes = new[] { "disc", "cd", "disk" };
+            var prefixes = new[] { "disc", "cd", "disk", "vol", "volume" };
 
             foreach (var prefix in prefixes)
             {
@@ -210,7 +211,7 @@ namespace MediaBrowser.Controller.Resolvers
                 {
                     if (includeCreationTime)
                     {
-                        item.DateCreated = fileSystem.GetCreationTimeUtc(childData);
+                        item.DateCreated = DateTime.UtcNow;
                     }
 
                     item.DateModified = fileSystem.GetLastWriteTimeUtc(childData);
@@ -223,7 +224,7 @@ namespace MediaBrowser.Controller.Resolvers
                     {
                         if (includeCreationTime)
                         {
-                            item.DateCreated = fileSystem.GetCreationTimeUtc(fileData);
+                            item.DateCreated = DateTime.UtcNow;
                         }
                         item.DateModified = fileSystem.GetLastWriteTimeUtc(fileData);
                     }
@@ -233,7 +234,7 @@ namespace MediaBrowser.Controller.Resolvers
             {
                 if (includeCreationTime)
                 {
-                    item.DateCreated = fileSystem.GetCreationTimeUtc(args.FileInfo);
+                    item.DateCreated = DateTime.UtcNow;
                 }
                 item.DateModified = fileSystem.GetLastWriteTimeUtc(args.FileInfo);
             }

+ 3 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -374,6 +374,9 @@
     <Compile Include="..\MediaBrowser.Model\Entities\EmptyRequestResult.cs">
       <Link>Entities\EmptyRequestResult.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Entities\ExtraType.cs">
+      <Link>Entities\ExtraType.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
       <Link>Entities\IHasProviderIds.cs</Link>
     </Compile>

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -334,6 +334,9 @@
     <Compile Include="..\MediaBrowser.Model\Entities\EmptyRequestResult.cs">
       <Link>Entities\EmptyRequestResult.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Entities\ExtraType.cs">
+      <Link>Entities\ExtraType.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
       <Link>Entities\IHasProviderIds.cs</Link>
     </Compile>

+ 23 - 0
MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs

@@ -0,0 +1,23 @@
+
+namespace MediaBrowser.Model.Configuration
+{
+    public class CinemaModeConfiguration
+    {
+        public bool EnableIntrosForMovies { get; set; }
+        public bool EnableIntrosForEpisodes { get; set; }
+        public bool EnableIntrosForWatchedContent { get; set; }
+        public bool EnableIntrosFromUpcomingTrailers { get; set; }
+        public bool EnableIntrosFromMoviesInLibrary { get; set; }
+        public bool EnableCustomIntro { get; set; }
+        public bool EnableIntrosParentalControl { get; set; }
+
+        public CinemaModeConfiguration()
+        {
+            EnableIntrosForMovies = true;
+            EnableCustomIntro = true;
+            EnableIntrosFromMoviesInLibrary = true;
+            EnableIntrosFromUpcomingTrailers = true;
+            EnableIntrosParentalControl = true;
+        }
+    }
+}

+ 4 - 1
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -179,6 +179,8 @@ namespace MediaBrowser.Model.Configuration
 
         public bool SaveMetadataHidden { get; set; }
 
+        public bool FindInternetTrailers { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
         /// </summary>
@@ -204,7 +206,8 @@ namespace MediaBrowser.Model.Configuration
 
             RealtimeMonitorDelay = 30;
 
-            EnableInternetProviders = true; 
+            EnableInternetProviders = true;
+            FindInternetTrailers = true;
 
             PathSubstitutions = new PathSubstitution[] { };
 

+ 1 - 0
MediaBrowser.Model/Configuration/UserConfiguration.cs

@@ -82,6 +82,7 @@ namespace MediaBrowser.Model.Configuration
         public bool SyncConnectName { get; set; }
         public bool SyncConnectImage { get; set; }
 
+
         /// <summary>
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
         /// </summary>

+ 16 - 0
MediaBrowser.Model/Entities/ExtraType.cs

@@ -0,0 +1,16 @@
+
+namespace MediaBrowser.Model.Entities
+{
+    public enum ExtraType
+    {
+        Clip = 1,
+        Trailer = 2,
+        BehindTheScenes = 3,
+        DeletedScene = 4,
+        Interview = 5,
+        Scene = 6,
+        Sample = 7,
+        ThemeSong = 8,
+        ThemeVideo = 9
+    }
+}

+ 0 - 1
MediaBrowser.Model/Entities/MediaUrl.cs

@@ -6,6 +6,5 @@ namespace MediaBrowser.Model.Entities
         public string Url { get; set; }
         public string Name { get; set; }
         public VideoSize? VideoSize { get; set; }
-        public bool IsDirectLink { get; set; }
     }
 }

+ 2 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -79,11 +79,13 @@
     <Compile Include="Collections\CollectionCreationResult.cs" />
     <Compile Include="Configuration\ChannelOptions.cs" />
     <Compile Include="Configuration\ChapterOptions.cs" />
+    <Compile Include="Configuration\CinemaModeConfiguration.cs" />
     <Compile Include="Configuration\XbmcMetadataOptions.cs" />
     <Compile Include="Configuration\SubtitlePlaybackMode.cs" />
     <Compile Include="Connect\UserLinkType.cs" />
     <Compile Include="Drawing\ImageOrientation.cs" />
     <Compile Include="Dto\StreamOptions.cs" />
+    <Compile Include="Entities\ExtraType.cs" />
     <Compile Include="FileOrganization\AutoOrganizeOptions.cs" />
     <Compile Include="FileOrganization\TvFileOrganizationOptions.cs" />
     <Compile Include="Configuration\BaseApplicationConfiguration.cs" />

+ 5 - 5
MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs

@@ -50,10 +50,6 @@ namespace MediaBrowser.Providers.BoxSets
         {
             var tmdbId = searchInfo.GetProviderId(MetadataProviders.Tmdb);
 
-            var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-            var tmdbImageUrl = tmdbSettings.images.base_url + "original";
-
             if (!string.IsNullOrEmpty(tmdbId))
             {
                 await EnsureInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
@@ -62,7 +58,11 @@ namespace MediaBrowser.Providers.BoxSets
                 var info = _json.DeserializeFromFile<RootObject>(dataFilePath);
 
                 var images = (info.images ?? new Images()).posters ?? new List<Poster>();
-               
+
+                var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+
+                var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+
                 var result = new RemoteSearchResult
                 {
                     Name = info.name,

+ 3 - 3
MediaBrowser.Providers/Channels/VideoChannelItemMetadataService.cs

@@ -1,15 +1,15 @@
-using System.Collections.Generic;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Providers.Channels
 {
-    public class VideoChannelItemMetadataService : MetadataService<ChannelVideoItem, ItemLookupInfo>
+    public class VideoChannelItemMetadataService : MetadataService<ChannelVideoItem, ChannelItemLookupInfo>
     {
         public VideoChannelItemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem)
             : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)

+ 15 - 0
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -489,6 +489,8 @@ namespace MediaBrowser.Providers.Manager
 
                     if (result.HasMetadata)
                     {
+                        NormalizeRemoteResult(result.Item);
+
                         MergeData(result.Item, temp, new List<MetadataFields>(), false, false);
 
                         refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
@@ -522,6 +524,19 @@ namespace MediaBrowser.Providers.Manager
             return refreshResult;
         }
 
+        private void NormalizeRemoteResult(TItemType item)
+        {
+            if (!ServerConfigurationManager.Configuration.FindInternetTrailers)
+            {
+                var hasTrailers = item as IHasTrailers;
+
+                if (hasTrailers != null)
+                {
+                    hasTrailers.RemoteTrailers.Clear();
+                }
+            }
+        }
+
         protected virtual void AfterRemoteRefresh(TItemType item)
         {
             

+ 2 - 1
MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs

@@ -1,11 +1,12 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;

+ 0 - 1
MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs

@@ -251,7 +251,6 @@ namespace MediaBrowser.Providers.Movies
                     hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
                     {
                         Url = string.Format("http://www.youtube.com/watch?v={0}", i.source),
-                        IsDirectLink = false,
                         Name = i.name,
                         VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
 

+ 9 - 0
MediaBrowser.Providers/Movies/MovieDbImageProvider.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
@@ -45,6 +47,13 @@ namespace MediaBrowser.Providers.Movies
                 return !trailer.IsLocalTrailer;
             }
 
+            var channelItem = item as ChannelVideoItem;
+
+            if (channelItem != null && channelItem.ContentType == ChannelMediaContentType.Trailer)
+            {
+                return true;
+            }
+            
             // Don't support local trailers
             return item is Movie || item is MusicVideo;
         }

+ 4 - 4
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -56,10 +56,6 @@ namespace MediaBrowser.Providers.Movies
 
         public async Task<IEnumerable<RemoteSearchResult>> GetMovieSearchResults(ItemLookupInfo searchInfo, CancellationToken cancellationToken)
         {
-            var tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-            var tmdbImageUrl = tmdbSettings.images.base_url + "original";
-
             var tmdbId = searchInfo.GetProviderId(MetadataProviders.Tmdb);
 
             if (!string.IsNullOrEmpty(tmdbId))
@@ -72,6 +68,10 @@ namespace MediaBrowser.Providers.Movies
 
                 var obj = _jsonSerializer.DeserializeFromFile<CompleteMovieData>(dataFilePath);
 
+                var tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+
+                var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+
                 var remoteResult = new RemoteSearchResult
                 {
                     Name = obj.title ?? obj.original_title ?? obj.name,

+ 23 - 1
MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs

@@ -1,6 +1,8 @@
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Providers;
 using System;
 using System.Collections.Generic;
@@ -9,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Movies
 {
-    public class MovieDbTrailerProvider : IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder
+    public class MovieDbTrailerProvider : IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder, IRemoteMetadataProvider<ChannelVideoItem, ChannelItemLookupInfo>
     {
         private readonly IHttpClient _httpClient;
 
@@ -28,6 +30,26 @@ namespace MediaBrowser.Providers.Movies
             return MovieDbProvider.Current.GetMovieSearchResults(searchInfo, cancellationToken);
         }
 
+        public Task<MetadataResult<ChannelVideoItem>> GetMetadata(ChannelItemLookupInfo info, CancellationToken cancellationToken)
+        {
+            if (info.ContentType != Model.Channels.ChannelMediaContentType.Trailer)
+            {
+                return Task.FromResult(new MetadataResult<ChannelVideoItem>());
+            }
+
+            return MovieDbProvider.Current.GetItemMetadata<ChannelVideoItem>(info, cancellationToken);
+        }
+
+        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ChannelItemLookupInfo searchInfo, CancellationToken cancellationToken)
+        {
+            if (searchInfo.ContentType != ChannelMediaContentType.Trailer)
+            {
+                return Task.FromResult<IEnumerable<RemoteSearchResult>>(new List<RemoteSearchResult>());
+            }
+            
+            return MovieDbProvider.Current.GetMovieSearchResults(searchInfo, cancellationToken);
+        }
+
         public string Name
         {
             get { return MovieDbProvider.Current.Name; }

+ 17 - 1
MediaBrowser.Providers/Movies/MovieExternalIds.cs

@@ -1,7 +1,9 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Movies
@@ -25,6 +27,13 @@ namespace MediaBrowser.Providers.Movies
 
         public bool Supports(IHasProviderIds item)
         {
+            var channelItem = item as ChannelVideoItem;
+
+            if (channelItem != null && channelItem.ContentType == ChannelMediaContentType.Trailer)
+            {
+                return true;
+            }
+
             return item is Movie || item is Trailer || item is MusicVideo;
         }
     }
@@ -140,6 +149,13 @@ namespace MediaBrowser.Providers.Movies
 
         public bool Supports(IHasProviderIds item)
         {
+            var channelItem = item as ChannelVideoItem;
+
+            if (channelItem != null && channelItem.ContentType == ChannelMediaContentType.Trailer)
+            {
+                return true;
+            }
+
             return item is Movie || item is Trailer || item is MusicVideo || item is Series || item is Episode;
         }
     }

+ 18 - 1
MediaBrowser.Providers/Omdb/OmdbItemProvider.cs

@@ -1,8 +1,10 @@
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
@@ -17,7 +19,7 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Providers.Omdb
 {
     public class OmdbItemProvider : IRemoteMetadataProvider<Series, SeriesInfo>,
-        IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<Trailer, TrailerInfo>
+        IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<Trailer, TrailerInfo>, IRemoteMetadataProvider<ChannelVideoItem, ChannelItemLookupInfo>
     {
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClient _httpClient;
@@ -45,6 +47,21 @@ namespace MediaBrowser.Providers.Omdb
             return new List<RemoteSearchResult>();
         }
 
+        public Task<MetadataResult<ChannelVideoItem>> GetMetadata(ChannelItemLookupInfo info, CancellationToken cancellationToken)
+        {
+            if (info.ContentType != ChannelMediaContentType.Trailer)
+            {
+                return Task.FromResult(new MetadataResult<ChannelVideoItem>());
+            }
+
+            return GetMovieResult<ChannelVideoItem>(info, cancellationToken);
+        }
+
+        public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ChannelItemLookupInfo searchInfo, CancellationToken cancellationToken)
+        {
+            return new List<RemoteSearchResult>();
+        }
+
         public string Name
         {
             get { return "The Open Movie Database"; }

+ 1 - 1
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)},
                 };
         }
 

+ 32 - 8
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 :

+ 136 - 0
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);
+                    }
+                }
+            }
+        }
+    }
+}

+ 2 - 2
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

+ 361 - 0
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"
+                }
+            };
+        }
+    }
+
+}

+ 1 - 2
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;
                     }

+ 32 - 3
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -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&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>
@@ -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);

+ 10 - 2
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);
+                    }
                 }
             }
 

+ 4 - 4
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;
                 }

+ 2 - 1
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"
 }

+ 31 - 15
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"
 }

+ 2 - 0
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" />

+ 5 - 1
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -571,7 +571,11 @@ namespace MediaBrowser.WebDashboard.Api
                                 "edititemmetadata.js",
                                 "edititemimages.js",
                                 "edititemsubtitles.js",
+
+                                "playbackconfiguration.js",
+                                "cinemamodeconfiguration.js",
                                 "encodingsettings.js",
+
                                 "externalplayer.js",
                                 "favorites.js",
                                 "gamesrecommendedpage.js",
@@ -610,7 +614,7 @@ namespace MediaBrowser.WebDashboard.Api
                                 "metadataconfigurationpage.js",
                                 "metadataimagespage.js",
                                 "metadatasubtitles.js",
-                                "metadataxbmc.js",
+                                "metadatakodi.js",
                                 "moviegenres.js",
                                 "moviecollections.js",
                                 "movies.js",

+ 15 - 6
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -101,6 +101,9 @@
     <Content Include="dashboard-ui\channelslatest.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\cinemamodeconfiguration.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\css\chromecast.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -161,7 +164,7 @@
     <Content Include="dashboard-ui\css\images\clients\playstore.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\css\images\clients\xbmc.png">
+    <Content Include="dashboard-ui\css\images\clients\kodi.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     <Content Include="dashboard-ui\css\images\favicon.ico">
@@ -335,7 +338,7 @@
     <Content Include="dashboard-ui\librarypathmapping.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\metadataxbmc.html">
+    <Content Include="dashboard-ui\metadatakodi.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     <Content Include="dashboard-ui\mypreferencesdisplay.html">
@@ -350,6 +353,9 @@
     <Content Include="dashboard-ui\notificationlist.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\playbackconfiguration.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\playlistedit.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -608,6 +614,9 @@
     <Content Include="dashboard-ui\scripts\chromecast.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\cinemamodeconfiguration.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\dashboardgeneral.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -659,7 +668,7 @@
     <Content Include="dashboard-ui\scripts\librarypathmapping.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\scripts\metadataxbmc.js">
+    <Content Include="dashboard-ui\scripts\metadatakodi.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     <Content Include="dashboard-ui\scripts\mypreferencesdisplay.js">
@@ -674,6 +683,9 @@
     <Content Include="dashboard-ui\scripts\notificationlist.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\playbackconfiguration.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\playlistedit.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -800,9 +812,6 @@
     <Content Include="dashboard-ui\thirdparty\cast_sender.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\thirdparty\jquery-2.0.3.min.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\thirdparty\jquery-2.1.1.min.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.434</version>
+        <version>3.0.435</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.434" />
+            <dependency id="MediaBrowser.Common" version="3.0.435" />
             <dependency id="NLog" version="3.1.0.0" />
             <dependency id="SimpleInjector" version="2.5.2" />
             <dependency id="sharpcompress" version="0.10.2" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.434</version>
+        <version>3.0.435</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 1 - 1
Nuget/MediaBrowser.Model.Signed.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Model.Signed</id>
-        <version>3.0.434</version>
+        <version>3.0.435</version>
         <title>MediaBrowser.Model - Signed Edition</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.434</version>
+        <version>3.0.435</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.434" />
+            <dependency id="MediaBrowser.Common" version="3.0.435" />
         </dependencies>
     </metadata>
     <files>