From d55af4f5292236317f572e0bddfe9575a21c4662 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 15 Mar 2014 00:14:07 -0400 Subject: [PATCH 01/16] support grouping behind boxsets --- MediaBrowser.Api/UserLibrary/ItemsService.cs | 43 ++++++++++ .../Dlna/{DlnaProfile.cs => DeviceProfile.cs} | 4 +- .../Dlna/DirectPlayProfile.cs | 86 +++++++++++++++++-- MediaBrowser.Controller/Dlna/IDlnaManager.cs | 6 +- .../Entities/ISupportsBoxSetGrouping.cs | 19 ++++ .../Entities/Movies/Movie.cs | 13 ++- .../MediaBrowser.Controller.csproj | 3 +- MediaBrowser.Dlna/DlnaManager.cs | 50 +++++++---- MediaBrowser.Dlna/PlayTo/PlaylistItem.cs | 4 +- .../Collections/CollectionManager.cs | 22 +++++ .../Library/Validators/BoxSetPostScanTask.cs | 50 +++++++++++ ...MediaBrowser.Server.Implementations.csproj | 1 + .../ApplicationHost.cs | 2 +- 13 files changed, 269 insertions(+), 34 deletions(-) rename MediaBrowser.Controller/Dlna/{DlnaProfile.cs => DeviceProfile.cs} (96%) create mode 100644 MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs create mode 100644 MediaBrowser.Server.Implementations/Library/Validators/BoxSetPostScanTask.cs diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index b040d3dd81..ec00be9882 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -238,6 +238,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public bool? HasOfficialRating { get; set; } + + [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool CollapseBoxSetItems { get; set; } } /// @@ -315,6 +318,11 @@ namespace MediaBrowser.Api.UserLibrary items = items.AsEnumerable(); + if (request.CollapseBoxSetItems) + { + items = CollapseItemsWithinBoxSets(items, user); + } + items = ApplySortOrder(request, items, user, _libraryManager); // This must be the last filter @@ -1218,6 +1226,41 @@ namespace MediaBrowser.Api.UserLibrary return false; } + private IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user) + { + var itemsToCollapse = new List(); + var boxsets = new List(); + + var list = items.ToList(); + + foreach (var item in list.OfType()) + { + var currentBoxSets = item.BoxSetIdList + .Select(i => _libraryManager.GetItemById(i)) + .Where(i => i != null && i.IsVisible(user)) + .ToList(); + + if (currentBoxSets.Count > 0) + { + itemsToCollapse.Add(item); + boxsets.AddRange(currentBoxSets); + } + } + + return list.Except(itemsToCollapse.Cast()).Concat(boxsets).Distinct(); + } + + private bool AllowBoxSetCollapsing(GetItems request) + { + // Only allow when using default sort order + if (!string.IsNullOrEmpty(request.SortBy) && !string.Equals(request.SortBy, "SortName", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + internal static IEnumerable FilterForAdjacency(IEnumerable items, string adjacentToId) { var list = items.ToList(); diff --git a/MediaBrowser.Controller/Dlna/DlnaProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs similarity index 96% rename from MediaBrowser.Controller/Dlna/DlnaProfile.cs rename to MediaBrowser.Controller/Dlna/DeviceProfile.cs index 33f95b7944..3fecf957b7 100644 --- a/MediaBrowser.Controller/Dlna/DlnaProfile.cs +++ b/MediaBrowser.Controller/Dlna/DeviceProfile.cs @@ -1,7 +1,7 @@  namespace MediaBrowser.Controller.Dlna { - public class DlnaProfile + public class DeviceProfile { /// /// Gets or sets the name. @@ -45,7 +45,7 @@ namespace MediaBrowser.Controller.Dlna /// The direct play profiles. public DirectPlayProfile[] DirectPlayProfiles { get; set; } - public DlnaProfile() + public DeviceProfile() { DirectPlayProfiles = new DirectPlayProfile[] { }; TranscodingProfiles = new TranscodingProfile[] { }; diff --git a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs index f1922dd323..8c35b52a81 100644 --- a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs @@ -1,25 +1,97 @@ - +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Xml.Serialization; + namespace MediaBrowser.Controller.Dlna { public class DirectPlayProfile { - public string[] Containers { get; set; } - public string[] AudioCodecs { get; set; } - public string[] VideoCodecs { get; set; } + public string Container { get; set; } + public string AudioCodec { get; set; } + public string VideoCodec { get; set; } + + [IgnoreDataMember] + [XmlIgnore] + public string[] Containers + { + get + { + return (Container ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + set + { + Container = value == null ? null : string.Join(",", value); + } + } + + [IgnoreDataMember] + [XmlIgnore] + public string[] AudioCodecs + { + get + { + return (AudioCodec ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + set + { + AudioCodec = value == null ? null : string.Join(",", value); + } + } + + [IgnoreDataMember] + [XmlIgnore] + public string[] VideoCodecs + { + get + { + return (VideoCodec ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + set + { + VideoCodec = value == null ? null : string.Join(",", value); + } + } + public string MimeType { get; set; } public DlnaProfileType Type { get; set; } + public List Conditions { get; set; } + public DirectPlayProfile() { - Containers = new string[] { }; - AudioCodecs = new string[] { }; - VideoCodecs = new string[] { }; + Conditions = new List(); } } + public class ProfileCondition + { + public ProfileConditionType Condition { get; set; } + public ProfileConditionValue Value { get; set; } + } + public enum DlnaProfileType { Audio = 0, Video = 1 } + + public enum ProfileConditionType + { + Equals = 0, + NotEquals = 1, + LessThanEqual = 2, + GreaterThanEqual = 3 + } + + public enum ProfileConditionValue + { + AudioChannels, + AudioBitrate, + Filesize, + VideoWidth, + VideoHeight, + VideoBitrate, + VideoFramerate + } } diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index 017dbc8746..04f6588055 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -8,13 +8,13 @@ namespace MediaBrowser.Controller.Dlna /// Gets the dlna profiles. /// /// IEnumerable{DlnaProfile}. - IEnumerable GetProfiles(); + IEnumerable GetProfiles(); /// /// Gets the default profile. /// /// DlnaProfile. - DlnaProfile GetDefaultProfile(); + DeviceProfile GetDefaultProfile(); /// /// Gets the profile. @@ -23,6 +23,6 @@ namespace MediaBrowser.Controller.Dlna /// Name of the model. /// The model number. /// DlnaProfile. - DlnaProfile GetProfile(string friendlyName, string modelName, string modelNumber); + DeviceProfile GetProfile(string friendlyName, string modelName, string modelNumber); } } diff --git a/MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs b/MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs new file mode 100644 index 0000000000..0fd463155f --- /dev/null +++ b/MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Entities +{ + /// + /// Marker interface to denote a class that supports being hidden underneath it's boxset. + /// Just about anything can be placed into a boxset, + /// but movies should also only appear underneath and not outside separately (subject to configuration). + /// + public interface ISupportsBoxSetGrouping + { + /// + /// Gets or sets the box set identifier list. + /// + /// The box set identifier list. + List BoxSetIdList { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 9858dd5a99..f53b676105 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -1,11 +1,11 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// /// Class Movie /// - public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo + public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo, ISupportsBoxSetGrouping { public List SpecialFeatureIds { get; set; } @@ -23,6 +23,12 @@ namespace MediaBrowser.Controller.Entities.Movies public List ThemeSongIds { get; set; } public List ThemeVideoIds { get; set; } + /// + /// This is just a cache to enable quick access by Id + /// + [IgnoreDataMember] + public List BoxSetIdList { get; set; } + /// /// Gets or sets the preferred metadata country code. /// @@ -39,6 +45,7 @@ namespace MediaBrowser.Controller.Entities.Movies LocalTrailerIds = new List(); ThemeSongIds = new List(); ThemeVideoIds = new List(); + BoxSetIdList = new List(); Taglines = new List(); Keywords = new List(); } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 21a501b08e..7e5e6d9b0c 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -75,7 +75,7 @@ - + @@ -114,6 +114,7 @@ + diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 1c9cba2bea..91d205b476 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -1,4 +1,7 @@ -using MediaBrowser.Controller.Dlna; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Model.Serialization; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -6,11 +9,23 @@ namespace MediaBrowser.Dlna { public class DlnaManager : IDlnaManager { - public IEnumerable GetProfiles() - { - var list = new List(); + private IApplicationPaths _appPaths; + private readonly IXmlSerializer _xmlSerializer; + private readonly IFileSystem _fileSystem; - list.Add(new DlnaProfile + public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem) + { + _xmlSerializer = xmlSerializer; + _fileSystem = fileSystem; + + //GetProfiles(); + } + + public IEnumerable GetProfiles() + { + var list = new List(); + + list.Add(new DeviceProfile { Name = "Samsung TV (B Series)", ClientType = "DLNA", @@ -59,7 +74,7 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "Samsung TV (E/F-series)", ClientType = "DLNA", @@ -107,7 +122,7 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "Samsung TV (C/D-series)", ClientType = "DLNA", @@ -154,7 +169,7 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "Xbox 360", ClientType = "DLNA", @@ -189,7 +204,7 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "Xbox One", ModelName = "Xbox One", @@ -225,7 +240,7 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "Sony Bravia (2012)", ClientType = "DLNA", @@ -262,7 +277,7 @@ namespace MediaBrowser.Dlna }); //WDTV does not need any transcoding of the formats we support statically - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "WDTV Live", ClientType = "DLNA", @@ -284,7 +299,7 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { //Linksys DMA2100us does not need any transcoding of the formats we support statically Name = "Linksys DMA2100", @@ -307,12 +322,17 @@ namespace MediaBrowser.Dlna } }); + foreach (var item in list) + { + //_xmlSerializer.SerializeToFile(item, "d:\\" + _fileSystem.GetValidFilename(item.Name)); + } + return list; } - public DlnaProfile GetDefaultProfile() + public DeviceProfile GetDefaultProfile() { - return new DlnaProfile + return new DeviceProfile { TranscodingProfiles = new[] { @@ -345,7 +365,7 @@ namespace MediaBrowser.Dlna }; } - public DlnaProfile GetProfile(string friendlyName, string modelName, string modelNumber) + public DeviceProfile GetProfile(string friendlyName, string modelName, string modelNumber) { foreach (var profile in GetProfiles()) { diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs index cfb2c7d1ce..4f776807e2 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Dlna.PlayTo public long StartPositionTicks { get; set; } - public static PlaylistItem Create(BaseItem item, DlnaProfile profile) + public static PlaylistItem Create(BaseItem item, DeviceProfile profile) { var playlistItem = new PlaylistItem { @@ -92,7 +92,7 @@ namespace MediaBrowser.Dlna.PlayTo return true; } - private static bool IsSupported(DlnaProfile profile, TranscodingProfile transcodingProfile, string path) + private static bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, string path) { // Placeholder for future conditions return true; diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 9a196cc47a..8e70c1d3dc 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -126,6 +126,18 @@ namespace MediaBrowser.Server.Implementations.Collections ItemType = item.GetType().Name, Type = LinkedChildType.Manual }); + + var supportsGrouping = item as ISupportsBoxSetGrouping; + + if (supportsGrouping != null) + { + var boxsetIdList = supportsGrouping.BoxSetIdList.ToList(); + if (!boxsetIdList.Contains(collectionId)) + { + boxsetIdList.Add(collectionId); + } + supportsGrouping.BoxSetIdList = boxsetIdList; + } } collection.LinkedChildren.AddRange(list); @@ -156,6 +168,16 @@ namespace MediaBrowser.Server.Implementations.Collections } list.Add(child); + + var childItem = _libraryManager.GetItemById(itemId); + var supportsGrouping = childItem as ISupportsBoxSetGrouping; + + if (supportsGrouping != null) + { + var boxsetIdList = supportsGrouping.BoxSetIdList.ToList(); + boxsetIdList.Remove(collectionId); + supportsGrouping.BoxSetIdList = boxsetIdList; + } } var shortcutFiles = Directory diff --git a/MediaBrowser.Server.Implementations/Library/Validators/BoxSetPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/BoxSetPostScanTask.cs new file mode 100644 index 0000000000..f02c907c66 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/BoxSetPostScanTask.cs @@ -0,0 +1,50 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + public class BoxSetPostScanTask : ILibraryPostScanTask + { + private readonly ILibraryManager _libraryManager; + + public BoxSetPostScanTask(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + public Task Run(IProgress progress, CancellationToken cancellationToken) + { + var items = _libraryManager.RootFolder.RecursiveChildren.ToList(); + + var boxsets = items.OfType().ToList(); + + var numComplete = 0; + + foreach (var boxset in boxsets) + { + foreach (var child in boxset.GetLinkedChildren().OfType()) + { + var boxsetIdList = child.BoxSetIdList.ToList(); + if (!boxsetIdList.Contains(boxset.Id)) + { + boxsetIdList.Add(boxset.Id); + } + child.BoxSetIdList = boxsetIdList; + } + + numComplete++; + double percent = numComplete; + percent /= boxsets.Count; + progress.Report(percent * 100); + } + + progress.Report(100); + return Task.FromResult(true); + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index c44b608458..8715651331 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -165,6 +165,7 @@ + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 32932fe0ba..f55f18cc37 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -492,7 +492,7 @@ namespace MediaBrowser.ServerApplication var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger); RegisterSingleInstance(appThemeManager); - var dlnaManager = new DlnaManager(); + var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager); RegisterSingleInstance(dlnaManager); var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); From 9a6afa92889c57e5f7b0e8cc937376fe6393ebb4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 15 Mar 2014 11:17:46 -0400 Subject: [PATCH 02/16] Add new params to collection creation --- .../Collections/CollectionCreationOptions.cs | 3 +++ .../Collections/ICollectionManager.cs | 5 +++-- .../Collections/CollectionManager.cs | 9 ++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index e147e09056..74ae420950 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -14,9 +14,12 @@ namespace MediaBrowser.Controller.Collections public Dictionary ProviderIds { get; set; } + public List ItemIdList { get; set; } + public CollectionCreationOptions() { ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + ItemIdList = new List(); } } } diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index d7bc178ad3..af65bbacaf 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,4 +1,5 @@ -using System; +using MediaBrowser.Controller.Entities.Movies; +using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Collections /// /// The options. /// Task. - Task CreateCollection(CollectionCreationOptions options); + Task CreateCollection(CollectionCreationOptions options); /// /// Adds to collection. diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 8e70c1d3dc..60d631c1ad 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -26,7 +26,7 @@ namespace MediaBrowser.Server.Implementations.Collections _iLibraryMonitor = iLibraryMonitor; } - public async Task CreateCollection(CollectionCreationOptions options) + public async Task CreateCollection(CollectionCreationOptions options) { var name = options.Name; @@ -64,6 +64,13 @@ namespace MediaBrowser.Server.Implementations.Collections await collection.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None) .ConfigureAwait(false); + + if (options.ItemIdList.Count > 0) + { + await AddToCollection(collection.Id, options.ItemIdList); + } + + return collection; } finally { From db3b106fe09e8e28a99fc817937113896d5f5169 Mon Sep 17 00:00:00 2001 From: 7illusions Date: Sat, 15 Mar 2014 17:11:10 +0100 Subject: [PATCH 03/16] Updated PlayTo profiles and recognition --- MediaBrowser.Controller/Dlna/DeviceProfile.cs | 8 + MediaBrowser.Controller/Dlna/IDlnaManager.cs | 5 +- MediaBrowser.Dlna/DlnaManager.cs | 204 +++++++++++++++++- MediaBrowser.Dlna/PlayTo/DlnaController.cs | 4 +- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 3 +- 5 files changed, 216 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Controller/Dlna/DeviceProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs index 3fecf957b7..f3a3bc1713 100644 --- a/MediaBrowser.Controller/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Controller/Dlna/DeviceProfile.cs @@ -33,6 +33,14 @@ namespace MediaBrowser.Controller.Dlna /// The name of the model. public string ModelName { get; set; } + /// + /// Gets or sets the manufacturer. + /// + /// + /// The manufacturer. + /// + public string Manufacturer { get; set; } + /// /// Gets or sets the transcoding profiles. /// diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index 04f6588055..599b42dd54 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -22,7 +22,8 @@ namespace MediaBrowser.Controller.Dlna /// Name of the friendly. /// Name of the model. /// The model number. - /// DlnaProfile. - DeviceProfile GetProfile(string friendlyName, string modelName, string modelNumber); + /// The manufacturer. + /// DlnaProfile. + DeviceProfile GetProfile(string friendlyName, string modelName, string modelNumber, string manufacturer); } } diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 91d205b476..b1e6dc5e5d 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -25,6 +25,8 @@ namespace MediaBrowser.Dlna { var list = new List(); + #region Samsung + list.Add(new DeviceProfile { Name = "Samsung TV (B Series)", @@ -169,6 +171,10 @@ namespace MediaBrowser.Dlna } }); + #endregion + + #region Xbox + list.Add(new DeviceProfile { Name = "Xbox 360", @@ -198,7 +204,7 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"avi"}, - MimeType = "x-msvideo", + MimeType = "avi", Type = DlnaProfileType.Video } } @@ -240,6 +246,10 @@ namespace MediaBrowser.Dlna } }); + #endregion + + #region Sony + list.Add(new DeviceProfile { Name = "Sony Bravia (2012)", @@ -276,6 +286,173 @@ namespace MediaBrowser.Dlna } }); + list.Add(new DeviceProfile + { + Name = "Sony Bravia (2013)", + ClientType = "DLNA", + FriendlyName = @"BRAVIA (KDL-\d{2}W[689]\d{2}A.*)|(KD-\d{2}X9\d{3}A.*)", + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + Type = DlnaProfileType.Audio + }, + new TranscodingProfile + { + Container = "ts", + Type = DlnaProfileType.Video, + MimeType = "mpeg" + } + }, + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3"}, + Type = DlnaProfileType.Audio + }, + new DirectPlayProfile + { + Containers = new[]{"wma"}, + Type = DlnaProfileType.Audio, + MimeType = "x-ms-wma" + }, + new DirectPlayProfile + { + Containers = new[]{"avi"}, + Type = DlnaProfileType.Video, + MimeType = "avi" + }, + new DirectPlayProfile + { + Containers = new[]{"mp4"}, + Type = DlnaProfileType.Video, + MimeType = "mp4" + } + } + }); + + #endregion + + #region Panasonic + + list.Add(new DeviceProfile + { + //Panasonic Viera (2011|2012) Without AVI Support + Name = "Panasonic Viera E/S/ST/VT (2011)", + ClientType = "DLNA", + FriendlyName = @"(VIERA (E|S)T?(3|5)0?.*)|(VIERA VT30.*)", + Manufacturer = "Panasonic", + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + Type = DlnaProfileType.Audio + }, + new TranscodingProfile + { + Container = "ts", + Type = DlnaProfileType.Video + } + }, + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3"}, + Type = DlnaProfileType.Audio + }, + new DirectPlayProfile + { + Containers = new[]{"mkv"}, + Type = DlnaProfileType.Video + } + } + }); + + list.Add(new DeviceProfile + { + //Panasonic Viera (2011|2012) With AVI Support + Name = "Panasonic Viera G/GT/DT/UT/VT (2011/2012)", + ClientType = "DLNA", + FriendlyName = @"(VIERA (G|D|U)T?(3|5)0?.*)|(VIERA VT50.*)", + Manufacturer = "Panasonic", + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + Type = DlnaProfileType.Audio + }, + new TranscodingProfile + { + Container = "ts", + Type = DlnaProfileType.Video + } + }, + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3"}, + Type = DlnaProfileType.Audio + }, + new DirectPlayProfile + { + Containers = new[]{"mkv"}, + Type = DlnaProfileType.Video + }, + new DirectPlayProfile + { + Containers = new[]{"avi"}, + Type = DlnaProfileType.Video , + MimeType="divx" + } + } + }); + + #endregion + + //WDTV does not need any transcoding of the formats we support statically + list.Add(new DeviceProfile + { + Name = "Philips (2010-)", + FriendlyName = ".*PHILIPS.*", + ClientType = "DLNA", + ModelName = "WD TV HD Live", + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3", "wma"}, + Type = DlnaProfileType.Audio + }, + + new DirectPlayProfile + { + Containers = new[]{"avi"}, + Type = DlnaProfileType.Video, + MimeType = "avi" + }, + + new DirectPlayProfile + { + Containers = new[]{"mkv"}, + Type = DlnaProfileType.Video, + MimeType = "x-matroska" + } + } + }); + //WDTV does not need any transcoding of the formats we support statically list.Add(new DeviceProfile { @@ -322,6 +499,23 @@ namespace MediaBrowser.Dlna } }); + list.Add(new DeviceProfile + { + Name = "Denon AVR", + FriendlyName = @"Denon:\[AVR:.*", + Manufacturer = "Denon", + ClientType = "DLNA", + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3", "flac", "m4a", "wma"}, + Type = DlnaProfileType.Audio + }, + } + }); + foreach (var item in list) { //_xmlSerializer.SerializeToFile(item, "d:\\" + _fileSystem.GetValidFilename(item.Name)); @@ -365,7 +559,7 @@ namespace MediaBrowser.Dlna }; } - public DeviceProfile GetProfile(string friendlyName, string modelName, string modelNumber) + public DeviceProfile GetProfile(string friendlyName, string modelName, string modelNumber, string manufacturer) { foreach (var profile in GetProfiles()) { @@ -387,6 +581,12 @@ namespace MediaBrowser.Dlna continue; } + if (!string.IsNullOrEmpty(profile.Manufacturer)) + { + if (!Regex.IsMatch(manufacturer, profile.Manufacturer)) + continue; + } + return profile; } diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 5836a1639d..7885ee481b 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -386,7 +386,7 @@ namespace MediaBrowser.Dlna.PlayTo var deviceInfo = _device.Properties; - var playlistItem = PlaylistItem.Create(item, _dlnaManager.GetProfile(deviceInfo.Name, deviceInfo.ModelName, deviceInfo.ModelNumber)); + var playlistItem = PlaylistItem.Create(item, _dlnaManager.GetProfile(deviceInfo.Name, deviceInfo.ModelName, deviceInfo.ModelNumber, deviceInfo.Manufacturer)); playlistItem.StartPositionTicks = startPostionTicks; if (playlistItem.IsAudio) @@ -485,7 +485,7 @@ namespace MediaBrowser.Dlna.PlayTo _updateTimer.Stop(); _disposed = true; _device.Dispose(); - _logger.Log(LogSeverity.Debug, "PlayTo - Controller disposed"); + _logger.Log(LogSeverity.Debug, "Controller disposed"); } } } diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index d1fdc9626d..cb30498e69 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -243,8 +243,7 @@ namespace MediaBrowser.Dlna.PlayTo /// The TranscodeSettings for the device private void GetProfileSettings(DeviceInfo deviceProperties) { - var profile = _dlnaManager.GetProfile(deviceProperties.DisplayName, deviceProperties.ModelName, - deviceProperties.ModelNumber); + var profile = _dlnaManager.GetProfile(deviceProperties.Name, deviceProperties.ModelName, deviceProperties.ModelNumber, deviceProperties.Manufacturer); if (!string.IsNullOrWhiteSpace(profile.Name)) { From e4a84aac974414eb517e17782caf709b369588d2 Mon Sep 17 00:00:00 2001 From: 7illusions Date: Sat, 15 Mar 2014 19:47:50 +0100 Subject: [PATCH 04/16] Improved PlayTo profile Samsung F series --- MediaBrowser.Dlna/DlnaManager.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index b1e6dc5e5d..6766400d9e 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Dlna new TranscodingProfile { Container = "mp3", - Type = DlnaProfileType.Audio + Type = DlnaProfileType.Audio, }, new TranscodingProfile { @@ -54,7 +54,7 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"mp3"}, - Type = DlnaProfileType.Audio + Type = DlnaProfileType.Audio, }, new DirectPlayProfile { @@ -74,13 +74,15 @@ namespace MediaBrowser.Dlna Type = DlnaProfileType.Video } } + + }); list.Add(new DeviceProfile { Name = "Samsung TV (E/F-series)", ClientType = "DLNA", - FriendlyName = @"(^\[TV\][A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung", + FriendlyName = @"(^\[TV\][A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung|(^\[TV\]Samsung [A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)", ModelNumber = @"(1\.0)|(AllShare1\.0)", TranscodingProfiles = new[] From d7cfa0d22cad210fb37dd8aa6bcf41b416129e58 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 15 Mar 2014 16:08:06 -0400 Subject: [PATCH 05/16] add dlna pause fix --- MediaBrowser.Api/UserLibrary/ItemsService.cs | 2 +- MediaBrowser.Dlna/PlayTo/Device.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 6 ++++-- .../MediaBrowser.WebDashboard.csproj | 8 +++++++- Nuget/MediaBrowser.Common.Internal.nuspec | 4 ++-- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 ++-- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index ec00be9882..44c2249896 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -318,7 +318,7 @@ namespace MediaBrowser.Api.UserLibrary items = items.AsEnumerable(); - if (request.CollapseBoxSetItems) + if (request.CollapseBoxSetItems && AllowBoxSetCollapsing(request)) { items = CollapseItemsWithinBoxSets(items, user); } diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 4120c1a7f3..2e092a939a 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -364,7 +364,7 @@ namespace MediaBrowser.Dlna.PlayTo var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); - var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0)) + var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); await Task.Delay(50).ConfigureAwait(false); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index e5127a07a3..8d54a64e91 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -464,6 +464,7 @@ namespace MediaBrowser.WebDashboard.Api "editorsidebar.js", "librarymenu.js", //"chromecast.js", + "contextmenu.js", "ratingdialog.js", "aboutpage.js", @@ -584,7 +585,7 @@ namespace MediaBrowser.WebDashboard.Api await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); await AppendResource(memoryStream, "thirdparty/autonumeric/autoNumeric.min.js", newLineBytes).ConfigureAwait(false); - + var assembly = GetType().Assembly; await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false); @@ -607,6 +608,7 @@ namespace MediaBrowser.WebDashboard.Api { "site.css", "chromecast.css", + "contextmenu.css", "mediaplayer.css", "librarybrowser.css", "detailtable.css", @@ -630,7 +632,7 @@ namespace MediaBrowser.WebDashboard.Api { await AppendResource(memoryStream, "css/" + file, newLineBytes).ConfigureAwait(false); } - + memoryStream.Position = 0; return memoryStream; } diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 90a8cedd34..04ce347fb6 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -96,6 +96,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -488,6 +491,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -552,7 +558,7 @@ PreserveNewest - PreserveNewest + PreserveNewest PreserveNewest diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 9ffd2c3e26..38422d5452 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.340 + 3.0.341 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 49d410c578..fd83505a17 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.340 + 3.0.341 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index acbce81ae6..bef95570ea 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.340 + 3.0.341 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + From bf30936550a0b9be69e646a1b27988914ce9ec4a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 15 Mar 2014 18:52:43 -0400 Subject: [PATCH 06/16] #712 - Support grouping multiple versions of a movie --- MediaBrowser.Api/VideosService.cs | 50 +++++- MediaBrowser.Controller/Entities/BaseItem.cs | 77 +++++++++ MediaBrowser.Controller/Entities/Folder.cs | 112 ++++-------- MediaBrowser.Controller/Entities/Video.cs | 163 +++++++++++++++++- .../Resolvers/EntityResolutionHelper.cs | 16 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 1 + .../Dto/DtoService.cs | 1 + .../Library/Resolvers/Movies/MovieResolver.cs | 86 +++++++-- .../Resolvers/MovieResolverTests.cs | 32 ++-- 9 files changed, 414 insertions(+), 124 deletions(-) diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index fb58e58b7b..31fda199e5 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -22,6 +22,21 @@ namespace MediaBrowser.Api [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } + + [Route("/Videos/{Id}/AlternateVersions", "GET")] + [Api(Description = "Gets alternate versions of a video.")] + public class GetAlternateVersions : IReturn + { + [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? UserId { get; set; } + + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } public class VideosService : BaseApiService { @@ -48,7 +63,7 @@ namespace MediaBrowser.Api var item = string.IsNullOrEmpty(request.Id) ? (request.UserId.HasValue ? user.RootFolder - : (Folder)_libraryManager.RootFolder) + : _libraryManager.RootFolder) : _dtoService.GetItemByDtoId(request.Id, request.UserId); // Get everything @@ -58,8 +73,37 @@ namespace MediaBrowser.Api var video = (Video)item; - var items = video.AdditionalPartIds.Select(_libraryManager.GetItemById) - .OrderBy(i => i.SortName) + var items = video.GetAdditionalParts() + .Select(i => _dtoService.GetBaseItemDto(i, fields, user, video)) + .ToArray(); + + var result = new ItemsResult + { + Items = items, + TotalRecordCount = items.Length + }; + + return ToOptimizedSerializedResultUsingCache(result); + } + + public object Get(GetAlternateVersions request) + { + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + + var item = string.IsNullOrEmpty(request.Id) + ? (request.UserId.HasValue + ? user.RootFolder + : _libraryManager.RootFolder) + : _dtoService.GetItemByDtoId(request.Id, request.UserId); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)) + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); + + var video = (Video)item; + + var items = video.GetAlternateVersions() .Select(i => _dtoService.GetBaseItemDto(i, fields, user, video)) .ToArray(); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index e0c792307e..be64d20c33 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -954,6 +954,83 @@ namespace MediaBrowser.Controller.Entities return (DateTime.UtcNow - DateCreated).TotalDays < ConfigurationManager.Configuration.RecentItemDays; } + /// + /// Gets the linked child. + /// + /// The info. + /// BaseItem. + protected BaseItem GetLinkedChild(LinkedChild info) + { + // First get using the cached Id + if (info.ItemId.HasValue) + { + if (info.ItemId.Value == Guid.Empty) + { + return null; + } + + var itemById = LibraryManager.GetItemById(info.ItemId.Value); + + if (itemById != null) + { + return itemById; + } + } + + var item = FindLinkedChild(info); + + // If still null, log + if (item == null) + { + // Don't keep searching over and over + info.ItemId = Guid.Empty; + } + else + { + // Cache the id for next time + info.ItemId = item.Id; + } + + return item; + } + + private BaseItem FindLinkedChild(LinkedChild info) + { + if (!string.IsNullOrEmpty(info.Path)) + { + var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); + + if (itemByPath == null) + { + Logger.Warn("Unable to find linked item at path {0}", info.Path); + } + + return itemByPath; + } + + if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType)) + { + return LibraryManager.RootFolder.RecursiveChildren.FirstOrDefault(i => + { + if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase)) + { + if (info.ItemYear.HasValue) + { + return info.ItemYear.Value == (i.ProductionYear ?? -1); + } + return true; + } + } + + return false; + }); + } + + return null; + } + /// /// Adds a person to the item /// diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index ee371680ef..45daaba0b2 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -354,18 +354,43 @@ namespace MediaBrowser.Controller.Entities private bool IsValidFromResolver(BaseItem current, BaseItem newItem) { - var currentAsPlaceHolder = current as ISupportsPlaceHolders; + var currentAsVideo = current as Video; - if (currentAsPlaceHolder != null) + if (currentAsVideo != null) { - var newHasPlaceHolder = newItem as ISupportsPlaceHolders; + var newAsVideo = newItem as Video; - if (newHasPlaceHolder != null) + if (newAsVideo != null) { - if (currentAsPlaceHolder.IsPlaceHolder != newHasPlaceHolder.IsPlaceHolder) + if (currentAsVideo.IsPlaceHolder != newAsVideo.IsPlaceHolder) { return false; } + if (currentAsVideo.IsMultiPart != newAsVideo.IsMultiPart) + { + return false; + } + if (currentAsVideo.HasLocalAlternateVersions != newAsVideo.HasLocalAlternateVersions) + { + return false; + } + } + } + else + { + var currentAsPlaceHolder = current as ISupportsPlaceHolders; + + if (currentAsPlaceHolder != null) + { + var newHasPlaceHolder = newItem as ISupportsPlaceHolders; + + if (newHasPlaceHolder != null) + { + if (currentAsPlaceHolder.IsPlaceHolder != newHasPlaceHolder.IsPlaceHolder) + { + return false; + } + } } } @@ -898,83 +923,6 @@ namespace MediaBrowser.Controller.Entities .Where(i => i != null); } - /// - /// Gets the linked child. - /// - /// The info. - /// BaseItem. - private BaseItem GetLinkedChild(LinkedChild info) - { - // First get using the cached Id - if (info.ItemId.HasValue) - { - if (info.ItemId.Value == Guid.Empty) - { - return null; - } - - var itemById = LibraryManager.GetItemById(info.ItemId.Value); - - if (itemById != null) - { - return itemById; - } - } - - var item = FindLinkedChild(info); - - // If still null, log - if (item == null) - { - // Don't keep searching over and over - info.ItemId = Guid.Empty; - } - else - { - // Cache the id for next time - info.ItemId = item.Id; - } - - return item; - } - - private BaseItem FindLinkedChild(LinkedChild info) - { - if (!string.IsNullOrEmpty(info.Path)) - { - var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); - - if (itemByPath == null) - { - Logger.Warn("Unable to find linked item at path {0}", info.Path); - } - - return itemByPath; - } - - if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType)) - { - return LibraryManager.RootFolder.RecursiveChildren.FirstOrDefault(i => - { - if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase)) - { - if (info.ItemYear.HasValue) - { - return info.ItemYear.Value == (i.ProductionYear ?? -1); - } - return true; - } - } - - return false; - }); - } - - return null; - } - protected override async Task RefreshedOwnedItems(MetadataRefreshOptions options, List fileSystemChildren, CancellationToken cancellationToken) { var changesFound = false; diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 10034d7e5f..e30458dd8f 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -19,15 +19,63 @@ namespace MediaBrowser.Controller.Entities public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio, IHasTags, ISupportsPlaceHolders { public bool IsMultiPart { get; set; } + public bool HasLocalAlternateVersions { get; set; } public List AdditionalPartIds { get; set; } + public List AlternateVersionIds { get; set; } public Video() { PlayableStreamFileNames = new List(); AdditionalPartIds = new List(); + AlternateVersionIds = new List(); Tags = new List(); SubtitleFiles = new List(); + LinkedAlternateVersions = new List(); + } + + [IgnoreDataMember] + public bool HasAlternateVersions + { + get + { + return HasLocalAlternateVersions || LinkedAlternateVersions.Count > 0; + } + } + + public List LinkedAlternateVersions { get; set; } + + /// + /// Gets the linked children. + /// + /// IEnumerable{BaseItem}. + public IEnumerable GetAlternateVersions() + { + var filesWithinSameDirectory = AlternateVersionIds + .Select(i => LibraryManager.GetItemById(i)) + .Where(i => i != null) + .OfType