#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.Library;
using MediaBrowser.Model.Querying;
@ -37,14 +40,44 @@ 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", "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
{
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;
@ -115,5 +148,61 @@ namespace MediaBrowser.Api
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.Resolvers;
using MediaBrowser.Model.Entities;
@ -20,26 +21,27 @@ namespace MediaBrowser.Controller.Entities
{
public bool IsMultiPart { get; set; }
public bool HasLocalAlternateVersions { get; set; }
public Guid? PrimaryVersionId { get; set; }
public List<Guid> AdditionalPartIds { get; set; }
public List<Guid> AlternateVersionIds { get; set; }
public List<Guid> LocalAlternateVersionIds { get; set; }
public Video()
{
PlayableStreamFileNames = new List<string>();
AdditionalPartIds = new List<Guid>();
AlternateVersionIds = new List<Guid>();
LocalAlternateVersionIds = new List<Guid>();
Tags = new List<string>();
SubtitleFiles = new List<string>();
LinkedAlternateVersions = new List<LinkedChild>();
}
[IgnoreDataMember]
public bool HasAlternateVersions
public int AlternateVersionCount
{
get
{
return HasLocalAlternateVersions || LinkedAlternateVersions.Count > 0;
return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count;
}
}
@ -51,7 +53,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IEnumerable{BaseItem}.</returns>
public IEnumerable<BaseItem> GetAlternateVersions()
{
var filesWithinSameDirectory = AlternateVersionIds
var filesWithinSameDirectory = LocalAlternateVersionIds
.Select(i => LibraryManager.GetItemById(i))
.Where(i => i != null)
.OfType<Video>();
@ -233,14 +235,11 @@ namespace MediaBrowser.Controller.Entities
{
RefreshLinkedAlternateVersions();
if (HasLocalAlternateVersions)
{
var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
if (additionalPartsChanged)
{
hasChanges = true;
}
if (additionalPartsChanged)
{
hasChanges = true;
}
}
}
@ -339,21 +338,72 @@ namespace MediaBrowser.Controller.Entities
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 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);
AlternateVersionIds = newItemIds;
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>
@ -395,7 +445,7 @@ namespace MediaBrowser.Controller.Entities
video = dbItem;
}
video.ImageInfos = ImageInfos;
video.PrimaryVersionId = Id;
return video;

View File

@ -28,6 +28,7 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly INetworkManager _networkManager;
private readonly ILogger _logger;
private readonly IDlnaManager _dlnaManager;
private readonly IUserManager _userManager;
private bool _playbackStarted = false;
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;
_itemRepository = itemRepository;
@ -54,6 +55,7 @@ namespace MediaBrowser.Dlna.PlayTo
_libraryManager = libraryManager;
_networkManager = networkManager;
_dlnaManager = dlnaManager;
_userManager = userManager;
_logger = logger;
}
@ -194,7 +196,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 +229,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)

View File

@ -227,7 +227,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);
}
controller.Init(device);

View File

@ -494,7 +494,8 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The part count.</value>
public int? PartCount { get; set; }
public bool? HasAlternateVersions { 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

@ -111,6 +111,7 @@ namespace MediaBrowser.Server.Implementations.Collections
}
var list = new List<LinkedChild>();
var currentLinkedChildren = collection.GetLinkedChildren().ToList();
foreach (var itemId in ids)
{
@ -121,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");
}

View File

@ -1082,7 +1082,12 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.IsHD = video.IsHD;
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))
{

View File

@ -410,7 +410,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
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;

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);
}
@ -723,6 +728,11 @@ namespace MediaBrowser.Server.Implementations.Session
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);
}