#712 - Support grouping multiple versions of a movie

This commit is contained in:
Luke Pulverenti 2014-03-16 00:23:58 -04:00
parent 4e6d306d00
commit b36aea4ff7
12 changed files with 216 additions and 38 deletions

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.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
@ -38,13 +41,43 @@ namespace MediaBrowser.Api
public string Id { get; set; } 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; }
}
public class VideosService : BaseApiService public class VideosService : BaseApiService
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
public VideosService( ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService) public VideosService(ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userManager = userManager; _userManager = userManager;
@ -115,5 +148,61 @@ namespace MediaBrowser.Api
return ToOptimizedSerializedResultUsingCache(result); 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

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -20,26 +21,27 @@ namespace MediaBrowser.Controller.Entities
{ {
public bool IsMultiPart { get; set; } public bool IsMultiPart { get; set; }
public bool HasLocalAlternateVersions { get; set; } public bool HasLocalAlternateVersions { get; set; }
public Guid? PrimaryVersionId { get; set; }
public List<Guid> AdditionalPartIds { get; set; } public List<Guid> AdditionalPartIds { get; set; }
public List<Guid> AlternateVersionIds { get; set; } public List<Guid> LocalAlternateVersionIds { get; set; }
public Video() public Video()
{ {
PlayableStreamFileNames = new List<string>(); PlayableStreamFileNames = new List<string>();
AdditionalPartIds = new List<Guid>(); AdditionalPartIds = new List<Guid>();
AlternateVersionIds = new List<Guid>(); LocalAlternateVersionIds = new List<Guid>();
Tags = new List<string>(); Tags = new List<string>();
SubtitleFiles = new List<string>(); SubtitleFiles = new List<string>();
LinkedAlternateVersions = new List<LinkedChild>(); LinkedAlternateVersions = new List<LinkedChild>();
} }
[IgnoreDataMember] [IgnoreDataMember]
public bool HasAlternateVersions public int AlternateVersionCount
{ {
get get
{ {
return HasLocalAlternateVersions || LinkedAlternateVersions.Count > 0; return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count;
} }
} }
@ -51,7 +53,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IEnumerable{BaseItem}.</returns> /// <returns>IEnumerable{BaseItem}.</returns>
public IEnumerable<BaseItem> GetAlternateVersions() public IEnumerable<BaseItem> GetAlternateVersions()
{ {
var filesWithinSameDirectory = AlternateVersionIds var filesWithinSameDirectory = LocalAlternateVersionIds
.Select(i => LibraryManager.GetItemById(i)) .Select(i => LibraryManager.GetItemById(i))
.Where(i => i != null) .Where(i => i != null)
.OfType<Video>(); .OfType<Video>();
@ -233,14 +235,11 @@ namespace MediaBrowser.Controller.Entities
{ {
RefreshLinkedAlternateVersions(); RefreshLinkedAlternateVersions();
if (HasLocalAlternateVersions) var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
{
var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
if (additionalPartsChanged) if (additionalPartsChanged)
{ {
hasChanges = true; hasChanges = true;
}
} }
} }
} }
@ -339,21 +338,72 @@ namespace MediaBrowser.Controller.Entities
private async Task<bool> RefreshAlternateVersionsWithinSameDirectory(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) private async Task<bool> RefreshAlternateVersionsWithinSameDirectory(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{ {
var newItems = LoadAlternateVersionsWithinSameDirectory(fileSystemChildren, options.DirectoryService).ToList(); var newItems = HasLocalAlternateVersions ?
LoadAlternateVersionsWithinSameDirectory(fileSystemChildren, options.DirectoryService).ToList() :
new List<Video>();
var newItemIds = newItems.Select(i => i.Id).ToList(); var newItemIds = newItems.Select(i => i.Id).ToList();
var itemsChanged = !AlternateVersionIds.SequenceEqual(newItemIds); var itemsChanged = !LocalAlternateVersionIds.SequenceEqual(newItemIds);
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken)); var tasks = newItems.Select(i => RefreshAlternateVersion(options, i, cancellationToken));
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
AlternateVersionIds = newItemIds; LocalAlternateVersionIds = newItemIds;
return itemsChanged; 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> /// <summary>
/// Loads the additional parts. /// Loads the additional parts.
/// </summary> /// </summary>
@ -395,7 +445,7 @@ namespace MediaBrowser.Controller.Entities
video = dbItem; video = dbItem;
} }
video.ImageInfos = ImageInfos; video.PrimaryVersionId = Id;
return video; return video;

View File

@ -28,6 +28,7 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDlnaManager _dlnaManager; private readonly IDlnaManager _dlnaManager;
private readonly IUserManager _userManager;
private bool _playbackStarted = false; private bool _playbackStarted = false;
public bool SupportsMediaRemoteControl public bool SupportsMediaRemoteControl
@ -46,7 +47,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)
{ {
_session = session; _session = session;
_itemRepository = itemRepository; _itemRepository = itemRepository;
@ -54,6 +55,7 @@ namespace MediaBrowser.Dlna.PlayTo
_libraryManager = libraryManager; _libraryManager = libraryManager;
_networkManager = networkManager; _networkManager = networkManager;
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
_userManager = userManager;
_logger = logger; _logger = logger;
} }
@ -194,7 +196,7 @@ namespace MediaBrowser.Dlna.PlayTo
#region SendCommands #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); _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
@ -227,16 +229,25 @@ namespace MediaBrowser.Dlna.PlayTo
if (command.PlayCommand == PlayCommand.PlayLast) if (command.PlayCommand == PlayCommand.PlayLast)
{ {
AddItemsToPlaylist(playlist); AddItemsToPlaylist(playlist);
return Task.FromResult(true);
} }
if (command.PlayCommand == PlayCommand.PlayNext) if (command.PlayCommand == PlayCommand.PlayNext)
{ {
AddItemsToPlaylist(playlist); AddItemsToPlaylist(playlist);
return Task.FromResult(true);
} }
_logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count); _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) public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)

View File

@ -227,7 +227,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (controller == null) 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);
} }
controller.Init(device); controller.Init(device);

View File

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

View File

@ -23,6 +23,12 @@ namespace MediaBrowser.Model.Session
/// </summary> /// </summary>
/// <value>The play command.</value> /// <value>The play command.</value>
public PlayCommand PlayCommand { get; set; } 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> /// <summary>

View File

@ -37,5 +37,11 @@ namespace MediaBrowser.Model.Session
public PlaystateCommand Command { get; set; } public PlaystateCommand Command { get; set; }
public long? SeekPositionTicks { 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) public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, IDirectoryService directoryService)
{ {
var files = paths.SelectMany(directoryService.GetFiles) var files = paths.SelectMany(directoryService.GetFiles)
.Where(i => .Where(i =>
{ {
var ext = i.Extension; var ext = i.Extension;
return !string.IsNullOrEmpty(ext) && return !string.IsNullOrEmpty(ext) &&
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
}) })
.Cast<FileSystemInfo>()
.ToList(); .ToList();
var list = new List<LocalImageInfo>(); var list = new List<LocalImageInfo>();

View File

@ -111,6 +111,7 @@ namespace MediaBrowser.Server.Implementations.Collections
} }
var list = new List<LinkedChild>(); var list = new List<LinkedChild>();
var currentLinkedChildren = collection.GetLinkedChildren().ToList();
foreach (var itemId in ids) foreach (var itemId in ids)
{ {
@ -121,7 +122,7 @@ namespace MediaBrowser.Server.Implementations.Collections
throw new ArgumentException("No item exists with the supplied Id"); 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"); throw new ArgumentException("Item already exists in collection");
} }

View File

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

View File

@ -410,7 +410,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrWhiteSpace(filenamePrefix)) if (!string.IsNullOrWhiteSpace(filenamePrefix))
{ {
if (sortedMovies.All(i => Path.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix, StringComparison.OrdinalIgnoreCase))) if (sortedMovies.Skip(1).All(i => Path.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase)))
{ {
firstMovie.HasLocalAlternateVersions = true; firstMovie.HasLocalAlternateVersions = true;

View File

@ -690,6 +690,11 @@ namespace MediaBrowser.Server.Implementations.Session
} }
} }
if (session.UserId.HasValue)
{
command.ControllingUserId = session.UserId.Value.ToString("N");
}
return session.SessionController.SendPlayCommand(command, cancellationToken); return session.SessionController.SendPlayCommand(command, cancellationToken);
} }
@ -723,6 +728,11 @@ namespace MediaBrowser.Server.Implementations.Session
throw new ArgumentException(string.Format("Session {0} is unable to seek.", session.Id)); throw new ArgumentException(string.Format("Session {0} is unable to seek.", session.Id));
} }
if (session.UserId.HasValue)
{
command.ControllingUserId = session.UserId.Value.ToString("N");
}
return session.SessionController.SendPlaystateCommand(command, cancellationToken); return session.SessionController.SendPlaystateCommand(command, cancellationToken);
} }