diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs
index b8ca70ba5e..df35c93a02 100644
--- a/MediaBrowser.Api/SessionsService.cs
+++ b/MediaBrowser.Api/SessionsService.cs
@@ -277,7 +277,7 @@ namespace MediaBrowser.Api
SeekPositionTicks = request.SeekPositionTicks
};
- var task = _sessionManager.SendPlaystateCommand(request.Id, command, CancellationToken.None);
+ var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None);
Task.WaitAll(task);
}
@@ -296,7 +296,7 @@ namespace MediaBrowser.Api
ItemType = request.ItemType
};
- var task = _sessionManager.SendBrowseCommand(request.Id, command, CancellationToken.None);
+ var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None);
Task.WaitAll(task);
}
@@ -307,7 +307,7 @@ namespace MediaBrowser.Api
/// The request.
public void Post(SendSystemCommand request)
{
- var task = _sessionManager.SendSystemCommand(request.Id, request.Command, CancellationToken.None);
+ var task = _sessionManager.SendSystemCommand(GetSession().Id, request.Id, request.Command, CancellationToken.None);
Task.WaitAll(task);
}
@@ -325,7 +325,7 @@ namespace MediaBrowser.Api
Text = request.Text
};
- var task = _sessionManager.SendMessageCommand(request.Id, command, CancellationToken.None);
+ var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None);
Task.WaitAll(task);
}
@@ -344,7 +344,7 @@ namespace MediaBrowser.Api
StartPositionTicks = request.StartPositionTicks
};
- var task = _sessionManager.SendPlayCommand(request.Id, command, CancellationToken.None);
+ var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None);
Task.WaitAll(task);
}
@@ -367,5 +367,14 @@ namespace MediaBrowser.Api
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
}
+
+ private SessionInfo GetSession()
+ {
+ var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request);
+
+ return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) &&
+ string.Equals(i.Client, auth.Client) &&
+ string.Equals(i.ApplicationVersion, auth.Version));
+ }
}
}
\ No newline at end of file
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index b040d3dd81..44c2249896 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 && AllowBoxSetCollapsing(request))
+ {
+ 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.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs
index fb58e58b7b..ba3b0912b6 100644
--- a/MediaBrowser.Api/VideosService.cs
+++ b/MediaBrowser.Api/VideosService.cs
@@ -1,4 +1,7 @@
-using MediaBrowser.Controller.Dto;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
@@ -22,14 +25,62 @@ 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; }
+ }
+
+ [Route("/Videos/{Id}/AlternateVersions", "POST")]
+ [Api(Description = "Assigns videos as alternates of antoher.")]
+ public class PostAlternateVersions : IReturnVoid
+ {
+ [ApiMember(Name = "AlternateVersionIds", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string AlternateVersionIds { 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; }
+ }
+
+ [Route("/Videos/{Id}/AlternateVersions", "DELETE")]
+ [Api(Description = "Assigns videos as alternates of antoher.")]
+ public class DeleteAlternateVersions : IReturnVoid
+ {
+ [ApiMember(Name = "AlternateVersionIds", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+ public string AlternateVersionIds { 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; }
+
+ [ApiMember(Name = "IsAlternateEncoding", Description = "Filter by versions that are considered alternate encodings of the original.", IsRequired = true, DataType = "bool", ParameterType = "path", Verb = "GET")]
+ public bool? IsAlternateEncoding { get; set; }
+ }
+
public class VideosService : BaseApiService
{
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IDtoService _dtoService;
- public VideosService( ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService)
+ public VideosService(ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService)
{
_libraryManager = libraryManager;
_userManager = userManager;
@@ -48,7 +99,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 +109,7 @@ 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();
@@ -71,5 +121,91 @@ namespace MediaBrowser.Api
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();
+
+ var result = new ItemsResult
+ {
+ Items = items,
+ TotalRecordCount = items.Length
+ };
+
+ return ToOptimizedSerializedResultUsingCache(result);
+ }
+
+ public void Post(PostAlternateVersions request)
+ {
+ var task = AddAlternateVersions(request);
+
+ Task.WaitAll(task);
+ }
+
+ public void Delete(DeleteAlternateVersions request)
+ {
+ var task = RemoveAlternateVersions(request);
+
+ Task.WaitAll(task);
+ }
+
+ private async Task AddAlternateVersions(PostAlternateVersions request)
+ {
+ var video = (Video)_dtoService.GetItemByDtoId(request.Id);
+
+ var list = new List();
+ var currentAlternateVersions = video.GetAlternateVersions().ToList();
+
+ foreach (var itemId in request.AlternateVersionIds.Split(',').Select(i => new Guid(i)))
+ {
+ var item = _libraryManager.GetItemById(itemId) as Video;
+
+ if (item == null)
+ {
+ throw new ArgumentException("No item exists with the supplied Id");
+ }
+
+ if (currentAlternateVersions.Any(i => i.Id == itemId))
+ {
+ throw new ArgumentException("Item already exists.");
+ }
+
+ list.Add(new LinkedChild
+ {
+ Path = item.Path,
+ Type = LinkedChildType.Manual
+ });
+
+ item.PrimaryVersionId = video.Id;
+ }
+
+ video.LinkedAlternateVersions.AddRange(list);
+
+ await video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
+
+ await video.RefreshMetadata(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ private async Task RemoveAlternateVersions(DeleteAlternateVersions request)
+ {
+ var video = (Video)_dtoService.GetItemByDtoId(request.Id);
+ }
}
}
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.Controller/Dlna/DeviceIdentification.cs b/MediaBrowser.Controller/Dlna/DeviceIdentification.cs
new file mode 100644
index 0000000000..a3a6155160
--- /dev/null
+++ b/MediaBrowser.Controller/Dlna/DeviceIdentification.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Dlna
+{
+ public class DeviceIdentification
+ {
+ ///
+ /// Gets or sets the name of the friendly.
+ ///
+ /// The name of the friendly.
+ public string FriendlyName { get; set; }
+ ///
+ /// Gets or sets the model number.
+ ///
+ /// The model number.
+ public string ModelNumber { get; set; }
+ ///
+ /// Gets or sets the serial number.
+ ///
+ /// The serial number.
+ public string SerialNumber { get; set; }
+ ///
+ /// Gets or sets the name of the model.
+ ///
+ /// 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 manufacturer URL.
+ ///
+ /// The manufacturer URL.
+ public string ManufacturerUrl { get; set; }
+ ///
+ /// Gets or sets the headers.
+ ///
+ /// The headers.
+ public List Headers { get; set; }
+
+ public DeviceIdentification()
+ {
+ Headers = new List();
+ }
+ }
+
+ public class HttpHeaderInfo
+ {
+ public string Name { get; set; }
+ public string Value { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/Dlna/DlnaProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs
similarity index 64%
rename from MediaBrowser.Controller/Dlna/DlnaProfile.cs
rename to MediaBrowser.Controller/Dlna/DeviceProfile.cs
index 33f95b7944..119cfffd74 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.
@@ -15,24 +15,6 @@ namespace MediaBrowser.Controller.Dlna
/// The type of the client.
public string ClientType { get; set; }
- ///
- /// Gets or sets the name of the friendly.
- ///
- /// The name of the friendly.
- public string FriendlyName { get; set; }
-
- ///
- /// Gets or sets the model number.
- ///
- /// The model number.
- public string ModelNumber { get; set; }
-
- ///
- /// Gets or sets the name of the model.
- ///
- /// The name of the model.
- public string ModelName { get; set; }
-
///
/// Gets or sets the transcoding profiles.
///
@@ -45,7 +27,13 @@ namespace MediaBrowser.Controller.Dlna
/// The direct play profiles.
public DirectPlayProfile[] DirectPlayProfiles { get; set; }
- public DlnaProfile()
+ ///
+ /// Gets or sets the identification.
+ ///
+ /// The identification.
+ public DeviceIdentification Identification { get; set; }
+
+ 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..6de17e5511 100644
--- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs
+++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs
@@ -8,21 +8,19 @@ 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.
///
- /// Name of the friendly.
- /// Name of the model.
- /// The model number.
- /// DlnaProfile.
- DlnaProfile GetProfile(string friendlyName, string modelName, string modelNumber);
+ /// The device information.
+ /// DeviceProfile.
+ DeviceProfile GetProfile(DeviceIdentification deviceInfo);
}
}
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/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/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 10034d7e5f..18db21f382 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
@@ -19,15 +20,64 @@ namespace MediaBrowser.Controller.Entities
public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio, IHasTags, ISupportsPlaceHolders
{
public bool IsMultiPart { get; set; }
+ public bool HasLocalAlternateVersions { get; set; }
+ public Guid? PrimaryVersionId { get; set; }
public List AdditionalPartIds { get; set; }
+ public List LocalAlternateVersionIds { get; set; }
public Video()
{
PlayableStreamFileNames = new List();
AdditionalPartIds = new List();
+ LocalAlternateVersionIds = new List();
Tags = new List();
SubtitleFiles = new List();
+ LinkedAlternateVersions = new List();
+ }
+
+ [IgnoreDataMember]
+ public int AlternateVersionCount
+ {
+ get
+ {
+ return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count;
+ }
+ }
+
+ public List LinkedAlternateVersions { get; set; }
+
+ ///
+ /// Gets the linked children.
+ ///
+ /// IEnumerable{BaseItem}.
+ public IEnumerable GetAlternateVersions()
+ {
+ var filesWithinSameDirectory = LocalAlternateVersionIds
+ .Select(i => LibraryManager.GetItemById(i))
+ .Where(i => i != null)
+ .OfType