Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Tim Hobbs 2014-03-17 15:47:22 -07:00
commit cf43180a2d
48 changed files with 1421 additions and 337 deletions

View File

@ -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
/// <param name="request">The request.</param>
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));
}
}
}

View File

@ -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; }
}
/// <summary>
@ -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<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
{
var itemsToCollapse = new List<ISupportsBoxSetGrouping>();
var boxsets = new List<BaseItem>();
var list = items.ToList();
foreach (var item in list.OfType<ISupportsBoxSetGrouping>())
{
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<BaseItem>()).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<BaseItem> FilterForAdjacency(IEnumerable<BaseItem> items, string adjacentToId)
{
var list = items.ToList();

View File

@ -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<ItemsResult>
{
[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; }
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[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; }
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[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; }
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[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<LinkedChild>();
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);
}
}
}

View File

@ -14,9 +14,12 @@ namespace MediaBrowser.Controller.Collections
public Dictionary<string, string> ProviderIds { get; set; }
public List<Guid> ItemIdList { get; set; }
public CollectionCreationOptions()
{
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
ItemIdList = new List<Guid>();
}
}
}

View File

@ -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
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task.</returns>
Task CreateCollection(CollectionCreationOptions options);
Task<BoxSet> CreateCollection(CollectionCreationOptions options);
/// <summary>
/// Adds to collection.

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
namespace MediaBrowser.Controller.Dlna
{
public class DeviceIdentification
{
/// <summary>
/// Gets or sets the name of the friendly.
/// </summary>
/// <value>The name of the friendly.</value>
public string FriendlyName { get; set; }
/// <summary>
/// Gets or sets the model number.
/// </summary>
/// <value>The model number.</value>
public string ModelNumber { get; set; }
/// <summary>
/// Gets or sets the serial number.
/// </summary>
/// <value>The serial number.</value>
public string SerialNumber { get; set; }
/// <summary>
/// Gets or sets the name of the model.
/// </summary>
/// <value>The name of the model.</value>
public string ModelName { get; set; }
/// <summary>
/// Gets or sets the manufacturer.
/// </summary>
/// <value>
/// The manufacturer.
/// </value>
public string Manufacturer { get; set; }
/// <summary>
/// Gets or sets the manufacturer URL.
/// </summary>
/// <value>The manufacturer URL.</value>
public string ManufacturerUrl { get; set; }
/// <summary>
/// Gets or sets the headers.
/// </summary>
/// <value>The headers.</value>
public List<HttpHeaderInfo> Headers { get; set; }
public DeviceIdentification()
{
Headers = new List<HttpHeaderInfo>();
}
}
public class HttpHeaderInfo
{
public string Name { get; set; }
public string Value { get; set; }
}
}

View File

@ -1,7 +1,7 @@

namespace MediaBrowser.Controller.Dlna
{
public class DlnaProfile
public class DeviceProfile
{
/// <summary>
/// Gets or sets the name.
@ -15,24 +15,6 @@ namespace MediaBrowser.Controller.Dlna
/// <value>The type of the client.</value>
public string ClientType { get; set; }
/// <summary>
/// Gets or sets the name of the friendly.
/// </summary>
/// <value>The name of the friendly.</value>
public string FriendlyName { get; set; }
/// <summary>
/// Gets or sets the model number.
/// </summary>
/// <value>The model number.</value>
public string ModelNumber { get; set; }
/// <summary>
/// Gets or sets the name of the model.
/// </summary>
/// <value>The name of the model.</value>
public string ModelName { get; set; }
/// <summary>
/// Gets or sets the transcoding profiles.
/// </summary>
@ -45,7 +27,13 @@ namespace MediaBrowser.Controller.Dlna
/// <value>The direct play profiles.</value>
public DirectPlayProfile[] DirectPlayProfiles { get; set; }
public DlnaProfile()
/// <summary>
/// Gets or sets the identification.
/// </summary>
/// <value>The identification.</value>
public DeviceIdentification Identification { get; set; }
public DeviceProfile()
{
DirectPlayProfiles = new DirectPlayProfile[] { };
TranscodingProfiles = new TranscodingProfile[] { };

View File

@ -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<ProfileCondition> Conditions { get; set; }
public DirectPlayProfile()
{
Containers = new string[] { };
AudioCodecs = new string[] { };
VideoCodecs = new string[] { };
Conditions = new List<ProfileCondition>();
}
}
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
}
}

View File

@ -8,21 +8,19 @@ namespace MediaBrowser.Controller.Dlna
/// Gets the dlna profiles.
/// </summary>
/// <returns>IEnumerable{DlnaProfile}.</returns>
IEnumerable<DlnaProfile> GetProfiles();
IEnumerable<DeviceProfile> GetProfiles();
/// <summary>
/// Gets the default profile.
/// </summary>
/// <returns>DlnaProfile.</returns>
DlnaProfile GetDefaultProfile();
DeviceProfile GetDefaultProfile();
/// <summary>
/// Gets the profile.
/// </summary>
/// <param name="friendlyName">Name of the friendly.</param>
/// <param name="modelName">Name of the model.</param>
/// <param name="modelNumber">The model number.</param>
/// <returns>DlnaProfile.</returns>
DlnaProfile GetProfile(string friendlyName, string modelName, string modelNumber);
/// <param name="deviceInfo">The device information.</param>
/// <returns>DeviceProfile.</returns>
DeviceProfile GetProfile(DeviceIdentification deviceInfo);
}
}

View File

@ -954,6 +954,83 @@ namespace MediaBrowser.Controller.Entities
return (DateTime.UtcNow - DateCreated).TotalDays < ConfigurationManager.Configuration.RecentItemDays;
}
/// <summary>
/// Gets the linked child.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>BaseItem.</returns>
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;
}
/// <summary>
/// Adds a person to the item
/// </summary>

View File

@ -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);
}
/// <summary>
/// Gets the linked child.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>BaseItem.</returns>
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<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var changesFound = false;

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// 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).
/// </summary>
public interface ISupportsBoxSetGrouping
{
/// <summary>
/// Gets or sets the box set identifier list.
/// </summary>
/// <value>The box set identifier list.</value>
List<Guid> BoxSetIdList { get; set; }
}
}

View File

@ -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
/// <summary>
/// Class Movie
/// </summary>
public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>
public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
{
public List<Guid> SpecialFeatureIds { get; set; }
@ -23,6 +23,12 @@ namespace MediaBrowser.Controller.Entities.Movies
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
/// <summary>
/// This is just a cache to enable quick access by Id
/// </summary>
[IgnoreDataMember]
public List<Guid> BoxSetIdList { get; set; }
/// <summary>
/// Gets or sets the preferred metadata country code.
/// </summary>
@ -39,6 +45,7 @@ namespace MediaBrowser.Controller.Entities.Movies
LocalTrailerIds = new List<Guid>();
ThemeSongIds = new List<Guid>();
ThemeVideoIds = new List<Guid>();
BoxSetIdList = new List<Guid>();
Taglines = new List<string>();
Keywords = new List<string>();
}

View File

@ -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<Guid> AdditionalPartIds { get; set; }
public List<Guid> LocalAlternateVersionIds { get; set; }
public Video()
{
PlayableStreamFileNames = new List<string>();
AdditionalPartIds = new List<Guid>();
LocalAlternateVersionIds = new List<Guid>();
Tags = new List<string>();
SubtitleFiles = new List<string>();
LinkedAlternateVersions = new List<LinkedChild>();
}
[IgnoreDataMember]
public int AlternateVersionCount
{
get
{
return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count;
}
}
public List<LinkedChild> LinkedAlternateVersions { get; set; }
/// <summary>
/// Gets the linked children.
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
public IEnumerable<BaseItem> GetAlternateVersions()
{
var filesWithinSameDirectory = LocalAlternateVersionIds
.Select(i => LibraryManager.GetItemById(i))
.Where(i => i != null)
.OfType<Video>();
var linkedVersions = LinkedAlternateVersions
.Select(GetLinkedChild)
.Where(i => i != null)
.OfType<Video>();
return filesWithinSameDirectory.Concat(linkedVersions)
.OrderBy(i => i.SortName);
}
/// <summary>
/// Gets the additional parts.
/// </summary>
/// <returns>IEnumerable{Video}.</returns>
public IEnumerable<Video> GetAdditionalParts()
{
return AdditionalPartIds
.Select(i => LibraryManager.GetItemById(i))
.Where(i => i != null)
.OfType<Video>()
.OrderBy(i => i.SortName);
}
/// <summary>
@ -43,13 +93,13 @@ namespace MediaBrowser.Controller.Entities
public bool HasSubtitles { get; set; }
public bool IsPlaceHolder { get; set; }
/// <summary>
/// Gets or sets the tags.
/// </summary>
/// <value>The tags.</value>
public List<string> Tags { get; set; }
/// <summary>
/// Gets or sets the video bit rate.
/// </summary>
@ -167,22 +217,50 @@ namespace MediaBrowser.Controller.Entities
{
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
// Must have a parent to have additional parts
// Must have a parent to have additional parts or alternate versions
// In other words, it must be part of the Parent/Child tree
// The additional parts won't have additional parts themselves
if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null)
if (LocationType == LocationType.FileSystem && Parent != null)
{
var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
if (additionalPartsChanged)
if (IsMultiPart)
{
hasChanges = true;
var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
if (additionalPartsChanged)
{
hasChanges = true;
}
}
else
{
RefreshLinkedAlternateVersions();
var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
if (additionalPartsChanged)
{
hasChanges = true;
}
}
}
return hasChanges;
}
private bool RefreshLinkedAlternateVersions()
{
foreach (var child in LinkedAlternateVersions)
{
// Reset the cached value
if (child.ItemId.HasValue && child.ItemId.Value == Guid.Empty)
{
child.ItemId = null;
}
}
return false;
}
/// <summary>
/// Refreshes the additional parts.
/// </summary>
@ -223,7 +301,7 @@ namespace MediaBrowser.Controller.Entities
{
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFolder(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
}
return false;
@ -258,6 +336,123 @@ namespace MediaBrowser.Controller.Entities
}).OrderBy(i => i.Path).ToList();
}
private async Task<bool> RefreshAlternateVersionsWithinSameDirectory(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var newItems = HasLocalAlternateVersions ?
LoadAlternateVersionsWithinSameDirectory(fileSystemChildren, options.DirectoryService).ToList() :
new List<Video>();
var newItemIds = newItems.Select(i => i.Id).ToList();
var itemsChanged = !LocalAlternateVersionIds.SequenceEqual(newItemIds);
var tasks = newItems.Select(i => RefreshAlternateVersion(options, i, cancellationToken));
await Task.WhenAll(tasks).ConfigureAwait(false);
LocalAlternateVersionIds = newItemIds;
return itemsChanged;
}
private Task RefreshAlternateVersion(MetadataRefreshOptions options, Video video, CancellationToken cancellationToken)
{
var currentImagePath = video.GetImagePath(ImageType.Primary);
var ownerImagePath = this.GetImagePath(ImageType.Primary);
var newOptions = new MetadataRefreshOptions
{
DirectoryService = options.DirectoryService,
ImageRefreshMode = options.ImageRefreshMode,
MetadataRefreshMode = options.MetadataRefreshMode,
ReplaceAllMetadata = options.ReplaceAllMetadata
};
if (!string.Equals(currentImagePath, ownerImagePath, StringComparison.OrdinalIgnoreCase))
{
newOptions.ForceSave = true;
if (string.IsNullOrWhiteSpace(ownerImagePath))
{
video.ImageInfos.Clear();
}
else
{
video.SetImagePath(ImageType.Primary, ownerImagePath);
}
}
return video.RefreshMetadata(newOptions, cancellationToken);
}
public override async Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken)
{
await base.UpdateToRepository(updateReason, cancellationToken).ConfigureAwait(false);
foreach (var item in LocalAlternateVersionIds.Select(i => LibraryManager.GetItemById(i)))
{
item.ImageInfos = ImageInfos;
item.Overview = Overview;
item.ProductionYear = ProductionYear;
item.PremiereDate = PremiereDate;
item.CommunityRating = CommunityRating;
item.OfficialRating = OfficialRating;
item.Genres = Genres;
item.ProviderIds = ProviderIds;
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Loads the additional parts.
/// </summary>
/// <returns>IEnumerable{Video}.</returns>
private IEnumerable<Video> LoadAlternateVersionsWithinSameDirectory(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService)
{
IEnumerable<FileSystemInfo> files;
var path = Path;
var currentFilename = System.IO.Path.GetFileNameWithoutExtension(path) ?? string.Empty;
// Only support this for video files. For folder rips, they'll have to use the linking feature
if (VideoType == VideoType.VideoFile || VideoType == VideoType.Iso)
{
files = fileSystemChildren.Where(i =>
{
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
return false;
}
return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) &&
EntityResolutionHelper.IsVideoFile(i.FullName) &&
i.Name.StartsWith(currentFilename, StringComparison.OrdinalIgnoreCase);
});
}
else
{
files = new List<FileSystemInfo>();
}
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;
}
video.PrimaryVersionId = Id;
return video;
// Sort them so that the list can be easily compared for changes
}).OrderBy(i => i.Path).ToList();
}
public override IEnumerable<string> GetDeletePaths()
{
if (!IsInMixedFolder)

View File

@ -25,5 +25,11 @@ namespace MediaBrowser.Controller
/// </summary>
/// <value><c>true</c> if [supports automatic run at startup]; otherwise, <c>false</c>.</value>
bool SupportsAutoRunAtStartup { get; }
/// <summary>
/// Gets the HTTP server port.
/// </summary>
/// <value>The HTTP server port.</value>
int HttpServerPort { get; }
}
}

View File

@ -73,9 +73,10 @@
<Compile Include="Channels\IChannelManager.cs" />
<Compile Include="Collections\CollectionCreationOptions.cs" />
<Compile Include="Collections\ICollectionManager.cs" />
<Compile Include="Dlna\DeviceIdentification.cs" />
<Compile Include="Dlna\DirectPlayProfile.cs" />
<Compile Include="Dlna\IDlnaManager.cs" />
<Compile Include="Dlna\DlnaProfile.cs" />
<Compile Include="Dlna\DeviceProfile.cs" />
<Compile Include="Dlna\TranscodingProfile.cs" />
<Compile Include="Drawing\IImageProcessor.cs" />
<Compile Include="Drawing\ImageFormat.cs" />
@ -114,6 +115,7 @@
<Compile Include="Entities\ILibraryItem.cs" />
<Compile Include="Entities\ImageSourceInfo.cs" />
<Compile Include="Entities\IMetadataContainer.cs" />
<Compile Include="Entities\ISupportsBoxSetGrouping.cs" />
<Compile Include="Entities\ISupportsPlaceHolders.cs" />
<Compile Include="Entities\ItemImageInfo.cs" />
<Compile Include="Entities\LinkedChild.cs" />

View File

@ -71,7 +71,21 @@ namespace MediaBrowser.Controller.Resolvers
throw new ArgumentNullException("path");
}
return MultiFileRegex.Match(path).Success || MultiFolderRegex.Match(path).Success;
path = Path.GetFileName(path);
return MultiFileRegex.Match(path).Success;
}
public static bool IsMultiPartFolder(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
path = Path.GetFileName(path);
return MultiFolderRegex.Match(path).Success;
}
/// <summary>

View File

@ -86,47 +86,52 @@ namespace MediaBrowser.Controller.Session
/// <summary>
/// Sends the system command.
/// </summary>
/// <param name="controllingSessionId">The controlling session identifier.</param>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken);
Task SendSystemCommand(Guid controllingSessionId, Guid sessionId, SystemCommand command, CancellationToken cancellationToken);
/// <summary>
/// Sends the message command.
/// </summary>
/// <param name="controllingSessionId">The controlling session identifier.</param>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken);
Task SendMessageCommand(Guid controllingSessionId, Guid sessionId, MessageCommand command, CancellationToken cancellationToken);
/// <summary>
/// Sends the play command.
/// </summary>
/// <param name="controllingSessionId">The controlling session identifier.</param>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken);
Task SendPlayCommand(Guid controllingSessionId, Guid sessionId, PlayRequest command, CancellationToken cancellationToken);
/// <summary>
/// Sends the browse command.
/// </summary>
/// <param name="controllingSessionId">The controlling session identifier.</param>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken);
Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken);
/// <summary>
/// Sends the playstate command.
/// </summary>
/// <param name="controllingSessionId">The controlling session identifier.</param>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken);
Task SendPlaystateCommand(Guid controllingSessionId, Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken);
/// <summary>
/// Sends the restart required message.

View File

@ -1,29 +1,51 @@
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.Linq;
using System.Text.RegularExpressions;
namespace MediaBrowser.Dlna
{
public class DlnaManager : IDlnaManager
{
public IEnumerable<DlnaProfile> GetProfiles()
{
var list = new List<DlnaProfile>();
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<DeviceProfile> GetProfiles()
{
var list = new List<DeviceProfile>();
#region Samsung
list.Add(new DeviceProfile
{
Name = "Samsung TV (B Series)",
ClientType = "DLNA",
FriendlyName = "^TV$",
ModelNumber = @"1\.0",
ModelName = "Samsung DTV DMR",
Identification = new DeviceIdentification
{
FriendlyName = "^TV$",
ModelNumber = @"1\.0",
ModelName = "Samsung DTV DMR"
},
TranscodingProfiles = new[]
{
new TranscodingProfile
{
Container = "mp3",
Type = DlnaProfileType.Audio
Type = DlnaProfileType.Audio,
},
new TranscodingProfile
{
@ -37,7 +59,7 @@ namespace MediaBrowser.Dlna
new DirectPlayProfile
{
Containers = new[]{"mp3"},
Type = DlnaProfileType.Audio
Type = DlnaProfileType.Audio,
},
new DirectPlayProfile
{
@ -57,14 +79,20 @@ namespace MediaBrowser.Dlna
Type = DlnaProfileType.Video
}
}
});
list.Add(new DlnaProfile
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",
ModelNumber = @"(1\.0)|(AllShare1\.0)",
Identification = new DeviceIdentification
{
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[]
{
@ -107,12 +135,17 @@ namespace MediaBrowser.Dlna
}
});
list.Add(new DlnaProfile
list.Add(new DeviceProfile
{
Name = "Samsung TV (C/D-series)",
ClientType = "DLNA",
FriendlyName = @"(^TV-\d{2}C\d{3}.*)|(^\[TV\][A-Z]{2}\d{2}(D)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung",
ModelNumber = @"(1\.0)|(AllShare1\.0)",
Identification = new DeviceIdentification
{
FriendlyName = @"(^TV-\d{2}C\d{3}.*)|(^\[TV\][A-Z]{2}\d{2}(D)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung",
ModelNumber = @"(1\.0)|(AllShare1\.0)"
},
TranscodingProfiles = new[]
{
new TranscodingProfile
@ -154,11 +187,20 @@ namespace MediaBrowser.Dlna
}
});
list.Add(new DlnaProfile
#endregion
#region Xbox
list.Add(new DeviceProfile
{
Name = "Xbox 360",
ClientType = "DLNA",
ModelName = "Xbox 360",
Identification = new DeviceIdentification
{
ModelName = "Xbox 360"
},
TranscodingProfiles = new[]
{
new TranscodingProfile
@ -183,18 +225,23 @@ namespace MediaBrowser.Dlna
new DirectPlayProfile
{
Containers = new[]{"avi"},
MimeType = "x-msvideo",
MimeType = "avi",
Type = DlnaProfileType.Video
}
}
});
list.Add(new DlnaProfile
list.Add(new DeviceProfile
{
Name = "Xbox One",
ModelName = "Xbox One",
ClientType = "DLNA",
FriendlyName = "Xbox-SystemOS",
Identification = new DeviceIdentification
{
ModelName = "Xbox One",
FriendlyName = "Xbox-SystemOS"
},
TranscodingProfiles = new[]
{
new TranscodingProfile
@ -225,11 +272,19 @@ namespace MediaBrowser.Dlna
}
});
list.Add(new DlnaProfile
#endregion
#region Sony
list.Add(new DeviceProfile
{
Name = "Sony Bravia (2012)",
ClientType = "DLNA",
FriendlyName = @"BRAVIA KDL-\d{2}[A-Z]X\d5(\d|G).*",
Identification = new DeviceIdentification
{
FriendlyName = @"BRAVIA KDL-\d{2}[A-Z]X\d5(\d|G).*"
},
TranscodingProfiles = new[]
{
@ -261,12 +316,199 @@ namespace MediaBrowser.Dlna
}
});
list.Add(new DeviceProfile
{
Name = "Sony Bravia (2013)",
ClientType = "DLNA",
Identification = new DeviceIdentification
{
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",
Identification = new DeviceIdentification
{
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",
Identification = new DeviceIdentification
{
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 DlnaProfile
list.Add(new DeviceProfile
{
Name = "Philips (2010-)",
ClientType = "DLNA",
Identification = new DeviceIdentification
{
FriendlyName = ".*PHILIPS.*",
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
{
Name = "WDTV Live",
ClientType = "DLNA",
ModelName = "WD TV HD Live",
Identification = new DeviceIdentification
{
ModelName = "WD TV HD Live"
},
DirectPlayProfiles = new[]
{
@ -284,12 +526,16 @@ 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",
ClientType = "DLNA",
ModelName = "DMA2100us",
Identification = new DeviceIdentification
{
ModelName = "DMA2100us"
},
DirectPlayProfiles = new[]
{
@ -307,12 +553,38 @@ namespace MediaBrowser.Dlna
}
});
list.Add(new DeviceProfile
{
Name = "Denon AVR",
ClientType = "DLNA",
Identification = new DeviceIdentification
{
FriendlyName = @"Denon:\[AVR:.*",
Manufacturer = "Denon"
},
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));
}
return list;
}
public DlnaProfile GetDefaultProfile()
public DeviceProfile GetDefaultProfile()
{
return new DlnaProfile
return new DeviceProfile
{
TranscodingProfiles = new[]
{
@ -345,32 +617,51 @@ namespace MediaBrowser.Dlna
};
}
public DlnaProfile GetProfile(string friendlyName, string modelName, string modelNumber)
public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
{
foreach (var profile in GetProfiles())
return GetProfiles().FirstOrDefault(i => IsMatch(deviceInfo, i.Identification)) ??
GetDefaultProfile();
}
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
{
if (!string.IsNullOrEmpty(profile.FriendlyName))
{
if (!Regex.IsMatch(friendlyName, profile.FriendlyName))
continue;
}
if (!string.IsNullOrEmpty(profile.ModelNumber))
{
if (!Regex.IsMatch(modelNumber, profile.ModelNumber))
continue;
}
if (!string.IsNullOrEmpty(profile.ModelName))
{
if (!Regex.IsMatch(modelName, profile.ModelName))
continue;
}
return profile;
if (!Regex.IsMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
return false;
}
return GetDefaultProfile();
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{
if (!Regex.IsMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
return false;
}
if (!string.IsNullOrEmpty(profileInfo.ModelName))
{
if (!Regex.IsMatch(deviceInfo.ModelName, profileInfo.ModelName))
return false;
}
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{
if (!Regex.IsMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
return false;
}
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{
if (!Regex.IsMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
return false;
}
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{
if (!Regex.IsMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
return false;
}
return true;
}
}
}

View File

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

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using MediaBrowser.Controller.Dlna;
namespace MediaBrowser.Dlna.PlayTo
{
@ -62,5 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo
return _services;
}
}
public DeviceIdentification ToDeviceIdentification()
{
return new DeviceIdentification
{
Manufacturer = Manufacturer,
ModelName = ModelName,
ModelNumber = ModelNumber,
FriendlyName = Name,
ManufacturerUrl = ManufacturerUrl
};
}
}
}

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@ -13,7 +14,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Timer = System.Timers.Timer;
namespace MediaBrowser.Dlna.PlayTo
{
@ -28,8 +28,12 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly INetworkManager _networkManager;
private readonly ILogger _logger;
private readonly IDlnaManager _dlnaManager;
private readonly IUserManager _userManager;
private readonly IServerApplicationHost _appHost;
private bool _playbackStarted = false;
private int UpdateTimerIntervalMs = 1000;
public bool SupportsMediaRemoteControl
{
get { return true; }
@ -46,7 +50,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager)
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost)
{
_session = session;
_itemRepository = itemRepository;
@ -54,6 +58,8 @@ namespace MediaBrowser.Dlna.PlayTo
_libraryManager = libraryManager;
_networkManager = networkManager;
_dlnaManager = dlnaManager;
_userManager = userManager;
_appHost = appHost;
_logger = logger;
}
@ -64,14 +70,12 @@ namespace MediaBrowser.Dlna.PlayTo
_device.CurrentIdChanged += Device_CurrentIdChanged;
_device.Start();
_updateTimer = new Timer(1000);
_updateTimer.Elapsed += updateTimer_Elapsed;
_updateTimer.Start();
_updateTimer = new System.Threading.Timer(updateTimer_Elapsed, null, UpdateTimerIntervalMs, UpdateTimerIntervalMs);
}
#region Device EventHandlers & Update Timer
Timer _updateTimer;
System.Threading.Timer _updateTimer;
async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
{
@ -122,27 +126,23 @@ namespace MediaBrowser.Dlna.PlayTo
/// <summary>
/// Handles the Elapsed event of the updateTimer control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="ElapsedEventArgs"/> instance containing the event data.</param>
async void updateTimer_Elapsed(object sender, ElapsedEventArgs e)
/// <param name="state">The state.</param>
private async void updateTimer_Elapsed(object state)
{
if (_disposed)
return;
((Timer)sender).Stop();
if (!IsSessionActive)
if (IsSessionActive)
{
//Session is inactive, mark it for Disposal and don't start the elapsed timer.
await _sessionManager.ReportSessionEnded(this._session.Id);
return;
await ReportProgress().ConfigureAwait(false);
}
else
{
_updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
await ReportProgress().ConfigureAwait(false);
if (!_disposed && IsSessionActive)
((Timer)sender).Start();
//Session is inactive, mark it for Disposal and don't start the elapsed timer.
await _sessionManager.ReportSessionEnded(_session.Id);
}
}
/// <summary>
@ -194,7 +194,7 @@ namespace MediaBrowser.Dlna.PlayTo
#region SendCommands
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{
_logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
@ -227,16 +227,25 @@ namespace MediaBrowser.Dlna.PlayTo
if (command.PlayCommand == PlayCommand.PlayLast)
{
AddItemsToPlaylist(playlist);
return Task.FromResult(true);
}
if (command.PlayCommand == PlayCommand.PlayNext)
{
AddItemsToPlaylist(playlist);
return Task.FromResult(true);
}
_logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
return PlayItems(playlist);
if (!string.IsNullOrWhiteSpace(command.ControllingUserId))
{
var userId = new Guid(command.ControllingUserId);
var user = _userManager.GetUserById(userId);
await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
_session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false);
}
await PlayItems(playlist).ConfigureAwait(false);
}
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
@ -376,7 +385,7 @@ namespace MediaBrowser.Dlna.PlayTo
"http",
_networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost",
"8096"
_appHost.HttpServerPort
);
}
@ -386,7 +395,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.ToDeviceIdentification()));
playlistItem.StartPositionTicks = startPostionTicks;
if (playlistItem.IsAudio)
@ -482,10 +491,10 @@ namespace MediaBrowser.Dlna.PlayTo
{
if (!_disposed)
{
_updateTimer.Stop();
_disposed = true;
_updateTimer.Dispose();
_device.Dispose();
_logger.Log(LogSeverity.Debug, "PlayTo - Controller disposed");
_logger.Log(LogSeverity.Debug, "Controller disposed");
}
}
}

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
@ -32,8 +33,9 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
private readonly IServerConfigurationManager _config;
private readonly IServerApplicationHost _appHost;
public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager)
public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost)
{
_locations = new ConcurrentDictionary<string, DateTime>();
_tokenSource = new CancellationTokenSource();
@ -46,6 +48,7 @@ namespace MediaBrowser.Dlna.PlayTo
_networkManager = networkManager;
_userManager = userManager;
_dlnaManager = dlnaManager;
_appHost = appHost;
_config = config;
}
@ -227,7 +230,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (controller == null)
{
sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager);
sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost);
}
controller.Init(device);
@ -243,8 +246,7 @@ namespace MediaBrowser.Dlna.PlayTo
/// <returns>The TranscodeSettings for the device</returns>
private void GetProfileSettings(DeviceInfo deviceProperties)
{
var profile = _dlnaManager.GetProfile(deviceProperties.DisplayName, deviceProperties.ModelName,
deviceProperties.ModelNumber);
var profile = _dlnaManager.GetProfile(deviceProperties.ToDeviceIdentification());
if (!string.IsNullOrWhiteSpace(profile.Name))
{

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
@ -22,8 +23,9 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly INetworkManager _networkManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
private readonly IServerApplicationHost _appHost;
public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager)
public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost)
{
_config = config;
_sessionManager = sessionManager;
@ -33,6 +35,7 @@ namespace MediaBrowser.Dlna.PlayTo
_networkManager = networkManager;
_userManager = userManager;
_dlnaManager = dlnaManager;
_appHost = appHost;
_logger = logManager.GetLogger("PlayTo");
}
@ -69,7 +72,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
try
{
_manager = new PlayToManager(_logger, _config, _sessionManager, _httpClient, _itemRepo, _libraryManager, _networkManager, _userManager, _dlnaManager);
_manager = new PlayToManager(_logger, _config, _sessionManager, _httpClient, _itemRepo, _libraryManager, _networkManager, _userManager, _dlnaManager, _appHost);
_manager.Start();
}
catch (Exception ex)

View File

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

View File

@ -5,5 +5,10 @@ namespace MediaBrowser.Model.Configuration
{
public bool EnablePlayTo { get; set; }
public bool EnablePlayToDebugLogging { get; set; }
public DlnaOptions()
{
EnablePlayTo = true;
}
}
}

View File

@ -494,6 +494,8 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The part count.</value>
public int? PartCount { get; set; }
public int? AlternateVersionCount { get; set; }
public string PrimaryVersionId { get; set; }
/// <summary>
/// Determines whether the specified type is type.

View File

@ -23,6 +23,12 @@ namespace MediaBrowser.Model.Session
/// </summary>
/// <value>The play command.</value>
public PlayCommand PlayCommand { get; set; }
/// <summary>
/// Gets or sets the controlling user identifier.
/// </summary>
/// <value>The controlling user identifier.</value>
public string ControllingUserId { get; set; }
}
/// <summary>

View File

@ -37,5 +37,11 @@ namespace MediaBrowser.Model.Session
public PlaystateCommand Command { get; set; }
public long? SeekPositionTicks { get; set; }
/// <summary>
/// Gets or sets the controlling user identifier.
/// </summary>
/// <value>The controlling user identifier.</value>
public string ControllingUserId { get; set; }
}
}

View File

@ -94,14 +94,13 @@ namespace MediaBrowser.Providers.All
public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, IDirectoryService directoryService)
{
var files = paths.SelectMany(directoryService.GetFiles)
.Where(i =>
{
var ext = i.Extension;
.Where(i =>
{
var ext = i.Extension;
return !string.IsNullOrEmpty(ext) &&
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
})
.Cast<FileSystemInfo>()
return !string.IsNullOrEmpty(ext) &&
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
})
.ToList();
var list = new List<LocalImageInfo>();

View File

@ -56,11 +56,16 @@ namespace MediaBrowser.Providers.Manager
/// <summary>
/// Gets the last result.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <param name="item">The item.</param>
/// <returns>ProviderResult.</returns>
protected MetadataStatus GetLastResult(Guid itemId)
protected MetadataStatus GetLastResult(IHasMetadata item)
{
return ProviderRepo.GetMetadataStatus(itemId) ?? new MetadataStatus { ItemId = itemId };
if (item.DateLastSaved == default(DateTime))
{
return new MetadataStatus { ItemId = item.Id };
}
return ProviderRepo.GetMetadataStatus(item.Id) ?? new MetadataStatus { ItemId = item.Id };
}
public async Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
@ -74,7 +79,7 @@ namespace MediaBrowser.Providers.Manager
var config = ProviderManager.GetMetadataOptions(item);
var updateType = ItemUpdateType.None;
var refreshResult = GetLastResult(item.Id);
var refreshResult = GetLastResult(item);
refreshResult.LastErrorMessage = string.Empty;
refreshResult.LastStatus = ProviderRefreshStatus.Success;

View File

@ -187,7 +187,7 @@
<Compile Include="TV\EpisodeXmlProvider.cs" />
<Compile Include="TV\EpisodeXmlParser.cs" />
<Compile Include="TV\FanArtTvUpdatesPostScanTask.cs" />
<Compile Include="TV\FanartSeasonProvider.cs" />
<Compile Include="TV\FanArtSeasonProvider.cs" />
<Compile Include="TV\FanartSeriesProvider.cs" />
<Compile Include="TV\MissingEpisodeProvider.cs" />
<Compile Include="TV\MovieDbSeriesImageProvider.cs" />

View File

@ -20,14 +20,14 @@ using System.Xml;
namespace MediaBrowser.Providers.TV
{
public class FanartSeasonProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
public class FanArtSeasonProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
public FanartSeasonProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
public FanArtSeasonProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
_config = config;
_httpClient = httpClient;

View File

@ -26,7 +26,7 @@ namespace MediaBrowser.Server.Implementations.Collections
_iLibraryMonitor = iLibraryMonitor;
}
public async Task CreateCollection(CollectionCreationOptions options)
public async Task<BoxSet> 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
{
@ -104,6 +111,7 @@ namespace MediaBrowser.Server.Implementations.Collections
}
var list = new List<LinkedChild>();
var currentLinkedChildren = collection.GetLinkedChildren().ToList();
foreach (var itemId in ids)
{
@ -114,7 +122,7 @@ namespace MediaBrowser.Server.Implementations.Collections
throw new ArgumentException("No item exists with the supplied Id");
}
if (collection.LinkedChildren.Any(i => i.ItemId.HasValue && i.ItemId == itemId))
if (currentLinkedChildren.Any(i => i.Id == itemId))
{
throw new ArgumentException("Item already exists in collection");
}
@ -126,6 +134,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 +176,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

View File

@ -1082,6 +1082,12 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.IsHD = video.IsHD;
dto.PartCount = video.AdditionalPartIds.Count + 1;
dto.AlternateVersionCount = video.AlternateVersionCount;
if (video.PrimaryVersionId.HasValue)
{
dto.PrimaryVersionId = video.PrimaryVersionId.Value.ToString("N");
}
if (fields.Contains(ItemFields.Chapters))
{

View File

@ -267,7 +267,7 @@ namespace MediaBrowser.Server.Implementations.IO
}
else
{
Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary." + path);
Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary.", path);
newWatcher.Dispose();
}

View File

@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{
@ -20,11 +21,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{
private readonly IServerApplicationPaths _applicationPaths;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager)
public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager, ILogger logger)
{
_applicationPaths = appPaths;
_libraryManager = libraryManager;
_logger = logger;
}
/// <summary>
@ -76,29 +79,29 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{
if (string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false);
return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false, false);
}
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false);
return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false, false);
}
if (string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true);
return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true);
return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, false);
}
if (string.IsNullOrEmpty(collectionType) ||
string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true);
return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, true);
}
return null;
@ -187,7 +190,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
/// <param name="directoryService">The directory service.</param>
/// <param name="supportMultiFileItems">if set to <c>true</c> [support multi file items].</param>
/// <returns>Movie.</returns>
private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems)
private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems, bool supportsAlternateVersions)
where T : Video, new()
{
var movies = new List<T>();
@ -218,7 +221,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
};
}
if (EntityResolutionHelper.IsMultiPartFile(filename))
if (EntityResolutionHelper.IsMultiPartFolder(filename))
{
multiDiscFolders.Add(child);
}
@ -248,9 +251,27 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
}
}
if (movies.Count > 1 && supportMultiFileItems)
if (movies.Count > 1)
{
return GetMultiFileMovie(movies);
if (supportMultiFileItems)
{
var result = GetMultiFileMovie(movies);
if (result != null)
{
return result;
}
}
if (supportsAlternateVersions)
{
var result = GetMovieWithAlternateVersions(movies);
if (result != null)
{
return result;
}
}
return null;
}
if (movies.Count == 1)
@ -356,12 +377,47 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
var firstMovie = sortedMovies[0];
// They must all be part of the sequence if we're going to consider it a multi-part movie
// Only support up to 8 (matches Plex), to help avoid incorrect detection
if (sortedMovies.All(i => EntityResolutionHelper.IsMultiPartFile(i.Path)) && sortedMovies.Count <= 8)
if (sortedMovies.All(i => EntityResolutionHelper.IsMultiPartFile(i.Path)))
{
firstMovie.IsMultiPart = true;
// Only support up to 8 (matches Plex), to help avoid incorrect detection
if (sortedMovies.Count <= 8)
{
firstMovie.IsMultiPart = true;
return firstMovie;
_logger.Info("Multi-part video found: " + firstMovie.Path);
return firstMovie;
}
}
return null;
}
private T GetMovieWithAlternateVersions<T>(IEnumerable<T> movies)
where T : Video, new()
{
var sortedMovies = movies.OrderBy(i => i.Path.Length).ToList();
// Cap this at five to help avoid incorrect matching
if (sortedMovies.Count > 5)
{
return null;
}
var firstMovie = sortedMovies[0];
var filenamePrefix = Path.GetFileNameWithoutExtension(firstMovie.Path);
if (!string.IsNullOrWhiteSpace(filenamePrefix))
{
if (sortedMovies.Skip(1).All(i => Path.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase)))
{
firstMovie.HasLocalAlternateVersions = true;
_logger.Info("Multi-version video found: " + firstMovie.Path);
return firstMovie;
}
}
return null;

View File

@ -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<double> progress, CancellationToken cancellationToken)
{
var items = _libraryManager.RootFolder.RecursiveChildren.ToList();
var boxsets = items.OfType<BoxSet>().ToList();
var numComplete = 0;
foreach (var boxset in boxsets)
{
foreach (var child in boxset.GetLinkedChildren().OfType<ISupportsBoxSetGrouping>())
{
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);
}
}
}

View File

@ -165,6 +165,7 @@
<Compile Include="Library\UserManager.cs" />
<Compile Include="Library\Validators\ArtistsPostScanTask.cs" />
<Compile Include="Library\Validators\ArtistsValidator.cs" />
<Compile Include="Library\Validators\BoxSetPostScanTask.cs" />
<Compile Include="Library\Validators\CountHelpers.cs" />
<Compile Include="Library\Validators\GameGenresPostScanTask.cs" />
<Compile Include="Library\Validators\GameGenresValidator.cs" />

View File

@ -622,42 +622,27 @@ namespace MediaBrowser.Server.Implementations.Session
return session;
}
/// <summary>
/// Sends the system command.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken)
public Task SendSystemCommand(Guid controllingSessionId, Guid sessionId, SystemCommand command, CancellationToken cancellationToken)
{
var session = GetSessionForRemoteControl(sessionId);
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
return session.SessionController.SendSystemCommand(command, cancellationToken);
}
/// <summary>
/// Sends the message command.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken)
public Task SendMessageCommand(Guid controllingSessionId, Guid sessionId, MessageCommand command, CancellationToken cancellationToken)
{
var session = GetSessionForRemoteControl(sessionId);
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
return session.SessionController.SendMessageCommand(command, cancellationToken);
}
/// <summary>
/// Sends the play command.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken)
public Task SendPlayCommand(Guid controllingSessionId, Guid sessionId, PlayRequest command, CancellationToken cancellationToken)
{
var session = GetSessionForRemoteControl(sessionId);
@ -690,31 +675,27 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
if (controllingSession.UserId.HasValue)
{
command.ControllingUserId = controllingSession.UserId.Value.ToString("N");
}
return session.SessionController.SendPlayCommand(command, cancellationToken);
}
/// <summary>
/// Sends the browse command.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken)
public Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken)
{
var session = GetSessionForRemoteControl(sessionId);
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
return session.SessionController.SendBrowseCommand(command, cancellationToken);
}
/// <summary>
/// Sends the playstate command.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken)
public Task SendPlaystateCommand(Guid controllingSessionId, Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken)
{
var session = GetSessionForRemoteControl(sessionId);
@ -723,9 +704,28 @@ namespace MediaBrowser.Server.Implementations.Session
throw new ArgumentException(string.Format("Session {0} is unable to seek.", session.Id));
}
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
if (controllingSession.UserId.HasValue)
{
command.ControllingUserId = controllingSession.UserId.Value.ToString("N");
}
return session.SessionController.SendPlaystateCommand(command, cancellationToken);
}
private void AssertCanControl(SessionInfo session, SessionInfo controllingSession)
{
if (session == null)
{
throw new ArgumentNullException("session");
}
if (controllingSession == null)
{
throw new ArgumentNullException("controllingSession");
}
}
/// <summary>
/// Sends the restart required message.
/// </summary>

View File

@ -492,7 +492,7 @@ namespace MediaBrowser.ServerApplication
var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
RegisterSingleInstance<IAppThemeManager>(appThemeManager);
var dlnaManager = new DlnaManager();
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager);
RegisterSingleInstance<IDlnaManager>(dlnaManager);
var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);
@ -861,7 +861,7 @@ namespace MediaBrowser.ServerApplication
ItemsByNamePath = ApplicationPaths.ItemsByNamePath,
CachePath = ApplicationPaths.CachePath,
MacAddress = GetMacAddress(),
HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber,
HttpServerPortNumber = HttpServerPort,
OperatingSystem = Environment.OSVersion.ToString(),
CanSelfRestart = CanSelfRestart,
CanSelfUpdate = CanSelfUpdate,
@ -874,6 +874,11 @@ namespace MediaBrowser.ServerApplication
};
}
public int HttpServerPort
{
get { return ServerConfigurationManager.Configuration.HttpServerPortNumber; }
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private string GetWanAddress()
{

View File

@ -9,6 +9,10 @@ namespace MediaBrowser.Tests.Resolvers
[TestMethod]
public void TestMultiPartFiles()
{
Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart.mkv"));
Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart - 480p.mkv"));
Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart - 720p.mkv"));
Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"blah blah.mkv"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd1.mkv"));
@ -33,25 +37,25 @@ namespace MediaBrowser.Tests.Resolvers
[TestMethod]
public void TestMultiPartFolders()
{
Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"blah blah"));
Assert.IsFalse(EntityResolutionHelper.IsMultiPartFolder(@"blah blah"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disc1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disk1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - cd1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disc1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disk1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - pt1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - part1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - dvd1"));
// Add a space
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disc 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disk 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - cd 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disc 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disk 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - pt 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - part 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - dvd 1"));
// Not case sensitive
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - Disc1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - Disc1"));
}
}
}

View File

@ -461,9 +461,11 @@ namespace MediaBrowser.WebDashboard.Api
"extensions.js",
"site.js",
"librarybrowser.js",
"librarylist.js",
"editorsidebar.js",
"librarymenu.js",
//"chromecast.js",
"contextmenu.js",
"ratingdialog.js",
"aboutpage.js",
@ -584,7 +586,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 +609,7 @@ namespace MediaBrowser.WebDashboard.Api
{
"site.css",
"chromecast.css",
"contextmenu.css",
"mediaplayer.css",
"librarybrowser.css",
"detailtable.css",
@ -630,7 +633,7 @@ namespace MediaBrowser.WebDashboard.Api
{
await AppendResource(memoryStream, "css/" + file, newLineBytes).ConfigureAwait(false);
}
memoryStream.Position = 0;
return memoryStream;
}

View File

@ -96,6 +96,9 @@
<Content Include="dashboard-ui\css\chromecast.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\contextmenu.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\icons.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -488,6 +491,9 @@
<Content Include="dashboard-ui\scripts\chromecast.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\contextmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\dashboardinfo.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -506,6 +512,9 @@
<Content Include="dashboard-ui\scripts\autoorganizelog.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\librarylist.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\librarymenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -552,7 +561,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\mediaplayer-video.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\movieslatest.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
<version>3.0.340</version>
<version>3.0.341</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.340" />
<dependency id="MediaBrowser.Common" version="3.0.341" />
<dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.4.1" />
<dependency id="sharpcompress" version="0.10.2" />

View File

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

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
<version>3.0.340</version>
<version>3.0.341</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.340" />
<dependency id="MediaBrowser.Common" version="3.0.341" />
</dependencies>
</metadata>
<files>

View File

@ -1,9 +1,9 @@
Media Browser
============
Media Browser Server is a home media server built on top of other popular open source technologies such as **Service Stack**, **jQuery**, **jQuery mobile** and **Lucene .NET**.
Media Browser Server is a home media server built on top of other popular open source technologies such as **Service Stack**, **jQuery**, **jQuery mobile**, and **Mono**.
It features a REST-based api with built-in documention to facilitate client development. We also have full .net and javascript wrappers around the api.
It features a REST-based api with built-in documention to facilitate client development. We also have client libraries for our api to enable rapid development.
We have several client apps released and in production: