#712 - Support grouping multiple versions of a movie
This commit is contained in:
parent
4e6d306d00
commit
b36aea4ff7
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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>();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user