diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 3360d9736..f9b760869 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -106,7 +106,10 @@ namespace MediaBrowser.Api.Movies _userDataRepository, _dtoService, Logger, - request, item => item is Movie || (item is Trailer && request.IncludeTrailers), + + // Strip out secondary versions + request, item => (item is Movie || (item is Trailer && request.IncludeTrailers)) && !((Video)item).PrimaryVersionId.HasValue, + SimilarItemsHelper.GetSimiliarityScore); return ToOptimizedSerializedResultUsingCache(result); diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index 4e17bc7b5..057506635 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -66,7 +66,10 @@ namespace MediaBrowser.Api.Movies _userDataRepository, _dtoService, Logger, - request, item => item is Movie || item is Trailer, + + // Strip out secondary versions + request, item => (item is Movie || item is Trailer) && !((Video)item).PrimaryVersionId.HasValue, + SimilarItemsHelper.GetSimiliarityScore); return ToOptimizedSerializedResultUsingCache(result); diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 9e58c9f53..a805b7b55 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -532,6 +532,8 @@ namespace MediaBrowser.Api var fields = request.GetItemFields().ToList(); + episodes = _libraryManager.ReplaceVideosWithPrimaryVersions(episodes).Cast(); + var returnItems = episodes.Select(i => _dtoService.GetBaseItemDto(i, fields, user)) .ToArray(); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index f70a21eaf..a787684bb 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -392,12 +392,16 @@ namespace MediaBrowser.Api.UserLibrary items = user == null ? ((Folder)item).RecursiveChildren : ((Folder)item).GetRecursiveChildren(user); + + items = _libraryManager.ReplaceVideosWithPrimaryVersions(items); } else { items = user == null ? ((Folder)item).Children : ((Folder)item).GetChildren(user, true); + + items = _libraryManager.ReplaceVideosWithPrimaryVersions(items); } if (request.IncludeIndexContainers) diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index ba3b0912b..476ea405c 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -1,13 +1,18 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using ServiceStack; using System; +using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Api { @@ -26,28 +31,10 @@ namespace MediaBrowser.Api public string Id { get; set; } } - [Route("/Videos/{Id}/AlternateVersions", "GET")] - [Api(Description = "Gets alternate versions of a video.")] - public class GetAlternateVersions : IReturn + [Route("/Videos/{Id}/Versions", "GET")] + [Api(Description = "Gets all versions of a video.")] + public class GetMediaVersions : IReturn> { - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid? UserId { get; set; } - - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Videos/{Id}/AlternateVersions", "POST")] - [Api(Description = "Assigns videos as alternates of antoher.")] - public class PostAlternateVersions : IReturnVoid - { - [ApiMember(Name = "AlternateVersionIds", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string AlternateVersionIds { get; set; } - /// /// Gets or sets the id. /// @@ -60,18 +47,16 @@ namespace MediaBrowser.Api [Api(Description = "Assigns videos as alternates of antoher.")] public class DeleteAlternateVersions : IReturnVoid { - [ApiMember(Name = "AlternateVersionIds", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string AlternateVersionIds { get; set; } - - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] 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; } + [Route("/Videos/MergeVersions", "POST")] + [Api(Description = "Merges videos into a single record")] + public class MergeVersions : IReturnVoid + { + [ApiMember(Name = "Ids", Description = "Item id list. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] + public string Ids { get; set; } } public class VideosService : BaseApiService @@ -79,12 +64,18 @@ namespace MediaBrowser.Api private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; + private readonly IFileSystem _fileSystem; + private readonly IItemRepository _itemRepo; + private readonly IServerConfigurationManager _config; - public VideosService(ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService) + public VideosService(ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService, IItemRepository itemRepo, IFileSystem fileSystem, IServerConfigurationManager config) { _libraryManager = libraryManager; _userManager = userManager; _dtoService = dtoService; + _itemRepo = itemRepo; + _fileSystem = fileSystem; + _config = config; } /// @@ -122,41 +113,163 @@ namespace MediaBrowser.Api return ToOptimizedSerializedResultUsingCache(result); } - public object Get(GetAlternateVersions request) + public object Get(GetMediaVersions 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 item = _libraryManager.GetItemById(new Guid(request.Id)); var video = (Video)item; - var items = video.GetAlternateVersions() - .Select(i => _dtoService.GetBaseItemDto(i, fields, user, video)) - .ToArray(); + var result = video.GetAlternateVersions().Select(GetVersionInfo).ToList(); - var result = new ItemsResult + result.Add(GetVersionInfo(video)); + + result = result.OrderBy(i => { - Items = items, - TotalRecordCount = items.Length - }; + if (video.VideoType == VideoType.VideoFile) + { + return 0; + } + + return 1; + + }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) + .ThenByDescending(i => + { + var stream = i.MediaStreams.FirstOrDefault(m => m.Type == MediaStreamType.Video); + + return stream == null || stream.Width == null ? 0 : stream.Width.Value; + }) + .ToList(); return ToOptimizedSerializedResultUsingCache(result); } - public void Post(PostAlternateVersions request) + private MediaVersionInfo GetVersionInfo(Video i) { - var task = AddAlternateVersions(request); + return new MediaVersionInfo + { + Chapters = _itemRepo.GetChapters(i.Id).Select(c => _dtoService.GetChapterInfoDto(c, i)).ToList(), - Task.WaitAll(task); + Id = i.Id.ToString("N"), + IsoType = i.IsoType, + LocationType = i.LocationType, + MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery {ItemId = i.Id}).ToList(), + Name = GetAlternateVersionName(i), + Path = GetMappedPath(i), + RunTimeTicks = i.RunTimeTicks, + Video3DFormat = i.Video3DFormat, + VideoType = i.VideoType, + IsHD = i.IsHD + }; + } + + private string GetMappedPath(Video video) + { + var path = video.Path; + + var locationType = video.LocationType; + + if (locationType != LocationType.FileSystem && locationType != LocationType.Offline) + { + return path; + } + + foreach (var map in _config.Configuration.PathSubstitutions) + { + path = _fileSystem.SubstitutePath(path, map.From, map.To); + } + + return path; + } + + private string GetAlternateVersionName(Video video) + { + var name = ""; + + var stream = video.GetDefaultVideoStream(); + + if (video.Video3DFormat.HasValue) + { + name = "3D " + name; + name = name.Trim(); + } + + if (video.VideoType == VideoType.BluRay) + { + name = name + " " + "Bluray"; + name = name.Trim(); + } + else if (video.VideoType == VideoType.Dvd) + { + name = name + " " + "DVD"; + name = name.Trim(); + } + else if (video.VideoType == VideoType.HdDvd) + { + name = name + " " + "HD-DVD"; + name = name.Trim(); + } + else if (video.VideoType == VideoType.Iso) + { + if (video.IsoType.HasValue) + { + if (video.IsoType.Value == IsoType.BluRay) + { + name = name + " " + "Bluray"; + } + else if (video.IsoType.Value == IsoType.Dvd) + { + name = name + " " + "DVD"; + } + } + else + { + name = name + " " + "ISO"; + } + name = name.Trim(); + } + else if (video.VideoType == VideoType.VideoFile) + { + if (stream != null) + { + if (stream.Width.HasValue) + { + if (stream.Width.Value >= 1900) + { + name = name + " " + "1080P"; + name = name.Trim(); + } + else if (stream.Width.Value >= 1270) + { + name = name + " " + "720P"; + name = name.Trim(); + } + else if (stream.Width.Value >= 700) + { + name = name + " " + "480p"; + name = name.Trim(); + } + else + { + name = name + " " + "SD"; + name = name.Trim(); + } + } + } + } + + if (stream != null && !string.IsNullOrWhiteSpace(stream.Codec)) + { + name = name + " " + stream.Codec.ToUpper(); + name = name.Trim(); + } + + if (string.IsNullOrWhiteSpace(name)) + { + return video.Name; + } + + return name; } public void Delete(DeleteAlternateVersions request) @@ -166,46 +279,83 @@ namespace MediaBrowser.Api Task.WaitAll(task); } - private async Task AddAlternateVersions(PostAlternateVersions request) - { - var video = (Video)_dtoService.GetItemByDtoId(request.Id); - - var list = new List(); - var currentAlternateVersions = video.GetAlternateVersions().ToList(); - - foreach (var itemId in request.AlternateVersionIds.Split(',').Select(i => new Guid(i))) - { - var item = _libraryManager.GetItemById(itemId) as Video; - - if (item == null) - { - throw new ArgumentException("No item exists with the supplied Id"); - } - - if (currentAlternateVersions.Any(i => i.Id == itemId)) - { - throw new ArgumentException("Item already exists."); - } - - list.Add(new LinkedChild - { - Path = item.Path, - Type = LinkedChildType.Manual - }); - - item.PrimaryVersionId = video.Id; - } - - video.LinkedAlternateVersions.AddRange(list); - - await video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - - await video.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); - } - private async Task RemoveAlternateVersions(DeleteAlternateVersions request) { var video = (Video)_dtoService.GetItemByDtoId(request.Id); + + foreach (var link in video.GetLinkedAlternateVersions()) + { + link.PrimaryVersionId = null; + + await link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + + video.LinkedAlternateVersions.Clear(); + await video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + + public void Post(MergeVersions request) + { + var task = MergeVersions(request); + + Task.WaitAll(task); + } + + private async Task MergeVersions(MergeVersions request) + { + var items = request.Ids.Split(',') + .Select(i => new Guid(i)) + .Select(i => _libraryManager.GetItemById(i)) + .ToList(); + + if (items.Count < 2) + { + throw new ArgumentException("Please supply at least two videos to merge."); + } + + if (items.Any(i => !(i is Video))) + { + throw new ArgumentException("Only videos can be grouped together."); + } + + var videos = items.Cast /// IEnumerable{BaseItem}. - public IEnumerable GetAlternateVersions() + public IEnumerable