diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 66ca8c25d4..a677bc6004 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -488,13 +488,17 @@ namespace MediaBrowser.Api { try { - Logger.Info("Killing ffmpeg process for {0}", job.Path); + Logger.Info("Stopping ffmpeg process with q command for {0}", job.Path); //process.Kill(); process.StandardInput.WriteLine("q"); // Need to wait because killing is asynchronous - process.WaitForExit(5000); + if (!process.WaitForExit(5000)) + { + Logger.Info("Killing ffmpeg process for {0}", job.Path); + process.Kill(); + } } catch (Exception ex) { diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 30750b3741..44d459a01e 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Api } } } - + /// /// To the optimized serialized result using cache. /// @@ -118,9 +118,6 @@ namespace MediaBrowser.Api return ResultFactory.GetStaticFileResult(Request, path); } - private readonly char[] _dashReplaceChars = { '?', '/', '&' }; - private const char SlugChar = '-'; - protected DtoOptions GetDtoOptions(object request) { var options = new DtoOptions(); @@ -154,152 +151,122 @@ namespace MediaBrowser.Api protected MusicArtist GetArtist(string name, ILibraryManager libraryManager) { - return libraryManager.GetArtist(DeSlugArtistName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(MusicArtist).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetArtist(name); } protected Studio GetStudio(string name, ILibraryManager libraryManager) { - return libraryManager.GetStudio(DeSlugStudioName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(Studio).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetStudio(name); } protected Genre GetGenre(string name, ILibraryManager libraryManager) { - return libraryManager.GetGenre(DeSlugGenreName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(Genre).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetGenre(name); } protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager) { - return libraryManager.GetMusicGenre(DeSlugGenreName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(MusicGenre).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetMusicGenre(name); } protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager) { - return libraryManager.GetGameGenre(DeSlugGameGenreName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(GameGenre).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetGameGenre(name); } protected Person GetPerson(string name, ILibraryManager libraryManager) { - return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager)); - } - - /// - /// Deslugs an artist name by finding the correct entry in the library - /// - /// - /// - /// - protected string DeSlugArtistName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) + if (name.IndexOf(BaseItem.SlugChar) != -1) { - return name; + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(Person).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } } - var items = libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name } - }); - - return items - .OfType() - .SelectMany(i => i.AllArtists) - .DistinctNames() - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; - } - - /// - /// Deslugs a genre name by finding the correct entry in the library - /// - protected string DeSlugGenreName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) - { - return name; - } - - return libraryManager.RootFolder.GetRecursiveChildren() - .SelectMany(i => i.Genres) - .DistinctNames() - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; - } - - protected string DeSlugGameGenreName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) - { - return name; - } - - var items = libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Game).Name } - }); - - return items - .SelectMany(i => i.Genres) - .DistinctNames() - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; - } - - /// - /// Deslugs a studio name by finding the correct entry in the library - /// - protected string DeSlugStudioName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) - { - return name; - } - - return libraryManager.RootFolder - .GetRecursiveChildren() - .SelectMany(i => i.Studios) - .DistinctNames() - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; - } - - /// - /// Deslugs a person name by finding the correct entry in the library - /// - protected string DeSlugPersonName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) - { - return name; - } - - return libraryManager.GetPeopleNames(new InternalPeopleQuery()) - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; + return libraryManager.GetPerson(name); } protected string GetPathValue(int index) diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs index c900e4d069..e991565ad2 100644 --- a/MediaBrowser.Api/BrandingService.cs +++ b/MediaBrowser.Api/BrandingService.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Api } [Route("/Branding/Css", "GET", Summary = "Gets custom css")] + [Route("/Branding/Css.css", "GET", Summary = "Gets custom css")] public class GetBrandingCss { } diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index f378747749..5bb4ed5f00 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -247,6 +247,12 @@ namespace MediaBrowser.Api hasBudget.Revenue = request.Revenue; } + var hasOriginalTitle = item as IHasOriginalTitle; + if (hasOriginalTitle != null) + { + hasOriginalTitle.OriginalTitle = hasOriginalTitle.OriginalTitle; + } + var hasCriticRating = item as IHasCriticRating; if (hasCriticRating != null) { diff --git a/MediaBrowser.Api/Library/FileOrganizationService.cs b/MediaBrowser.Api/Library/FileOrganizationService.cs index 849e9cf0db..0ed08a8607 100644 --- a/MediaBrowser.Api/Library/FileOrganizationService.cs +++ b/MediaBrowser.Api/Library/FileOrganizationService.cs @@ -119,8 +119,6 @@ namespace MediaBrowser.Api.Library { private readonly IFileOrganizationService _iFileOrganizationService; - /// The _json serializer - /// private readonly IJsonSerializer _jsonSerializer; public FileOrganizationService(IFileOrganizationService iFileOrganizationService, IJsonSerializer jsonSerializer) diff --git a/MediaBrowser.Api/Library/LibraryHelpers.cs b/MediaBrowser.Api/Library/LibraryHelpers.cs deleted file mode 100644 index 46ec4f2701..0000000000 --- a/MediaBrowser.Api/Library/LibraryHelpers.cs +++ /dev/null @@ -1,89 +0,0 @@ -using MediaBrowser.Controller; -using System; -using System.IO; -using System.Linq; -using CommonIO; - -namespace MediaBrowser.Api.Library -{ - /// - /// Class LibraryHelpers - /// - public static class LibraryHelpers - { - /// - /// The shortcut file extension - /// - private const string ShortcutFileExtension = ".mblink"; - /// - /// The shortcut file search - /// - private const string ShortcutFileSearch = "*" + ShortcutFileExtension; - - /// - /// Deletes a shortcut from within a virtual folder, within either the default view or a user view - /// - /// The file system. - /// Name of the virtual folder. - /// The media path. - /// The app paths. - /// The media folder does not exist - public static void RemoveMediaPath(IFileSystem fileSystem, string virtualFolderName, string mediaPath, IServerApplicationPaths appPaths) - { - if (string.IsNullOrWhiteSpace(mediaPath)) - { - throw new ArgumentNullException("mediaPath"); - } - - var rootFolderPath = appPaths.DefaultUserViewsPath; - var path = Path.Combine(rootFolderPath, virtualFolderName); - - if (!fileSystem.DirectoryExists(path)) - { - throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName)); - } - - var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrEmpty(shortcut)) - { - fileSystem.DeleteFile(shortcut); - } - } - - /// - /// Adds an additional mediaPath to an existing virtual folder, within either the default view or a user view - /// - /// The file system. - /// Name of the virtual folder. - /// The path. - /// The app paths. - public static void AddMediaPath(IFileSystem fileSystem, string virtualFolderName, string path, IServerApplicationPaths appPaths) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - if (!fileSystem.DirectoryExists(path)) - { - throw new DirectoryNotFoundException("The path does not exist."); - } - - var rootFolderPath = appPaths.DefaultUserViewsPath; - var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); - - var shortcutFilename = fileSystem.GetFileNameWithoutExtension(path); - - var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); - - while (fileSystem.FileExists(lnk)) - { - shortcutFilename += "1"; - lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); - } - - fileSystem.CreateShortcut(lnk, path); - } - } -} diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 244dcf09f3..3cf0d5d937 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -190,75 +190,7 @@ namespace MediaBrowser.Api.Library /// The request. public void Post(AddVirtualFolder request) { - if (string.IsNullOrWhiteSpace(request.Name)) - { - throw new ArgumentNullException("request"); - } - - var name = _fileSystem.GetValidFilename(request.Name); - - var rootFolderPath = _appPaths.DefaultUserViewsPath; - - var virtualFolderPath = Path.Combine(rootFolderPath, name); - while (_fileSystem.DirectoryExists(virtualFolderPath)) - { - name += "1"; - virtualFolderPath = Path.Combine(rootFolderPath, name); - } - - if (request.Paths != null) - { - var invalidpath = request.Paths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i)); - if (invalidpath != null) - { - throw new ArgumentException("The specified path does not exist: " + invalidpath + "."); - } - } - - _libraryMonitor.Stop(); - - try - { - _fileSystem.CreateDirectory(virtualFolderPath); - - if (!string.IsNullOrEmpty(request.CollectionType)) - { - var path = Path.Combine(virtualFolderPath, request.CollectionType + ".collection"); - - using (File.Create(path)) - { - - } - } - - if (request.Paths != null) - { - foreach (var path in request.Paths) - { - LibraryHelpers.AddMediaPath(_fileSystem, name, path, _appPaths); - } - } - } - finally - { - Task.Run(() => - { - // No need to start if scanning the library because it will handle it - if (request.RefreshLibrary) - { - _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); - - _libraryMonitor.Start(); - } - }); - } + _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, request.RefreshLibrary); } /// @@ -336,46 +268,7 @@ namespace MediaBrowser.Api.Library /// The request. public void Delete(RemoveVirtualFolder request) { - if (string.IsNullOrWhiteSpace(request.Name)) - { - throw new ArgumentNullException("request"); - } - - var rootFolderPath = _appPaths.DefaultUserViewsPath; - - var path = Path.Combine(rootFolderPath, request.Name); - - if (!_fileSystem.DirectoryExists(path)) - { - throw new DirectoryNotFoundException("The media folder does not exist"); - } - - _libraryMonitor.Stop(); - - try - { - _fileSystem.DeleteDirectory(path, true); - } - finally - { - Task.Run(() => - { - // No need to start if scanning the library because it will handle it - if (request.RefreshLibrary) - { - _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); - - _libraryMonitor.Start(); - } - }); - } + _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary); } /// @@ -393,7 +286,7 @@ namespace MediaBrowser.Api.Library try { - LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, _appPaths); + _libraryManager.AddMediaPath(request.Name, request.Path); } finally { @@ -432,7 +325,7 @@ namespace MediaBrowser.Api.Library try { - LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths); + _libraryManager.RemoveMediaPath(request.Name, request.Path); } finally { diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index ebcf8fbeac..d3a4558c88 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -254,6 +254,8 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableImages { get; set; } + public bool EnableTotalRecordCount { get; set; } + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? ImageTypeLimit { get; set; } @@ -266,12 +268,24 @@ namespace MediaBrowser.Api.LiveTv /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Fields { get; set; } + + public GetPrograms() + { + EnableTotalRecordCount = true; + } } [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")] [Authenticated] public class GetRecommendedPrograms : IReturn>, IHasDtoOptions { + public bool EnableTotalRecordCount { get; set; } + + public GetRecommendedPrograms() + { + EnableTotalRecordCount = true; + } + [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string UserId { get; set; } @@ -662,7 +676,8 @@ namespace MediaBrowser.Api.LiveTv { ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(), UserId = request.UserId, - HasAired = request.HasAired + HasAired = request.HasAired, + EnableTotalRecordCount = request.EnableTotalRecordCount }; if (!string.IsNullOrEmpty(request.MinStartDate)) @@ -709,7 +724,8 @@ namespace MediaBrowser.Api.LiveTv HasAired = request.HasAired, IsMovie = request.IsMovie, IsKids = request.IsKids, - IsSports = request.IsSports + IsSports = request.IsSports, + EnableTotalRecordCount = request.EnableTotalRecordCount }; var result = await _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index db8961a66c..77949d1793 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -129,7 +129,6 @@ - diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 065f882687..786615cc3e 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -144,7 +144,6 @@ namespace MediaBrowser.Api.Movies var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId }; var movies = _libraryManager.GetItemList(query, parentIds); - movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies); var listEligibleForCategories = new List(); var listEligibleForSuggestion = new List(); @@ -197,12 +196,6 @@ namespace MediaBrowser.Api.Movies var parentIds = new string[] { }; var list = _libraryManager.GetItemList(query, parentIds) - .Where(i => - { - // Strip out secondary versions - var v = i as Video; - return v != null && !v.PrimaryVersionId.HasValue; - }) .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) .ToList(); @@ -247,7 +240,7 @@ namespace MediaBrowser.Api.Movies var recentlyPlayedMovies = allMoviesForCategories .Select(i => { - var userdata = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + var userdata = _userDataRepository.GetUserData(user, i); return new Tuple(i, userdata.Played, userdata.LastPlayedDate ?? DateTime.MinValue); }) .Where(i => i.Item2) @@ -260,7 +253,7 @@ namespace MediaBrowser.Api.Movies .Select(i => { var score = 0; - var userData = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + var userData = _userDataRepository.GetUserData(user, i); if (userData.IsFavorite) { diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 901554973b..421ccdb5d0 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -288,9 +288,9 @@ namespace MediaBrowser.Api.Playback { if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { - - return "h264_qsv"; - + + return "h264_qsv"; + } return "libx264"; @@ -460,7 +460,7 @@ namespace MediaBrowser.Api.Playback // Boost volume to 200% when downsampling from 6ch to 2ch if (channels.HasValue && channels.Value <= 2) { - if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) + if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.Equals(1)) { volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture); } @@ -712,15 +712,16 @@ namespace MediaBrowser.Api.Playback inputChannels = null; } + int? resultChannels = null; var codec = outputAudioCodec ?? string.Empty; if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1) { // wmav2 currently only supports two channel output - return Math.Min(2, inputChannels ?? 2); + resultChannels = Math.Min(2, inputChannels ?? 2); } - if (request.MaxAudioChannels.HasValue) + else if (request.MaxAudioChannels.HasValue) { var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1 ? 2 @@ -732,10 +733,18 @@ namespace MediaBrowser.Api.Playback } // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels - return Math.Min(request.MaxAudioChannels.Value, channelLimit); + resultChannels = Math.Min(request.MaxAudioChannels.Value, channelLimit); } - return request.AudioChannels; + if (resultChannels.HasValue && !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + { + if (request.TranscodingMaxAudioChannels.HasValue) + { + resultChannels = Math.Min(request.TranscodingMaxAudioChannels.Value, resultChannels.Value); + } + } + + return resultChannels ?? request.AudioChannels; } /// @@ -821,9 +830,14 @@ namespace MediaBrowser.Api.Playback /// System.String. protected string GetVideoDecoder(StreamState state) { - if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) + return null; + } + + if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) + { + if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { switch (state.MediaSource.VideoStream.Codec.ToLower()) { @@ -831,7 +845,8 @@ namespace MediaBrowser.Api.Playback case "h264": if (MediaEncoder.SupportsDecoder("h264_qsv")) { - return "-c:v h264_qsv "; + // Seeing stalls and failures with decoding. Not worth it compared to encoding. + //return "-c:v h264_qsv "; } break; case "mpeg2video": @@ -947,6 +962,21 @@ namespace MediaBrowser.Api.Playback await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); + if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + var auth = AuthorizationContext.GetAuthorizationInfo(Request); + if (!string.IsNullOrWhiteSpace(auth.UserId)) + { + var user = UserManager.GetUserById(auth.UserId); + if (!user.Policy.EnableVideoPlaybackTranscoding) + { + ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); + + throw new ArgumentException("User does not have access to video transcoding"); + } + } + } + var transcodingId = Guid.NewGuid().ToString("N"); var commandLineArgs = GetCommandLineArguments(outputPath, state, true); @@ -1033,7 +1063,7 @@ namespace MediaBrowser.Api.Playback process.BeginOutputReadLine(); // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream); + Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream)); // Wait for the file to exist before proceeeding while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited) @@ -1076,7 +1106,7 @@ namespace MediaBrowser.Api.Playback return true; } - private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target) + private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target) { try { @@ -1498,6 +1528,10 @@ namespace MediaBrowser.Api.Playback } } } + else if (i == 26) + { + request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture); + } } } @@ -1804,6 +1838,15 @@ namespace MediaBrowser.Api.Playback } } + if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value) + { + Logger.Debug("Cannot stream copy video. Stream is marked as not AVC"); + return false; + } + } + // Source and target codecs must match if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase)) { @@ -2222,9 +2265,10 @@ namespace MediaBrowser.Api.Playback if (state.VideoRequest != null) { + // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps) { - inputModifier += " -noaccurate_seek"; + //inputModifier += " -noaccurate_seek"; } } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index cb344690cd..49f50735f6 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -289,17 +289,5 @@ namespace MediaBrowser.Api.Playback.Hls return isLiveStream; } - - protected override bool CanStreamCopyAudio(StreamState state, List supportedAudioCodecs) - { - var isLiveStream = IsLiveStream(state); - - if (!isLiveStream) - { - return false; - } - - return base.CanStreamCopyAudio(state, supportedAudioCodecs); - } } } \ No newline at end of file diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index bc155ff458..f857a43e44 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -500,18 +500,6 @@ namespace MediaBrowser.Api.Playback.Hls return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); } - private bool IsLiveStream(StreamState state) - { - var isLiveStream = (state.RunTimeTicks ?? 0) == 0; - - if (state.VideoRequest.ForceLiveStream) - { - return true; - } - - return isLiveStream; - } - private string GetMasterPlaylistFileText(StreamState state, int totalBitrate) { var builder = new StringBuilder(); @@ -830,11 +818,10 @@ namespace MediaBrowser.Api.Playback.Hls { if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - Logger.Debug("Enabling h264_mp4toannexb due to nal_length_size of {0}", state.VideoStream.NalLengthSize); args += " -bsf:v h264_mp4toannexb"; } - args += " -flags -global_header -sc_threshold 0"; + args += " -flags -global_header"; } else { @@ -859,7 +846,12 @@ namespace MediaBrowser.Api.Playback.Hls args += GetGraphicalSubtitleParam(state, codec); } - args += " -flags -global_header -sc_threshold 0"; + args += " -flags -global_header"; + } + + if (EnableCopyTs(state) && args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1) + { + args += " -copyts"; } return args; @@ -867,7 +859,8 @@ namespace MediaBrowser.Api.Playback.Hls private bool EnableCopyTs(StreamState state) { - return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; + //return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; + return true; } protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) @@ -889,24 +882,28 @@ namespace MediaBrowser.Api.Playback.Hls var mapArgs = state.IsOutputVideo ? GetMapArgs(state) : string.Empty; - //var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state); + var enableGenericSegmenter = false; - //return string.Format("{0} {11} {1}{10} -map_metadata -1 -threads {2} {3} {4} {5} -f segment -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", - // inputModifier, - // GetInputArgument(state), - // threads, - // mapArgs, - // GetVideoArguments(state), - // GetAudioArguments(state), - // state.SegmentLength.ToString(UsCulture), - // startNumberParam, - // outputPath, - // outputTsArg, - // slowSeekParam, - // toTimeParam - // ).Trim(); + if (enableGenericSegmenter) + { + var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state); - return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"", + return string.Format("{0} {10} {1} -map_metadata -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + inputModifier, + GetInputArgument(state), + threads, + mapArgs, + GetVideoArguments(state), + GetAudioArguments(state), + state.SegmentLength.ToString(UsCulture), + startNumberParam, + outputPath, + outputTsArg, + toTimeParam + ).Trim(); + } + + return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"", inputModifier, GetInputArgument(state), threads, @@ -946,10 +943,10 @@ namespace MediaBrowser.Api.Playback.Hls { var isLiveStream = IsLiveStream(state); - if (!isLiveStream) - { - return false; - } + //if (!isLiveStream && Request.QueryString["AllowCustomSegmenting"] != "true") + //{ + // return false; + //} return base.CanStreamCopyVideo(state); } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 87b1c4248c..8a14948d21 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -89,7 +89,6 @@ namespace MediaBrowser.Api.Playback.Hls // if h264_mp4toannexb is ever added, do not use it for live tv if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - Logger.Debug("Enabling h264_mp4toannexb due to nal_length_size of {0}", state.VideoStream.NalLengthSize); args += " -bsf:v h264_mp4toannexb"; } return args; diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index ffe7c50c8e..2d9cc40c0a 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Api.Playback { @@ -66,14 +67,16 @@ namespace MediaBrowser.Api.Playback private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; + private readonly IMediaEncoder _mediaEncoder; - public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager) + public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder) { _mediaSourceManager = mediaSourceManager; _deviceManager = deviceManager; _libraryManager = libraryManager; _config = config; _networkManager = networkManager; + _mediaEncoder = mediaEncoder; } public object Get(GetBitrateTestBytes request) @@ -241,7 +244,7 @@ namespace MediaBrowser.Api.Playback int? subtitleStreamIndex, string playSessionId) { - var streamBuilder = new StreamBuilder(Logger); + var streamBuilder = new StreamBuilder(_mediaEncoder, Logger); var options = new VideoOptions { diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 3319fbaec7..be3995aeb6 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -141,7 +141,6 @@ namespace MediaBrowser.Api.Playback.Progressive { if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - Logger.Debug("Enabling h264_mp4toannexb due to nal_length_size of {0}", state.VideoStream.NalLengthSize); args += " -bsf:v h264_mp4toannexb"; } diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 371dbbda2a..370915ec38 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -51,7 +51,9 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "MaxAudioChannels", Description = "Optional. Specify a maximum number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MaxAudioChannels { get; set; } - + + public int? TranscodingMaxAudioChannels { get; set; } + /// /// Gets or sets the audio sample rate. /// diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index ed8a27fafe..061afed6d9 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -69,7 +69,19 @@ namespace MediaBrowser.Api.Playback public List PlayableStreamFileNames { get; set; } - public int SegmentLength = 3; + public int SegmentLength + { + get + { + if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + return 10; + } + + return 3; + } + } + public int HlsListSize { get diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index cd95d9f104..e3dfb7f5a0 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -429,7 +429,7 @@ namespace MediaBrowser.Api if (request.IsMissing.HasValue) { var val = request.IsMissing.Value; - items = items.Where(i => i.IsMissingSeason == val); + items = items.Where(i => (i.IsMissingSeason ?? false) == val); } if (request.IsVirtualUnaired.HasValue) @@ -508,8 +508,7 @@ namespace MediaBrowser.Api returnItems = UserViewBuilder.FilterForAdjacency(returnItems, request.AdjacentTo); } - var returnList = _libraryManager.ReplaceVideosWithPrimaryVersions(returnItems) - .ToList(); + var returnList = returnItems.ToList(); var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit); diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 6ae2b08322..b3164ce3f7 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -274,7 +274,7 @@ namespace MediaBrowser.Api.UserLibrary { items = items.Where(i => { - var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + var userdata = UserDataRepository.GetUserData(user, i); return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value; }); @@ -284,7 +284,7 @@ namespace MediaBrowser.Api.UserLibrary { items = items.Where(i => { - var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + var userdata = UserDataRepository.GetUserData(user, i); return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value; }); @@ -294,7 +294,7 @@ namespace MediaBrowser.Api.UserLibrary { items = items.Where(i => { - var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + var userdata = UserDataRepository.GetUserData(user, i); var likes = userdata.Likes ?? false; var favorite = userdata.IsFavorite; @@ -307,7 +307,7 @@ namespace MediaBrowser.Api.UserLibrary { items = items.Where(i => { - var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + var userdata = UserDataRepository.GetUserData(user, i); return userdata != null && userdata.IsFavorite; }); diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 6867f6308c..aee1a8d545 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Api.UserLibrary protected BaseItemsRequest() { EnableImages = true; + EnableTotalRecordCount = true; } /// @@ -104,7 +105,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsInBoxSet { get; set; } - + + public bool EnableTotalRecordCount { get; set; } + /// /// Skips over a given number of items within the results. Use for paging. /// diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 7d2029cabe..ff937078e6 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -230,7 +230,8 @@ namespace MediaBrowser.Api.UserLibrary ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId), ParentIndexNumber = request.ParentIndexNumber, AiredDuringSeason = request.AiredDuringSeason, - AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater + AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater, + EnableTotalRecordCount = request.EnableTotalRecordCount }; if (!string.IsNullOrWhiteSpace(request.Ids)) diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index c2c481cb6f..8cc5cab358 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -519,10 +519,8 @@ namespace MediaBrowser.Api.UserLibrary var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId); - var key = item.GetUserDataKey(); - // Get the user data for this item - var data = _userDataRepository.GetUserData(user.Id, key); + var data = _userDataRepository.GetUserData(user, item); // Set favorite status data.IsFavorite = isFavorite; @@ -567,10 +565,8 @@ namespace MediaBrowser.Api.UserLibrary var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId); - var key = item.GetUserDataKey(); - // Get the user data for this item - var data = _userDataRepository.GetUserData(user.Id, key); + var data = _userDataRepository.GetUserData(user, item); data.Likes = likes; diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index c6ec69c858..c8dbb7bb26 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -130,6 +130,7 @@ namespace MediaBrowser.Api var items = request.Ids.Split(',') .Select(i => new Guid(i)) .Select(i => _libraryManager.GetItemById(i)) + .OfType /// The last result. /// if set to true [is application startup]. - public void Start(TaskResult lastResult, bool isApplicationStartup) + public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { DisposeTimer(); @@ -44,7 +46,11 @@ namespace MediaBrowser.Common.ScheduledTasks var triggerDate = now.TimeOfDay > TimeOfDay ? now.Date.AddDays(1) : now.Date; triggerDate = triggerDate.Add(TimeOfDay); - Timer = new Timer(state => OnTriggered(), null, triggerDate - now, TimeSpan.FromMilliseconds(-1)); + var dueTime = triggerDate - now; + + logger.Info("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + + Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } /// diff --git a/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs b/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs index 8c87f8f380..ef1ea9d387 100644 --- a/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs @@ -1,6 +1,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Tasks; using System; +using MediaBrowser.Model.Logging; namespace MediaBrowser.Common.ScheduledTasks { @@ -19,7 +20,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// The last result. /// if set to true [is application startup]. - void Start(TaskResult lastResult, bool isApplicationStartup); + void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup); /// /// Stops waiting for the trigger action diff --git a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs b/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs index e07dfcceb4..5107db6c4d 100644 --- a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Tasks; using System; using System.Linq; using System.Threading; +using MediaBrowser.Model.Logging; namespace MediaBrowser.Common.ScheduledTasks { @@ -38,7 +39,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// The last result. /// if set to true [is application startup]. - public void Start(TaskResult lastResult, bool isApplicationStartup) + public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { DisposeTimer(); diff --git a/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs b/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs index 1d82dc76a5..41f58a7ad5 100644 --- a/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs @@ -2,6 +2,7 @@ using MediaBrowser.Model.Tasks; using System; using System.Threading.Tasks; +using MediaBrowser.Model.Logging; namespace MediaBrowser.Common.ScheduledTasks { @@ -30,7 +31,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// The last result. /// if set to true [is application startup]. - public async void Start(TaskResult lastResult, bool isApplicationStartup) + public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { if (isApplicationStartup) { diff --git a/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs b/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs index eaf4afc758..9972dc8044 100644 --- a/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Tasks; using Microsoft.Win32; using System; using System.Threading.Tasks; +using MediaBrowser.Model.Logging; namespace MediaBrowser.Common.ScheduledTasks { @@ -30,7 +31,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// The last result. /// if set to true [is application startup]. - public void Start(TaskResult lastResult, bool isApplicationStartup) + public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { switch (SystemEvent) { diff --git a/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs b/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs index 2e38264b24..318802e07d 100644 --- a/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Tasks; namespace MediaBrowser.Common.ScheduledTasks @@ -41,7 +42,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// The last result. /// if set to true [is application startup]. - public void Start(TaskResult lastResult, bool isApplicationStartup) + public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { DisposeTimer(); diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 929308ba08..c34a884ff5 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Entities.Audio IArchivable { public List ChannelMediaSources { get; set; } - + public long? Size { get; set; } public string Container { get; set; } public int? TotalBitrate { get; set; } @@ -40,12 +40,6 @@ namespace MediaBrowser.Controller.Entities.Audio public List AlbumArtists { get; set; } - /// - /// Gets or sets the album. - /// - /// The album. - public string Album { get; set; } - [IgnoreDataMember] public bool IsThemeMedia { @@ -150,12 +144,10 @@ namespace MediaBrowser.Controller.Entities.Audio + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name; } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { + var list = base.GetUserDataKeys(); + if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys) { var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty; @@ -165,7 +157,7 @@ namespace MediaBrowser.Controller.Entities.Audio { songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey; } - songKey+= Name; + songKey += Name; if (!string.IsNullOrWhiteSpace(Album)) { @@ -178,25 +170,25 @@ namespace MediaBrowser.Controller.Entities.Audio songKey = albumArtist + "-" + songKey; } - return songKey; + list.Insert(0, songKey); } - - var parent = AlbumEntity; - - if (parent != null) + else { - var parentKey = parent.GetUserDataKey(); + var parent = AlbumEntity; - if (IndexNumber.HasValue) + if (parent != null && IndexNumber.HasValue) { - var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "") - + IndexNumber.Value.ToString("0000 - "); + list.InsertRange(0, parent.GetUserDataKeys().Select(i => + { + var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "") + + IndexNumber.Value.ToString("0000 - "); - return parentKey + songKey; + return i + songKey; + })); } } - return base.CreateUserDataKey(); + return list; } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index e6178c183b..615276e837 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -48,6 +48,15 @@ namespace MediaBrowser.Controller.Entities.Audio } } + [IgnoreDataMember] + public override bool SupportsCumulativeRunTimeTicks + { + get + { + return true; + } + } + [IgnoreDataMember] public List AllArtists { @@ -96,36 +105,34 @@ namespace MediaBrowser.Controller.Entities.Audio public List Artists { get; set; } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - var id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); - - if (!string.IsNullOrWhiteSpace(id)) - { - return "MusicAlbum-MusicBrainzReleaseGroup-" + id; - } - - id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum); - - if (!string.IsNullOrWhiteSpace(id)) - { - return "MusicAlbum-Musicbrainz-" + id; - } + var list = base.GetUserDataKeys(); if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys) { var albumArtist = AlbumArtist; if (!string.IsNullOrWhiteSpace(albumArtist)) { - return albumArtist + "-" + Name; + list.Insert(0, albumArtist + "-" + Name); } } - return base.CreateUserDataKey(); + var id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum); + + if (!string.IsNullOrWhiteSpace(id)) + { + list.Insert(0, "MusicAlbum-Musicbrainz-" + id); + } + + id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); + + if (!string.IsNullOrWhiteSpace(id)) + { + list.Insert(0, "MusicAlbum-MusicBrainzReleaseGroup-" + id); + } + + return list; } protected override bool GetBlockUnratedValue(UserPolicy config) diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 02bcceada4..fb8a24061a 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -17,7 +17,12 @@ namespace MediaBrowser.Controller.Entities.Audio /// public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasProductionLocations, IHasLookupInfo { - public bool IsAccessedByName { get; set; } + [IgnoreDataMember] + public bool IsAccessedByName + { + get { return ParentId == Guid.Empty; } + } + public List ProductionLocations { get; set; } [IgnoreDataMember] @@ -29,6 +34,15 @@ namespace MediaBrowser.Controller.Entities.Audio } } + [IgnoreDataMember] + public override bool SupportsCumulativeRunTimeTicks + { + get + { + return true; + } + } + [IgnoreDataMember] public override bool SupportsAddingToPlaylist { @@ -40,6 +54,40 @@ namespace MediaBrowser.Controller.Entities.Audio return !IsAccessedByName; } + public IEnumerable GetTaggedItems(InternalItemsQuery query) + { + var itemByNameFilter = GetItemFilter(); + + if (query.User != null) + { + return query.User.RootFolder + .GetRecursiveChildren(query.User, i => + { + if (query.IsFolder.HasValue) + { + if (query.IsFolder.Value != i.IsFolder) + { + return false; + } + } + return itemByNameFilter(i); + }); + } + + return LibraryManager.RootFolder + .GetRecursiveChildren(i => + { + if (query.IsFolder.HasValue) + { + if (query.IsFolder.Value != i.IsFolder) + { + return false; + } + } + return itemByNameFilter(i); + }); + } + protected override IEnumerable ActualChildren { get @@ -80,13 +128,12 @@ namespace MediaBrowser.Controller.Entities.Audio ProductionLocations = new List(); } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - return GetUserDataKey(this); + var list = base.GetUserDataKeys(); + + list.InsertRange(0, GetUserDataKeys(this)); + return list; } /// @@ -121,16 +168,18 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// The item. /// System.String. - private static string GetUserDataKey(MusicArtist item) + private static List GetUserDataKeys(MusicArtist item) { + var list = new List(); var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist); if (!string.IsNullOrEmpty(id)) { - return "Artist-Musicbrainz-" + id; + list.Add("Artist-Musicbrainz-" + id); } - return "Artist-" + item.Name; + list.Add("Artist-" + item.Name); + return list; } protected override bool GetBlockUnratedValue(UserPolicy config) diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 45304d47ee..77cf0cc494 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -10,13 +10,12 @@ namespace MediaBrowser.Controller.Entities.Audio /// public class MusicGenre : BaseItem, IItemByName { - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - return "MusicGenre-" + Name; + var list = base.GetUserDataKeys(); + + list.Insert(0, "MusicGenre-" + Name); + return list; } [IgnoreDataMember] @@ -80,5 +79,13 @@ namespace MediaBrowser.Controller.Entities.Audio return false; } } + + public IEnumerable GetTaggedItems(InternalItemsQuery query) + { + query.Genres = new[] { Name }; + query.IncludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name }; + + return LibraryManager.GetItemList(query); + } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 475e2ace83..61060766f9 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -44,6 +44,9 @@ namespace MediaBrowser.Controller.Entities ImageInfos = new List(); } + public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; + public static char SlugChar = '-'; + /// /// The supported image extensions /// @@ -66,6 +69,12 @@ namespace MediaBrowser.Controller.Entities public List ImageInfos { get; set; } + /// + /// Gets or sets the album. + /// + /// The album. + public string Album { get; set; } + /// /// Gets or sets the channel identifier. /// @@ -125,6 +134,29 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public string SlugName + { + get + { + var name = Name; + if (string.IsNullOrWhiteSpace(name)) + { + return string.Empty; + } + + return SlugReplaceChars.Aggregate(name, (current, c) => current.Replace(c, SlugChar)); + } + } + + [IgnoreDataMember] + public bool IsUnaired + { + get { return PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date; } + } + + public string OriginalTitle { get; set; } + /// /// Gets or sets the id. /// @@ -726,12 +758,14 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the critic rating. /// /// The critic rating. + [IgnoreDataMember] public float? CriticRating { get; set; } /// /// Gets or sets the critic rating summary. /// /// The critic rating summary. + [IgnoreDataMember] public string CriticRatingSummary { get; set; } /// @@ -1147,33 +1181,31 @@ namespace MediaBrowser.Controller.Entities get { return null; } } - private string _userDataKey; - /// - /// Gets the user data key. - /// - /// System.String. - public string GetUserDataKey() + [IgnoreDataMember] + public virtual string PresentationUniqueKey { - if (string.IsNullOrWhiteSpace(_userDataKey)) - { - var key = CreateUserDataKey(); - _userDataKey = key; - return key; - } - - return _userDataKey; + get { return Id.ToString("N"); } } - protected virtual string CreateUserDataKey() + public virtual bool RequiresRefresh() { + return false; + } + + public virtual List GetUserDataKeys() + { + var list = new List(); + if (SourceType == SourceType.Channel) { if (!string.IsNullOrWhiteSpace(ExternalId)) { - return ExternalId; + list.Add(ExternalId); } } - return Id.ToString(); + + list.Add(Id.ToString()); + return list; } internal virtual bool IsValidFromResolver(BaseItem newItem) @@ -1186,7 +1218,6 @@ namespace MediaBrowser.Controller.Entities public void AfterMetadataRefresh() { _sortName = null; - _userDataKey = null; } /// @@ -1540,11 +1571,11 @@ namespace MediaBrowser.Controller.Entities { if (!string.IsNullOrEmpty(info.Path)) { - var itemByPath = LibraryManager.FindByPath(info.Path); + var itemByPath = LibraryManager.FindByPath(info.Path, null); if (itemByPath == null) { - Logger.Warn("Unable to find linked item at path {0}", info.Path); + //Logger.Warn("Unable to find linked item at path {0}", info.Path); } return itemByPath; @@ -1553,6 +1584,15 @@ namespace MediaBrowser.Controller.Entities return null; } + [IgnoreDataMember] + public virtual bool EnableRememberingTrackSelections + { + get + { + return true; + } + } + /// /// Adds a studio to the item /// @@ -1606,9 +1646,7 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException(); } - var key = GetUserDataKey(); - - var data = UserDataManager.GetUserData(user.Id, key); + var data = UserDataManager.GetUserData(user, this); if (datePlayed.HasValue) { @@ -1643,9 +1681,7 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException(); } - var key = GetUserDataKey(); - - var data = UserDataManager.GetUserData(user.Id, key); + var data = UserDataManager.GetUserData(user, this); //I think it is okay to do this here. // if this is only called when a user is manually forcing something to un-played @@ -1976,14 +2012,14 @@ namespace MediaBrowser.Controller.Entities public virtual bool IsPlayed(User user) { - var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey()); + var userdata = UserDataManager.GetUserData(user, this); return userdata != null && userdata.Played; } public bool IsFavoriteOrLiked(User user) { - var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey()); + var userdata = UserDataManager.GetUserData(user, this); return userdata != null && (userdata.IsFavorite || (userdata.Likes ?? false)); } @@ -1995,7 +2031,7 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException("user"); } - var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey()); + var userdata = UserDataManager.GetUserData(user, this); return userdata == null || !userdata.Played; } @@ -2026,7 +2062,6 @@ namespace MediaBrowser.Controller.Entities /// public virtual bool BeforeMetadataRefresh() { - _userDataKey = null; _sortName = null; var hasChanges = false; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 4297003277..5e0cf6e88b 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -8,6 +8,7 @@ using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MoreLinq; namespace MediaBrowser.Controller.Entities { @@ -97,7 +98,6 @@ namespace MediaBrowser.Controller.Entities } } - return base.IsValidFromResolver(newItem); } @@ -200,9 +200,30 @@ namespace MediaBrowser.Controller.Entities public IEnumerable GetPhysicalParents() { - return LibraryManager.RootFolder.Children + var rootChildren = LibraryManager.RootFolder.Children .OfType() - .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase)); + .ToList(); + + return PhysicalLocations.Where(i => !string.Equals(i, Path, StringComparison.OrdinalIgnoreCase)).SelectMany(i => GetPhysicalParents(i, rootChildren)).DistinctBy(i => i.Id); + } + + private IEnumerable GetPhysicalParents(string path, List rootChildren) + { + var result = rootChildren + .Where(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (result.Count == 0) + { + var folder = LibraryManager.FindByPath(path, true) as Folder; + + if (folder != null) + { + result.Add(folder); + } + } + + return result; } [IgnoreDataMember] diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index f4cdc8fa1b..77e3624191 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -28,6 +28,9 @@ namespace MediaBrowser.Controller.Entities public List ThemeSongIds { get; set; } public List ThemeVideoIds { get; set; } + [IgnoreDataMember] + public DateTime? DateLastMediaAdded { get; set; } + public Folder() { LinkedChildren = new List(); @@ -55,6 +58,36 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public virtual bool SupportsCumulativeRunTimeTicks + { + get + { + return false; + } + } + + [IgnoreDataMember] + public virtual bool SupportsDateLastMediaAdded + { + get + { + return false; + } + } + + public override bool RequiresRefresh() + { + var baseResult = base.RequiresRefresh(); + + if (SupportsCumulativeRunTimeTicks && !RunTimeTicks.HasValue) + { + baseResult = true; + } + + return baseResult; + } + [IgnoreDataMember] public override string FileNameWithoutExtension { @@ -199,8 +232,8 @@ namespace MediaBrowser.Controller.Entities /// Dictionary{System.StringFunc{UserIEnumerable{BaseItem}}}. protected virtual IEnumerable GetIndexByOptions() { - return new List { - {"None"}, + return new List { + {"None"}, {"Performer"}, {"Genre"}, {"Director"}, @@ -707,8 +740,8 @@ namespace MediaBrowser.Controller.Entities { return ItemRepository.GetItemIdsList(new InternalItemsQuery { - ParentId = Id - + ParentId = Id, + GroupByPresentationUniqueKey = false }); } @@ -751,39 +784,44 @@ namespace MediaBrowser.Controller.Entities return true; } } + + var supportsUserDataQueries = ConfigurationManager.Configuration.SchemaVersion >= 76; if (query.SortBy != null && query.SortBy.Length > 0) { - if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase)) + if (!supportsUserDataQueries) { - Logger.Debug("Query requires post-filtering due to ItemSortBy.DatePlayed"); - return true; - } - if (query.SortBy.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.IsFavoriteOrLiked"); - return true; - } - if (query.SortBy.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.IsPlayed"); - return true; - } - if (query.SortBy.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.IsUnplayed"); - return true; + if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase)) + { + Logger.Debug("Query requires post-filtering due to ItemSortBy.IsFavoriteOrLiked"); + return true; + } + if (query.SortBy.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase)) + { + Logger.Debug("Query requires post-filtering due to ItemSortBy.PlayCount"); + return true; + } + if (query.SortBy.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)) + { + Logger.Debug("Query requires post-filtering due to ItemSortBy.IsFavoriteOrLiked"); + return true; + } + if (query.SortBy.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase)) + { + Logger.Debug("Query requires post-filtering due to ItemSortBy.IsPlayed"); + return true; + } + if (query.SortBy.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase)) + { + Logger.Debug("Query requires post-filtering due to ItemSortBy.IsUnplayed"); + return true; + } } if (query.SortBy.Contains(ItemSortBy.AiredEpisodeOrder, StringComparer.OrdinalIgnoreCase)) { Logger.Debug("Query requires post-filtering due to ItemSortBy.AiredEpisodeOrder"); return true; } - if (query.SortBy.Contains(ItemSortBy.Album, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.Album"); - return true; - } if (query.SortBy.Contains(ItemSortBy.AlbumArtist, StringComparer.OrdinalIgnoreCase)) { Logger.Debug("Query requires post-filtering due to ItemSortBy.AlbumArtist"); @@ -799,11 +837,6 @@ namespace MediaBrowser.Controller.Entities Logger.Debug("Query requires post-filtering due to ItemSortBy.Budget"); return true; } - if (query.SortBy.Contains(ItemSortBy.DateLastContentAdded, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.DateLastContentAdded"); - return true; - } if (query.SortBy.Contains(ItemSortBy.GameSystem, StringComparer.OrdinalIgnoreCase)) { Logger.Debug("Query requires post-filtering due to ItemSortBy.GameSystem"); @@ -819,11 +852,6 @@ namespace MediaBrowser.Controller.Entities Logger.Debug("Query requires post-filtering due to ItemSortBy.OfficialRating"); return true; } - if (query.SortBy.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.PlayCount"); - return true; - } if (query.SortBy.Contains(ItemSortBy.Players, StringComparer.OrdinalIgnoreCase)) { Logger.Debug("Query requires post-filtering due to ItemSortBy.Players"); @@ -839,11 +867,6 @@ namespace MediaBrowser.Controller.Entities Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName"); return true; } - if (query.SortBy.Contains(ItemSortBy.StartDate, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.StartDate"); - return true; - } if (query.SortBy.Contains(ItemSortBy.Studio, StringComparer.OrdinalIgnoreCase)) { Logger.Debug("Query requires post-filtering due to ItemSortBy.Studio"); @@ -868,34 +891,37 @@ namespace MediaBrowser.Controller.Entities return true; } - if (query.IsLiked.HasValue) + if (!supportsUserDataQueries) { - Logger.Debug("Query requires post-filtering due to IsLiked"); - return true; - } + if (query.IsLiked.HasValue) + { + Logger.Debug("Query requires post-filtering due to IsLiked"); + return true; + } - if (query.IsFavoriteOrLiked.HasValue) - { - Logger.Debug("Query requires post-filtering due to IsFavoriteOrLiked"); - return true; - } + if (query.IsFavoriteOrLiked.HasValue) + { + Logger.Debug("Query requires post-filtering due to IsFavoriteOrLiked"); + return true; + } - if (query.IsFavorite.HasValue) - { - Logger.Debug("Query requires post-filtering due to IsFavorite"); - return true; - } + if (query.IsFavorite.HasValue) + { + Logger.Debug("Query requires post-filtering due to IsFavorite"); + return true; + } - if (query.IsResumable.HasValue) - { - Logger.Debug("Query requires post-filtering due to IsResumable"); - return true; - } + if (query.IsResumable.HasValue) + { + Logger.Debug("Query requires post-filtering due to IsResumable"); + return true; + } - if (query.IsPlayed.HasValue) - { - Logger.Debug("Query requires post-filtering due to IsPlayed"); - return true; + if (query.IsPlayed.HasValue) + { + Logger.Debug("Query requires post-filtering due to IsPlayed"); + return true; + } } if (query.IsInBoxSet.HasValue) @@ -1059,30 +1085,6 @@ namespace MediaBrowser.Controller.Entities return true; } - if (!string.IsNullOrWhiteSpace(query.NameContains)) - { - Logger.Debug("Query requires post-filtering due to NameContains"); - return true; - } - - if (!string.IsNullOrWhiteSpace(query.NameLessThan)) - { - Logger.Debug("Query requires post-filtering due to NameLessThan"); - return true; - } - - if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) - { - Logger.Debug("Query requires post-filtering due to NameStartsWith"); - return true; - } - - if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater)) - { - Logger.Debug("Query requires post-filtering due to NameStartsWithOrGreater"); - return true; - } - if (query.AirDays.Length > 0) { Logger.Debug("Query requires post-filtering due to AirDays"); @@ -1107,12 +1109,6 @@ namespace MediaBrowser.Controller.Entities return true; } - if (query.AlbumNames.Length > 0) - { - Logger.Debug("Query requires post-filtering due to AlbumNames"); - return true; - } - if (query.ArtistNames.Length > 0) { Logger.Debug("Query requires post-filtering due to ArtistNames"); @@ -1297,33 +1293,42 @@ namespace MediaBrowser.Controller.Entities public IList GetRecursiveChildren(Func filter) { - var list = new List(); + var result = new Dictionary(); - AddChildrenToList(list, true, filter); + AddChildrenToList(result, true, true, filter); - return list; + return result.Values.ToList(); } /// /// Adds the children to list. /// - /// The list. - /// if set to true [recursive]. - /// The filter. - private void AddChildrenToList(List list, bool recursive, Func filter) + private void AddChildrenToList(Dictionary result, bool includeLinkedChildren, bool recursive, Func filter) { foreach (var child in Children) { if (filter == null || filter(child)) { - list.Add(child); + result[child.Id] = child; } if (recursive && child.IsFolder) { var folder = (Folder)child; - folder.AddChildrenToList(list, true, filter); + // We can only support includeLinkedChildren for the first folder, or we might end up stuck in a loop of linked items + folder.AddChildrenToList(result, false, true, filter); + } + } + + if (includeLinkedChildren) + { + foreach (var child in GetLinkedChildren()) + { + if (filter == null || filter(child)) + { + result[child.Id] = child; + } } } } @@ -1567,38 +1572,17 @@ namespace MediaBrowser.Controller.Entities await Task.WhenAll(tasks).ConfigureAwait(false); } - /// - /// Finds an item by path, recursively - /// - /// The path. - /// BaseItem. - /// - public BaseItem FindByPath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(); - } - - if (string.Equals(Path, path, StringComparison.OrdinalIgnoreCase)) - { - return this; - } - - if (PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - return this; - } - - return GetRecursiveChildren(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) || - (!i.IsFolder && !i.IsInMixedFolder && string.Equals(i.ContainingFolderPath, path, StringComparison.OrdinalIgnoreCase)) || - i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) - .FirstOrDefault(); - } - public override bool IsPlayed(User user) { - return GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual) + var itemsResult = GetItems(new InternalItemsQuery(user) + { + Recursive = true, + IsFolder = false, + ExcludeLocationTypes = new[] { LocationType.Virtual } + + }).Result; + + return itemsResult.Items .All(i => i.IsPlayed(user)); } @@ -1607,26 +1591,50 @@ namespace MediaBrowser.Controller.Entities return !IsPlayed(user); } + [IgnoreDataMember] + public virtual bool SupportsUserDataFromChildren + { + get + { + // These are just far too slow. + if (this is ICollectionFolder) + { + return false; + } + if (this is UserView) + { + return false; + } + if (this is UserRootFolder) + { + return false; + } + + return true; + } + } + public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user) { + if (!SupportsUserDataFromChildren) + { + return; + } + var recursiveItemCount = 0; var unplayed = 0; double totalPercentPlayed = 0; - IEnumerable children; - var folder = this; - - var season = folder as Season; - - if (season != null) + var itemsResult = GetItems(new InternalItemsQuery(user) { - children = season.GetEpisodes(user).Where(i => i.LocationType != LocationType.Virtual); - } - else - { - children = folder.GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual); - } + Recursive = true, + IsFolder = false, + ExcludeLocationTypes = new[] { LocationType.Virtual } + + }).Result; + + var children = itemsResult.Items; // Loop through each recursive child foreach (var child in children) @@ -1635,7 +1643,7 @@ namespace MediaBrowser.Controller.Entities var isUnplayed = true; - var itemUserData = UserDataManager.GetUserData(user.Id, child.GetUserDataKey()); + var itemUserData = UserDataManager.GetUserData(user, child); // Incrememt totalPercentPlayed if (itemUserData != null) diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index e597b2a152..9ed2400469 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -76,15 +76,16 @@ namespace MediaBrowser.Controller.Entities /// public List MultiPartGameFiles { get; set; } - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { + var list = base.GetUserDataKeys(); var id = this.GetProviderId(MetadataProviders.Gamesdb); if (!string.IsNullOrEmpty(id)) { - return "Game-Gamesdb-" + id; + list.Insert(0, "Game-Gamesdb-" + id); } - return base.CreateUserDataKey(); + return list; } public override IEnumerable GetDeletePaths() diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index d2b6b48568..7c1e88cb1a 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -7,13 +7,12 @@ namespace MediaBrowser.Controller.Entities { public class GameGenre : BaseItem, IItemByName { - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - return "GameGenre-" + Name; + var list = base.GetUserDataKeys(); + + list.Insert(0, "GameGenre-" + Name); + return list; } /// @@ -63,6 +62,14 @@ namespace MediaBrowser.Controller.Entities return i => i is Game && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase); } + public IEnumerable GetTaggedItems(InternalItemsQuery query) + { + query.Genres = new[] { Name }; + query.IncludeItemTypes = new[] { typeof(Game).Name }; + + return LibraryManager.GetItemList(query); + } + [IgnoreDataMember] public override bool SupportsPeople { diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs index bc35c4738a..1c09ee5075 100644 --- a/MediaBrowser.Controller/Entities/GameSystem.cs +++ b/MediaBrowser.Controller/Entities/GameSystem.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using System; +using System.Collections.Generic; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities @@ -31,17 +32,15 @@ namespace MediaBrowser.Controller.Entities /// The game system. public string GameSystemName { get; set; } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { + var list = base.GetUserDataKeys(); + if (!string.IsNullOrEmpty(GameSystemName)) { - return "GameSystem-" + GameSystemName; + list.Insert(0, "GameSystem-" + GameSystemName); } - return base.CreateUserDataKey(); + return list; } protected override bool GetBlockUnratedValue(UserPolicy config) diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 233e1e0fd1..c87d4daaf9 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -11,13 +11,12 @@ namespace MediaBrowser.Controller.Entities /// public class Genre : BaseItem, IItemByName { - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - return "Genre-" + Name; + var list = base.GetUserDataKeys(); + + list.Insert(0, "Genre-" + Name); + return list; } /// @@ -67,6 +66,14 @@ namespace MediaBrowser.Controller.Entities return i => !(i is Game) && !(i is IHasMusicGenres) && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase); } + public IEnumerable GetTaggedItems(InternalItemsQuery query) + { + query.Genres = new[] { Name }; + query.ExcludeItemTypes = new[] { typeof(Game).Name, typeof(MusicVideo).Name, typeof(Audio.Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name }; + + return LibraryManager.GetItemList(query); + } + [IgnoreDataMember] public override bool SupportsPeople { diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index 1f680b35f3..c7940c8a97 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -49,5 +49,7 @@ namespace MediaBrowser.Controller.Entities /// /// true if [supports people]; otherwise, false. bool SupportsPeople { get; } + + bool RequiresRefresh(); } } diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs index 34a8208533..244b319bd6 100644 --- a/MediaBrowser.Controller/Entities/IHasUserData.cs +++ b/MediaBrowser.Controller/Entities/IHasUserData.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Dto; +using System.Collections.Generic; +using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Entities { @@ -7,11 +8,7 @@ namespace MediaBrowser.Controller.Entities /// public interface IHasUserData : IHasId { - /// - /// Gets the user data key. - /// - /// System.String. - string GetUserDataKey(); + List GetUserDataKeys(); /// /// Fills the user data dto values. @@ -20,5 +17,7 @@ namespace MediaBrowser.Controller.Entities /// The user data. /// The user. void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user); + + bool EnableRememberingTrackSelections { get; } } } diff --git a/MediaBrowser.Controller/Entities/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs index e6667290c1..7747e738cb 100644 --- a/MediaBrowser.Controller/Entities/IItemByName.cs +++ b/MediaBrowser.Controller/Entities/IItemByName.cs @@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities /// /// Func<BaseItem, System.Boolean>. Func GetItemFilter(); + + IEnumerable GetTaggedItems(InternalItemsQuery query); } public interface IHasDualAccess : IItemByName diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index f3b4f40534..5236b0a278 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -46,8 +46,11 @@ namespace MediaBrowser.Controller.Entities public string NameLessThan { get; set; } public string NameContains { get; set; } + public string PresentationUniqueKey { get; set; } public string Path { get; set; } - + public string Name { get; set; } + public string SlugName { get; set; } + public string Person { get; set; } public string[] PersonIds { get; set; } public string[] ItemIds { get; set; } @@ -106,6 +109,7 @@ namespace MediaBrowser.Controller.Entities internal List ItemIdsFromPersonFilters { get; set; } public int? ParentIndexNumber { get; set; } + public int? IndexNumber { get; set; } public int? MinParentalRating { get; set; } public int? MaxParentalRating { get; set; } @@ -130,9 +134,16 @@ namespace MediaBrowser.Controller.Entities public string[] AlbumNames { get; set; } public string[] ArtistNames { get; set; } - + public string AncestorWithPresentationUniqueKey { get; set; } + + public bool GroupByPresentationUniqueKey { get; set; } + public bool EnableTotalRecordCount { get; set; } + public InternalItemsQuery() { + GroupByPresentationUniqueKey = true; + EnableTotalRecordCount = true; + AlbumNames = new string[] { }; ArtistNames = new string[] { }; diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index cd3e07ea38..09a9d97bcf 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using MediaBrowser.Controller.Entities.Audio; namespace MediaBrowser.Controller.Entities.Movies { @@ -118,7 +119,7 @@ namespace MediaBrowser.Controller.Entities.Movies // Gather all possible ratings var ratings = GetRecursiveChildren() .Concat(GetLinkedChildren()) - .Where(i => i is Movie || i is Series) + .Where(i => i is Movie || i is Series || i is MusicAlbum || i is Game) .Select(i => i.OfficialRating) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 4d2ca9ffea..5882b5f4dd 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -18,8 +18,6 @@ namespace MediaBrowser.Controller.Entities.Movies { public List SpecialFeatureIds { get; set; } - public string OriginalTitle { get; set; } - public List ThemeSongIds { get; set; } public List ThemeVideoIds { get; set; } public List ProductionLocations { get; set; } @@ -77,34 +75,6 @@ namespace MediaBrowser.Controller.Entities.Movies get { return TmdbCollectionName; } set { TmdbCollectionName = value; } } - - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() - { - var key = GetMovieUserDataKey(this); - - if (string.IsNullOrWhiteSpace(key)) - { - key = base.CreateUserDataKey(); - } - - return key; - } - - public static string GetMovieUserDataKey(BaseItem movie) - { - var key = movie.GetProviderId(MetadataProviders.Tmdb); - - if (string.IsNullOrWhiteSpace(key)) - { - key = movie.GetProviderId(MetadataProviders.Imdb); - } - - return key; - } protected override async Task RefreshedOwnedItems(MetadataRefreshOptions options, List fileSystemChildren, CancellationToken cancellationToken) { diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index b52f16a462..7119828e26 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -9,12 +9,6 @@ namespace MediaBrowser.Controller.Entities { public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasProductionLocations, IHasBudget, IHasLookupInfo { - /// - /// Gets or sets the album. - /// - /// The album. - public string Album { get; set; } - /// /// Gets or sets the budget. /// @@ -44,15 +38,6 @@ namespace MediaBrowser.Controller.Entities } } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() - { - return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.CreateUserDataKey(); - } - public override UnratedItem GetBlockUnratedType() { return UnratedItem.Music; diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 560ea6e05e..2b099e3d5f 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -18,13 +18,12 @@ namespace MediaBrowser.Controller.Entities /// The place of birth. public string PlaceOfBirth { get; set; } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - return "Person-" + Name; + var list = base.GetUserDataKeys(); + + list.Insert(0, "Person-" + Name); + return list; } public PersonLookupInfo GetLookupInfo() @@ -32,6 +31,13 @@ namespace MediaBrowser.Controller.Entities return GetItemLookupInfo(); } + public IEnumerable GetTaggedItems(InternalItemsQuery query) + { + query.Person = Name; + + return LibraryManager.GetItemList(query); + } + /// /// Returns the folder containing the item. /// If the item is a folder, it returns the folder itself diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs index c8ab67a698..b0ddcfb8c6 100644 --- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs +++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs @@ -7,15 +7,6 @@ namespace MediaBrowser.Controller.Entities { public class PhotoAlbum : Folder { - [IgnoreDataMember] - public override bool SupportsLocalMetadata - { - get - { - return false; - } - } - [IgnoreDataMember] public override bool AlwaysScanInternalMetadataPath { diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index a55527f37b..48ca7bbccc 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -10,13 +10,12 @@ namespace MediaBrowser.Controller.Entities /// public class Studio : BaseItem, IItemByName, IHasTags { - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - return "Studio-" + Name; + var list = base.GetUserDataKeys(); + + list.Insert(0, "Studio-" + Name); + return list; } /// @@ -66,6 +65,13 @@ namespace MediaBrowser.Controller.Entities return i => i.Studios.Contains(Name, StringComparer.OrdinalIgnoreCase); } + public IEnumerable GetTaggedItems(InternalItemsQuery query) + { + query.Studios = new[] { Name }; + + return LibraryManager.GetItemList(query); + } + [IgnoreDataMember] public override bool SupportsPeople { diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index e1a91086b2..a4b0b3082a 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -58,25 +58,7 @@ namespace MediaBrowser.Controller.Entities.TV { get { - return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? PhysicalSeasonNumber; - } - } - - [IgnoreDataMember] - public int? PhysicalSeasonNumber - { - get - { - var value = ParentIndexNumber; - - if (value.HasValue) - { - return value; - } - - var season = Season; - - return season != null ? season.IndexNumber : null; + return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? ParentIndexNumber; } } @@ -98,20 +80,26 @@ namespace MediaBrowser.Controller.Entities.TV } } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + [IgnoreDataMember] + protected override bool EnableDefaultVideoUserDataKeys { - var series = Series; + get + { + return false; + } + } + public override List GetUserDataKeys() + { + var list = base.GetUserDataKeys(); + + var series = Series; if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) { - return series.GetUserDataKey() + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000"); + list.InsertRange(0, series.GetUserDataKeys().Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000"))); } - return base.CreateUserDataKey(); + return list; } /// @@ -222,12 +210,6 @@ namespace MediaBrowser.Controller.Entities.TV } } - [IgnoreDataMember] - public bool IsUnaired - { - get { return PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date; } - } - [IgnoreDataMember] public bool IsVirtualUnaired { @@ -310,6 +292,19 @@ namespace MediaBrowser.Controller.Entities.TV Logger.ErrorException("Error in FillMissingEpisodeNumbersFromPath. Episode: {0}", ex, Path ?? Name ?? Id.ToString()); } + if (!ParentIndexNumber.HasValue) + { + var season = Season; + if (season != null) + { + if (season.ParentIndexNumber.HasValue) + { + ParentIndexNumber = season.ParentIndexNumber; + hasChanges = true; + } + } + } + return hasChanges; } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index acd02e8ab9..ab125eecb6 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -32,6 +32,15 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override bool SupportsDateLastMediaAdded + { + get + { + return true; + } + } + [IgnoreDataMember] public override Guid? DisplayParentId { @@ -45,27 +54,25 @@ namespace MediaBrowser.Controller.Entities.TV // Genre, Rating and Stuido will all be the same protected override IEnumerable GetIndexByOptions() { - return new List { - {"None"}, + return new List { + {"None"}, {"Performer"}, {"Director"}, {"Year"}, }; } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - if (Series != null) + var list = base.GetUserDataKeys(); + + var series = Series; + if (series != null) { - var seasonNo = IndexNumber ?? 0; - return Series.GetUserDataKey() + seasonNo.ToString("000"); + list.InsertRange(0, series.GetUserDataKeys().Select(i => i + (IndexNumber ?? 0).ToString("000"))); } - return base.CreateUserDataKey(); + return list; } /// @@ -94,6 +101,24 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override string PresentationUniqueKey + { + get + { + if (IndexNumber.HasValue) + { + var series = Series; + if (series != null) + { + return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000"); + } + } + + return base.PresentationUniqueKey; + } + } + /// /// Creates the name of the sort. /// @@ -103,17 +128,23 @@ namespace MediaBrowser.Controller.Entities.TV return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name; } - [IgnoreDataMember] - public bool IsMissingSeason + public override bool RequiresRefresh() { - get { return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.IsMissingEpisode); } + var result = base.RequiresRefresh(); + + if (!result) + { + if (!IsMissingSeason.HasValue) + { + return true; + } + } + + return result; } [IgnoreDataMember] - public bool IsUnaired - { - get { return GetEpisodes().All(i => i.IsUnaired); } - } + public bool? IsMissingSeason { get; set; } [IgnoreDataMember] public bool IsVirtualUnaired @@ -124,7 +155,7 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public bool IsMissingOrVirtualUnaired { - get { return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.IsVirtualUnaired || i.IsMissingEpisode); } + get { return (IsMissingSeason ?? false) || (LocationType == LocationType.Virtual && IsUnaired); } } [IgnoreDataMember] @@ -135,22 +166,16 @@ namespace MediaBrowser.Controller.Entities.TV protected override Task> GetItemsInternal(InternalItemsQuery query) { + if (query.User == null) + { + return base.GetItemsInternal(query); + } + var user = query.User; Func filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); - IEnumerable items; - - if (query.User == null) - { - items = query.Recursive - ? GetRecursiveChildren(filter) - : Children.Where(filter); - } - else - { - items = GetEpisodes(query.User).Where(filter); - } + var items = GetEpisodes(user).Where(filter); var result = PostFilterAndSort(items, query); @@ -171,16 +196,16 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetEpisodes(User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) { - var episodes = GetRecursiveChildren(user) - .OfType(); - var series = Series; if (IndexNumber.HasValue && series != null) { - return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes); + return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes); } + var episodes = GetRecursiveChildren(user) + .OfType(); + if (series != null && series.ContainsEpisodesWithoutSeasonFolders) { var seasonNumber = IndexNumber; @@ -199,7 +224,7 @@ namespace MediaBrowser.Controller.Entities.TV episodes = list.DistinctBy(i => i.Id); } - + if (!includeMissingEpisodes) { episodes = episodes.Where(i => !i.IsMissingEpisode); @@ -214,7 +239,7 @@ namespace MediaBrowser.Controller.Entities.TV .Cast(); } - private IEnumerable GetEpisodes() + public IEnumerable GetEpisodes() { var episodes = GetRecursiveChildren().OfType(); var series = Series; diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 9eeb89a1fa..82ab99980d 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; +using MoreLinq; namespace MediaBrowser.Controller.Entities.TV { @@ -19,8 +20,6 @@ namespace MediaBrowser.Controller.Entities.TV { public List SpecialFeatureIds { get; set; } - public string OriginalTitle { get; set; } - public int? AnimeSeriesIndex { get; set; } public Series() @@ -49,6 +48,15 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override bool SupportsDateLastMediaAdded + { + get + { + return true; + } + } + public bool DisplaySpecialsWithSeasons { get; set; } public List LocalTrailerIds { get; set; } @@ -93,25 +101,40 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override string PresentationUniqueKey + { + get + { + if (EnablePooling()) + { + return GetUserDataKeys().First(); + } + return base.PresentationUniqueKey; + } + } + /// /// Gets the user data key. /// /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - var key = this.GetProviderId(MetadataProviders.Tvdb); + var list = base.GetUserDataKeys(); - if (string.IsNullOrWhiteSpace(key)) + var key = this.GetProviderId(MetadataProviders.Imdb); + if (!string.IsNullOrWhiteSpace(key)) { - key = this.GetProviderId(MetadataProviders.Imdb); + list.Insert(0, key); } - if (string.IsNullOrWhiteSpace(key)) + key = this.GetProviderId(MetadataProviders.Tvdb); + if (!string.IsNullOrWhiteSpace(key)) { - key = base.CreateUserDataKey(); + list.Insert(0, key); } - return key; + return list; } /// @@ -128,8 +151,8 @@ namespace MediaBrowser.Controller.Entities.TV // Studio, Genre and Rating will all be the same so makes no sense to index by these protected override IEnumerable GetIndexByOptions() { - return new List { - {"None"}, + return new List { + {"None"}, {"Performer"}, {"Director"}, {"Year"}, @@ -174,7 +197,7 @@ namespace MediaBrowser.Controller.Entities.TV else { items = query.Recursive - ? GetRecursiveChildren(user, filter) + ? GetSeasons(user).Cast().Concat(GetEpisodes(user)).Where(filter) : GetSeasons(user).Where(filter); } @@ -185,8 +208,35 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired) { - var seasons = base.GetChildren(user, true) - .OfType(); + IEnumerable seasons; + + if (EnablePooling()) + { + var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user) + { + PresentationUniqueKey = PresentationUniqueKey, + IncludeItemTypes = new[] { typeof(Series).Name } + }); + + if (seriesIds.Count > 1) + { + seasons = LibraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(), + IncludeItemTypes = new[] { typeof(Season).Name }, + SortBy = new[] { ItemSortBy.SortName } + + }).Cast(); + } + else + { + seasons = LibraryManager.Sort(base.GetChildren(user, true), user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).OfType(); + } + } + else + { + seasons = LibraryManager.Sort(base.GetChildren(user, true), user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).OfType(); + } if (!includeMissingSeasons && !includeVirtualUnaired) { @@ -196,7 +246,7 @@ namespace MediaBrowser.Controller.Entities.TV { if (!includeMissingSeasons) { - seasons = seasons.Where(i => !i.IsMissingSeason); + seasons = seasons.Where(i => !(i.IsMissingSeason ?? false)); } if (!includeVirtualUnaired) { @@ -204,9 +254,7 @@ namespace MediaBrowser.Controller.Entities.TV } } - return LibraryManager - .Sort(seasons, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending) - .Cast(); + return seasons; } public IEnumerable GetEpisodes(User user) @@ -226,17 +274,8 @@ namespace MediaBrowser.Controller.Entities.TV // Specials could appear twice based on above - once in season 0, once in the aired season // This depends on settings for that series // When this happens, remove the duplicate from season 0 - var returnList = new List(); - foreach (var episode in allEpisodes) - { - if (!returnList.Contains(episode)) - { - returnList.Insert(0, episode); - } - } - - return returnList; + return allEpisodes.DistinctBy(i => i.Id).Reverse(); } public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress progress, CancellationToken cancellationToken) @@ -257,7 +296,7 @@ namespace MediaBrowser.Controller.Entities.TV // Refresh current item await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); - // Refresh TV + // Refresh seasons foreach (var item in seasons) { cancellationToken.ThrowIfCancellationRequested(); @@ -270,12 +309,30 @@ namespace MediaBrowser.Controller.Entities.TV progress.Report(percent * 100); } - // Refresh all non-songs + // Refresh episodes and other children foreach (var item in otherItems) { cancellationToken.ThrowIfCancellationRequested(); - await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + var skipItem = false; + + var episode = item as Episode; + + if (episode != null + && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh + && !refreshOptions.ReplaceAllMetadata + && episode.IsMissingEpisode + && episode.LocationType == Model.Entities.LocationType.Virtual + && episode.PremiereDate.HasValue + && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30) + { + skipItem = true; + } + + if (!skipItem) + { + await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + } numComplete++; double percent = numComplete; @@ -295,21 +352,47 @@ namespace MediaBrowser.Controller.Entities.TV return GetEpisodes(user, seasonNumber, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); } - public IEnumerable GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) + private bool EnablePooling() { - return GetEpisodes(user, seasonNumber, includeMissingEpisodes, includeVirtualUnairedEpisodes, - new List()); + return false; } - internal IEnumerable GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable additionalEpisodes) + public IEnumerable GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) { - var episodes = GetRecursiveChildren(user, i => i is Episode) - .Cast(); + IEnumerable episodes; + + if (EnablePooling()) + { + var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user) + { + PresentationUniqueKey = PresentationUniqueKey, + IncludeItemTypes = new[] { typeof(Series).Name } + }); + + if (seriesIds.Count > 1) + { + episodes = LibraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(), + IncludeItemTypes = new[] { typeof(Episode).Name }, + SortBy = new[] { ItemSortBy.SortName } + + }).Cast(); + } + else + { + episodes = GetRecursiveChildren(user, i => i is Episode) + .Cast(); + } + } + else + { + episodes = GetRecursiveChildren(user, i => i is Episode) + .Cast(); + } episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons); - episodes = episodes.Concat(additionalEpisodes).Distinct(); - if (!includeMissingEpisodes) { episodes = episodes.Where(i => !i.IsMissingEpisode); @@ -336,7 +419,7 @@ namespace MediaBrowser.Controller.Entities.TV { if (!includeSpecials || seasonNumber < 1) { - return episodes.Where(i => (i.PhysicalSeasonNumber ?? -1) == seasonNumber); + return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber); } return episodes.Where(i => diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index c3e24090c3..3be2fc6243 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Entities /// /// Class Trailer /// - public class Trailer : Video, IHasCriticRating, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTaglines, IHasMetascore, IHasLookupInfo + public class Trailer : Video, IHasCriticRating, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTaglines, IHasMetascore, IHasOriginalTitle, IHasLookupInfo { public List ProductionLocations { get; set; } @@ -56,26 +56,6 @@ namespace MediaBrowser.Controller.Entities /// The revenue. public double? Revenue { get; set; } - protected override string CreateUserDataKey() - { - var key = Movie.GetMovieUserDataKey(this); - - if (!string.IsNullOrWhiteSpace(key)) - { - key = key + "-trailer"; - - // Make sure different trailers have their own data. - if (RunTimeTicks.HasValue) - { - key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture); - } - - return key; - } - - return base.CreateUserDataKey(); - } - public override UnratedItem GetBlockUnratedType() { return UnratedItem.Trailer; diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index 16c37e7d33..f95fd70364 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -88,6 +88,8 @@ namespace MediaBrowser.Controller.Entities /// /// The index of the subtitle stream. public int? SubtitleStreamIndex { get; set; } + + public const double MinLikeValue = 6.5; /// /// This is an interpreted property to indicate likes or dislikes @@ -101,7 +103,7 @@ namespace MediaBrowser.Controller.Entities { if (Rating != null) { - return Rating >= 6.5; + return Rating >= MinLikeValue; } return null; diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 8ce39c6979..b9e997d175 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -102,10 +102,5 @@ namespace MediaBrowser.Controller.Entities LibraryManager.RegisterItem(item); } } - - public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user) - { - // Nothing meaninful here and will only waste resources - } } } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index db669ca37d..ba496fa5c1 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -348,7 +348,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => !i.IsFolder) .OfType(); - var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite); + var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite); return GetResult(artists, parent, query); } @@ -1077,7 +1077,7 @@ namespace MediaBrowser.Controller.Entities var e = i as Season; if (e != null) { - return e.IsMissingSeason == val; + return (e.IsMissingSeason ?? false) == val; } return true; }); @@ -1218,7 +1218,7 @@ namespace MediaBrowser.Controller.Entities if (query.IsLiked.HasValue) { - userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + userData = userData ?? userDataManager.GetUserData(user, item); if (!userData.Likes.HasValue || userData.Likes != query.IsLiked.Value) { @@ -1228,7 +1228,7 @@ namespace MediaBrowser.Controller.Entities if (query.IsFavoriteOrLiked.HasValue) { - userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + userData = userData ?? userDataManager.GetUserData(user, item); var isFavoriteOrLiked = userData.IsFavorite || (userData.Likes ?? false); if (isFavoriteOrLiked != query.IsFavoriteOrLiked.Value) @@ -1239,7 +1239,7 @@ namespace MediaBrowser.Controller.Entities if (query.IsFavorite.HasValue) { - userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + userData = userData ?? userDataManager.GetUserData(user, item); if (userData.IsFavorite != query.IsFavorite.Value) { @@ -1249,7 +1249,7 @@ namespace MediaBrowser.Controller.Entities if (query.IsResumable.HasValue) { - userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + userData = userData ?? userDataManager.GetUserData(user, item); var isResumable = userData.PlaybackPositionTicks > 0; if (isResumable != query.IsResumable.Value) diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 2c7d3856b3..6a9d7cb511 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -28,7 +28,8 @@ namespace MediaBrowser.Controller.Entities IThemeMedia, IArchivable { - public Guid? PrimaryVersionId { get; set; } + [IgnoreDataMember] + public string PrimaryVersionId { get; set; } public List AdditionalParts { get; set; } public List LocalAlternateVersions { get; set; } @@ -44,6 +45,20 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public override string PresentationUniqueKey + { + get + { + if (!string.IsNullOrWhiteSpace(PrimaryVersionId)) + { + return PrimaryVersionId; + } + + return base.PresentationUniqueKey; + } + } + public long? Size { get; set; } public string Container { get; set; } public int? TotalBitrate { get; set; } @@ -56,143 +71,6 @@ namespace MediaBrowser.Controller.Entities /// The timestamp. public TransportStreamTimestamp? Timestamp { get; set; } - public Video() - { - PlayableStreamFileNames = new List(); - AdditionalParts = new List(); - LocalAlternateVersions = new List(); - Tags = new List(); - SubtitleFiles = new List(); - LinkedAlternateVersions = new List(); - } - - public override bool CanDownload() - { - if (VideoType == VideoType.HdDvd || VideoType == VideoType.Dvd || - VideoType == VideoType.BluRay) - { - return false; - } - - var locationType = LocationType; - return locationType != LocationType.Remote && - locationType != LocationType.Virtual; - } - - [IgnoreDataMember] - public override bool SupportsAddingToPlaylist - { - get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; } - } - - [IgnoreDataMember] - public int MediaSourceCount - { - get - { - return LinkedAlternateVersions.Count + LocalAlternateVersions.Count + 1; - } - } - - [IgnoreDataMember] - public bool IsStacked - { - get { return AdditionalParts.Count > 0; } - } - - [IgnoreDataMember] - public bool HasLocalAlternateVersions - { - get { return LocalAlternateVersions.Count > 0; } - } - - [IgnoreDataMember] - public bool IsArchive - { - get - { - if (string.IsNullOrWhiteSpace(Path)) - { - return false; - } - var ext = System.IO.Path.GetExtension(Path) ?? string.Empty; - - return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase); - } - } - - public IEnumerable GetAdditionalPartIds() - { - return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); - } - - public IEnumerable GetLocalAlternateVersionIds() - { - return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); - } - - protected override string CreateUserDataKey() - { - if (ExtraType.HasValue) - { - var key = this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tmdb); - - if (!string.IsNullOrWhiteSpace(key)) - { - key = key + "-" + ExtraType.ToString().ToLower(); - - // Make sure different trailers have their own data. - if (RunTimeTicks.HasValue) - { - key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture); - } - - return key; - } - } - - return base.CreateUserDataKey(); - } - - /// - /// Gets the linked children. - /// - /// IEnumerable{BaseItem}. - public IEnumerable public class Year : BaseItem, IItemByName { - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - return "Year-" + Name; + var list = base.GetUserDataKeys(); + + list.Insert(0, "Year-" + Name); + return list; } /// @@ -71,6 +70,22 @@ namespace MediaBrowser.Controller.Entities return inputItems.Where(i => i.ProductionYear.HasValue && i.ProductionYear.Value == year); } + public IEnumerable GetTaggedItems(InternalItemsQuery query) + { + int year; + + var usCulture = new CultureInfo("en-US"); + + if (!int.TryParse(Name, NumberStyles.Integer, usCulture, out year)) + { + return new List(); + } + + query.Years = new[] { year }; + + return LibraryManager.GetItemList(query); + } + public int? GetYearValue() { int i; diff --git a/MediaBrowser.Controller/Health/IHealthMonitor.cs b/MediaBrowser.Controller/Health/IHealthMonitor.cs new file mode 100644 index 0000000000..b8ad98fc14 --- /dev/null +++ b/MediaBrowser.Controller/Health/IHealthMonitor.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Notifications; + +namespace MediaBrowser.Controller.Health +{ + public interface IHealthMonitor + { + Task> GetNotifications(CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index c8b3d51317..936b97c6e2 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Library /// The file information. /// The parent. /// BaseItem. - BaseItem ResolvePath(FileSystemMetadata fileInfo, + BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null); /// @@ -36,9 +36,9 @@ namespace MediaBrowser.Controller.Library /// The parent. /// Type of the collection. /// List{``0}. - IEnumerable ResolvePaths(IEnumerable files, + IEnumerable ResolvePaths(IEnumerable files, IDirectoryService directoryService, - Folder parent, string + Folder parent, string collectionType = null); /// @@ -59,8 +59,8 @@ namespace MediaBrowser.Controller.Library /// /// The path. /// BaseItem. - BaseItem FindByPath(string path); - + BaseItem FindByPath(string path, bool? isFolder); + /// /// Gets the artist. /// @@ -156,7 +156,7 @@ namespace MediaBrowser.Controller.Library /// The identifier. /// BaseItem. BaseItem GetMemoryItemById(Guid id); - + /// /// Gets the intros. /// @@ -243,6 +243,8 @@ namespace MediaBrowser.Controller.Library /// BaseItem. BaseItem RetrieveItem(Guid id); + bool IsScanRunning { get; } + /// /// Occurs when [item added]. /// @@ -290,7 +292,7 @@ namespace MediaBrowser.Controller.Library /// The path. /// System.String. string GetConfiguredContentType(string path); - + /// /// Normalizes the root path list. /// @@ -332,8 +334,8 @@ namespace MediaBrowser.Controller.Library Task GetNamedView(User user, string name, string parentId, - string viewType, - string sortName, + string viewType, + string sortName, CancellationToken cancellationToken); /// @@ -346,8 +348,8 @@ namespace MediaBrowser.Controller.Library /// The cancellation token. /// Task<UserView>. Task GetNamedView(User user, - string name, - string viewType, + string name, + string viewType, string sortName, CancellationToken cancellationToken); @@ -393,7 +395,7 @@ namespace MediaBrowser.Controller.Library string viewType, string sortName, CancellationToken cancellationToken); - + /// /// Determines whether [is video file] [the specified path]. /// @@ -477,14 +479,14 @@ namespace MediaBrowser.Controller.Library /// The query. /// List<PersonInfo>. List GetPeople(InternalPeopleQuery query); - + /// /// Gets the people items. /// /// The query. /// List<Person>. List GetPeopleItems(InternalPeopleQuery query); - + /// /// Gets all people names. /// @@ -559,7 +561,7 @@ namespace MediaBrowser.Controller.Library /// The query. /// QueryResult<BaseItem>. QueryResult GetItemsResult(InternalItemsQuery query); - + /// /// Ignores the file. /// @@ -567,5 +569,10 @@ namespace MediaBrowser.Controller.Library /// The parent. /// true if XXXX, false otherwise. bool IgnoreFile(FileSystemMetadata file, BaseItem parent); + + void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary); + void RemoveVirtualFolder(string name, bool refreshLibrary); + void AddMediaPath(string virtualFolderName, string path); + void RemoveMediaPath(string virtualFolderName, string path); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 56ac14e9df..e2358650be 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -29,21 +29,10 @@ namespace MediaBrowser.Controller.Library /// Task. Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); - /// - /// Gets the user data. - /// - /// The user id. - /// The key. - /// Task{UserItemData}. - UserItemData GetUserData(string userId, string key); + UserItemData GetUserData(IHasUserData user, IHasUserData item); - /// - /// Gets the user data. - /// - /// The user id. - /// The key. - /// Task{UserItemData}. - UserItemData GetUserData(Guid userId, string key); + UserItemData GetUserData(string userId, IHasUserData item); + UserItemData GetUserData(Guid userId, IHasUserData item); /// /// Gets the user data dto. diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index f8039b2cf2..29421ebafc 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Library /// /// The banner URL /// - public static readonly string BannerUrl = "http://www.thetvdb.com/banners/"; + public static readonly string BannerUrl = "https://www.thetvdb.com/banners/"; /// /// Gets the air days. diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs index ba328ff754..654c6b581b 100644 --- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs +++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.Library { @@ -15,11 +16,7 @@ namespace MediaBrowser.Controller.Library /// The user id. public Guid UserId { get; set; } - /// - /// Gets or sets the key. - /// - /// The key. - public string Key { get; set; } + public List Keys { get; set; } /// /// Gets or sets the save reason. diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 498602ddf9..1e7aa3de5a 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -46,6 +46,8 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// Task<List<MediaSourceInfo>>. Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); + + string ApplyDuration(string streamPath, TimeSpan duration); } public interface IConfigurableTunerHost { diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs index 17a27eac1e..e6f472414e 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs @@ -45,17 +45,6 @@ namespace MediaBrowser.Controller.LiveTv set { } } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() - { - var name = GetClientTypeName(); - - return name + "-" + Name + (EpisodeTitle ?? string.Empty); - } - /// /// Gets a value indicating whether this instance is owned item. /// diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 35b9a1959e..50aeed27d4 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -11,13 +11,12 @@ namespace MediaBrowser.Controller.LiveTv { public class LiveTvChannel : BaseItem, IHasMediaSources { - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - return GetClientTypeName() + "-" + Name; + var list = base.GetUserDataKeys(); + + list.Insert(0, GetClientTypeName() + "-" + Name); + return list; } public override UnratedItem GetBlockUnratedType() @@ -45,6 +44,15 @@ namespace MediaBrowser.Controller.LiveTv set { } } + [IgnoreDataMember] + public override bool EnableRememberingTrackSelections + { + get + { + return false; + } + } + /// /// Gets or sets the number. /// diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 59b921c6a9..cc30709db2 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -4,36 +4,40 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.LiveTv; using System; +using System.Collections.Generic; using System.Runtime.Serialization; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.LiveTv { public class LiveTvProgram : BaseItem, IHasLookupInfo, IHasStartDate, IHasProgramAttributes { - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() + public override List GetUserDataKeys() { - if (IsMovie) - { - var key = Movie.GetMovieUserDataKey(this); + var list = base.GetUserDataKeys(); + if (!IsSeries) + { + var key = this.GetProviderId(MetadataProviders.Imdb); if (!string.IsNullOrWhiteSpace(key)) { - return key; + list.Insert(0, key); + } + + key = this.GetProviderId(MetadataProviders.Tmdb); + if (!string.IsNullOrWhiteSpace(key)) + { + list.Insert(0, key); } } - - if (IsSeries && !string.IsNullOrWhiteSpace(EpisodeTitle)) + else if (!string.IsNullOrWhiteSpace(EpisodeTitle)) { var name = GetClientTypeName(); - return name + "-" + Name + (EpisodeTitle ?? string.Empty); + list.Insert(0, name + "-" + Name + (EpisodeTitle ?? string.Empty)); } - return base.CreateUserDataKey(); + return list; } [IgnoreDataMember] diff --git a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs index f310a957cc..a8c7376734 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs @@ -45,32 +45,6 @@ namespace MediaBrowser.Controller.LiveTv set { } } - /// - /// Gets the user data key. - /// - /// System.String. - protected override string CreateUserDataKey() - { - if (IsMovie) - { - var key = Movie.GetMovieUserDataKey(this); - - if (!string.IsNullOrWhiteSpace(key)) - { - return key; - } - } - - if (IsSeries && !string.IsNullOrWhiteSpace(EpisodeTitle)) - { - var name = GetClientTypeName(); - - return name + "-" + Name + (EpisodeTitle ?? string.Empty); - } - - return base.CreateUserDataKey(); - } - [IgnoreDataMember] public override string MediaType { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 6ff4e39e2d..bc28ec0155 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -180,6 +180,7 @@ + diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index d2feb41169..7c3959f6e8 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -4,13 +4,14 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Dlna; namespace MediaBrowser.Controller.MediaEncoding { /// /// Interface IMediaEncoder /// - public interface IMediaEncoder + public interface IMediaEncoder : ITranscoderSupport { /// /// Gets the encoder path. diff --git a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs index 6ad4a5377e..cd587a5099 100644 --- a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs +++ b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs @@ -19,12 +19,6 @@ namespace MediaBrowser.Controller.Notifications /// Occurs when [notifications marked read]. /// event EventHandler NotificationsMarkedRead; - - /// - /// Opens the connection to the repository - /// - /// Task. - Task Initialize(); /// /// Gets the notifications. diff --git a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs index 17de730cb8..abf96994f1 100644 --- a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs +++ b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs @@ -11,12 +11,6 @@ namespace MediaBrowser.Controller.Persistence /// public interface IDisplayPreferencesRepository : IRepository { - /// - /// Opens the connection to the repository - /// - /// Task. - Task Initialize(); - /// /// Saves display preferences for an item /// diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 15df1f6492..7bcc36958a 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -13,12 +13,6 @@ namespace MediaBrowser.Controller.Persistence /// public interface IItemRepository : IRepository { - /// - /// Opens the connection to the repository - /// - /// Task. - Task Initialize(); - /// /// Saves an item /// diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 2a904be0d9..2e165f4163 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -11,12 +11,6 @@ namespace MediaBrowser.Controller.Persistence /// public interface IUserDataRepository : IRepository { - /// - /// Opens the connection to the repository - /// - /// Task. - Task Initialize(); - /// /// Saves the user data. /// diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 0f9af6550b..67b1d479b9 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using System.Threading.Tasks; namespace MediaBrowser.Controller.Playlists { @@ -38,6 +39,15 @@ namespace MediaBrowser.Controller.Playlists } } + [IgnoreDataMember] + public override bool SupportsCumulativeRunTimeTicks + { + get + { + return true; + } + } + public override bool IsAuthorizedToDelete(User user) { return true; @@ -50,12 +60,12 @@ namespace MediaBrowser.Controller.Playlists public override IEnumerable GetChildren(User user, bool includeLinkedChildren) { - return GetPlayableItems(user); + return GetPlayableItems(user).Result; } public override IEnumerable GetRecursiveChildren(User user, Func filter) { - var items = GetPlayableItems(user); + var items = GetPlayableItems(user).Result; if (filter != null) { @@ -70,32 +80,40 @@ namespace MediaBrowser.Controller.Playlists return GetLinkedChildrenInfos(); } - private IEnumerable GetPlayableItems(User user) + private Task> GetPlayableItems(User user) { return GetPlaylistItems(MediaType, base.GetChildren(user, true), user); } - public static IEnumerable GetPlaylistItems(string playlistMediaType, IEnumerable inputItems, User user) + public static async Task> GetPlaylistItems(string playlistMediaType, IEnumerable inputItems, User user) { if (user != null) { inputItems = inputItems.Where(i => i.IsVisible(user)); } - return inputItems.SelectMany(i => GetPlaylistItems(i, user)) - .Where(m => string.Equals(m.MediaType, playlistMediaType, StringComparison.OrdinalIgnoreCase)); + var list = new List(); + + foreach (var item in inputItems) + { + var playlistItems = await GetPlaylistItems(item, user, playlistMediaType).ConfigureAwait(false); + list.AddRange(playlistItems); + } + + return list; } - private static IEnumerable GetPlaylistItems(BaseItem item, User user) + private static async Task> GetPlaylistItems(BaseItem item, User user, string mediaType) { var musicGenre = item as MusicGenre; if (musicGenre != null) { - Func filter = i => i is Audio && i.Genres.Contains(musicGenre.Name, StringComparer.OrdinalIgnoreCase); - - var items = user == null - ? LibraryManager.RootFolder.GetRecursiveChildren(filter) - : user.RootFolder.GetRecursiveChildren(user, filter); + var items = LibraryManager.GetItemList(new InternalItemsQuery(user) + { + Recursive = true, + IncludeItemTypes = new[] { typeof(Audio).Name }, + Genres = new[] { musicGenre.Name } + }); return LibraryManager.Sort(items, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending); } @@ -119,15 +137,19 @@ namespace MediaBrowser.Controller.Playlists var folder = item as Folder; if (folder != null) { - var items = user == null - ? folder.GetRecursiveChildren(m => !m.IsFolder) - : folder.GetRecursiveChildren(user, m => !m.IsFolder); - - if (folder.IsPreSorted) + var query = new InternalItemsQuery(user) { - return items; - } - return LibraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending); + Recursive = true, + IsFolder = false, + SortBy = new[] { ItemSortBy.SortName }, + MediaTypes = new[] { mediaType }, + EnableTotalRecordCount = false + }; + + var itemsResult = await folder.GetItems(query).ConfigureAwait(false); + var items = itemsResult.Items; + + return items; } return new[] { item }; diff --git a/MediaBrowser.Controller/Providers/IProviderRepository.cs b/MediaBrowser.Controller/Providers/IProviderRepository.cs index 1f77d0ca1c..891275d775 100644 --- a/MediaBrowser.Controller/Providers/IProviderRepository.cs +++ b/MediaBrowser.Controller/Providers/IProviderRepository.cs @@ -21,11 +21,5 @@ namespace MediaBrowser.Controller.Providers /// The cancellation token. /// Task. Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken); - - /// - /// Initializes this instance. - /// - /// Task. - Task Initialize(); } } diff --git a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs index b6ee3d4341..a8e778751d 100644 --- a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs +++ b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs @@ -1,194 +1,41 @@ -namespace MediaBrowser.Dlna.Channels +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Dlna.ContentDirectory; +using MediaBrowser.Dlna.PlayTo; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Dlna.Channels { - //public class DlnaChannelFactory : IChannelFactory, IDisposable + //public class DlnaChannel : IChannel, IDisposable //{ - // private readonly IServerConfigurationManager _config; // private readonly ILogger _logger; // private readonly IHttpClient _httpClient; - - // private readonly IDeviceDiscovery _deviceDiscovery; - - // private readonly SemaphoreSlim _syncLock = new SemaphoreSlim(1, 1); + // private readonly IServerConfigurationManager _config; // private List _servers = new List(); - // public static DlnaChannelFactory Instance; - + // private readonly IDeviceDiscovery _deviceDiscovery; + // private readonly SemaphoreSlim _syncLock = new SemaphoreSlim(1, 1); // private Func> _localServersLookup; - // public DlnaChannelFactory(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IDeviceDiscovery deviceDiscovery) + // public static DlnaChannel Current; + + // public DlnaChannel(ILogger logger, IHttpClient httpClient, IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config) // { - // _config = config; - // _httpClient = httpClient; // _logger = logger; + // _httpClient = httpClient; // _deviceDiscovery = deviceDiscovery; - // Instance = this; - // } - - // internal void Start(Func> localServersLookup) - // { - // _localServersLookup = localServersLookup; - - // //deviceDiscovery.DeviceDiscovered += deviceDiscovery_DeviceDiscovered; - // _deviceDiscovery.DeviceLeft += deviceDiscovery_DeviceLeft; - // } - - // async void deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) - // { - // string usn; - // if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; - - // string nt; - // if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; - - // string location; - // if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty; - - // if (!IsValid(nt, usn)) - // { - // return; - // } - - // if (_localServersLookup != null) - // { - // if (_localServersLookup().Any(i => usn.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) - // { - // // Don't add the local Dlna server to this - // return; - // } - // } - - // if (GetExistingServers(usn).Any()) - // { - // return; - // } - - // await _syncLock.WaitAsync().ConfigureAwait(false); - - // try - // { - // if (GetExistingServers(usn).Any()) - // { - // return; - // } - - // var device = await Device.CreateuPnpDeviceAsync(new Uri(location), _httpClient, _config, _logger) - // .ConfigureAwait(false); - - // if (!_servers.Any(i => string.Equals(i.Properties.UUID, device.Properties.UUID, StringComparison.OrdinalIgnoreCase))) - // { - // _servers.Add(device); - // } - // } - // catch (Exception ex) - // { - - // } - // finally - // { - // _syncLock.Release(); - // } - // } - - // async void deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e) - // { - // string usn; - // if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty; - - // string nt; - // if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty; - - // if (!IsValid(nt, usn)) - // { - // return; - // } - - // if (!GetExistingServers(usn).Any()) - // { - // return; - // } - - // await _syncLock.WaitAsync().ConfigureAwait(false); - - // try - // { - // var list = _servers.ToList(); - - // foreach (var device in GetExistingServers(usn).ToList()) - // { - // list.Remove(device); - // } - - // _servers = list; - // } - // finally - // { - // _syncLock.Release(); - // } - // } - - // private bool IsValid(string nt, string usn) - // { - // // It has to report that it's a media renderer - // if (usn.IndexOf("ContentDirectory:", StringComparison.OrdinalIgnoreCase) == -1 && - // nt.IndexOf("ContentDirectory:", StringComparison.OrdinalIgnoreCase) == -1 && - // usn.IndexOf("MediaServer:", StringComparison.OrdinalIgnoreCase) == -1 && - // nt.IndexOf("MediaServer:", StringComparison.OrdinalIgnoreCase) == -1) - // { - // return false; - // } - - // return true; - // } - - // private IEnumerable GetExistingServers(string usn) - // { - // return _servers - // .Where(i => usn.IndexOf(i.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1); - // } - - // public IEnumerable GetChannels() - // { - // //if (_servers.Count > 0) - // //{ - // // var service = _servers[0].Properties.Services - // // .FirstOrDefault(i => string.Equals(i.ServiceType, "urn:schemas-upnp-org:service:ContentDirectory:1", StringComparison.OrdinalIgnoreCase)); - - // // var controlUrl = service == null ? null : (_servers[0].Properties.BaseUrl.TrimEnd('/') + "/" + service.ControlUrl.TrimStart('/')); - - // // if (!string.IsNullOrEmpty(controlUrl)) - // // { - // // return new List - // // { - // // new ServerChannel(_servers.ToList(), _httpClient, _logger, controlUrl) - // // }; - // // } - // //} - - // return new List(); - // } - - // public void Dispose() - // { - // if (_deviceDiscovery != null) - // { - // _deviceDiscovery.DeviceDiscovered -= deviceDiscovery_DeviceDiscovered; - // _deviceDiscovery.DeviceLeft -= deviceDiscovery_DeviceLeft; - // } - // } - //} - - //public class ServerChannel : IChannel, IFactoryChannel - //{ - // private readonly IHttpClient _httpClient; - // private readonly ILogger _logger; - // public string ControlUrl { get; set; } - // public List Servers { get; set; } - - // public ServerChannel(IHttpClient httpClient, ILogger logger) - // { - // _httpClient = httpClient; - // _logger = logger; - // Servers = new List(); + // _config = config; + // Current = this; // } // public string Name @@ -221,17 +68,17 @@ // return new InternalChannelFeatures // { // ContentTypes = new List - // { - // ChannelMediaContentType.Song, - // ChannelMediaContentType.Clip - // }, + // { + // ChannelMediaContentType.Song, + // ChannelMediaContentType.Clip + // }, // MediaTypes = new List - // { - // ChannelMediaType.Audio, - // ChannelMediaType.Video, - // ChannelMediaType.Photo - // } + // { + // ChannelMediaType.Audio, + // ChannelMediaType.Video, + // ChannelMediaType.Photo + // } // }; // } @@ -240,85 +87,219 @@ // return true; // } - // public async Task GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken) - // { - // IEnumerable items; - - // if (string.IsNullOrWhiteSpace(query.FolderId)) - // { - // items = Servers.Select(i => new ChannelItemInfo - // { - // FolderType = ChannelFolderType.Container, - // Id = GetServerId(i), - // Name = i.Properties.Name, - // Overview = i.Properties.ModelDescription, - // Type = ChannelItemType.Folder - // }); - // } - // else - // { - // var idParts = query.FolderId.Split('|'); - // var folderId = idParts.Length == 2 ? idParts[1] : null; - - // var result = await new ContentDirectoryBrowser(_httpClient, _logger).Browse(new ContentDirectoryBrowseRequest - // { - // Limit = query.Limit, - // StartIndex = query.StartIndex, - // ParentId = folderId, - // ContentDirectoryUrl = ControlUrl - - // }, cancellationToken).ConfigureAwait(false); - - // items = result.Items.ToList(); - // } - - // var list = items.ToList(); - // var count = list.Count; - - // list = ApplyPaging(list, query).ToList(); - - // return new ChannelItemResult - // { - // Items = list, - // TotalRecordCount = count - // }; - // } - - // private string GetServerId(Device device) - // { - // return device.Properties.UUID.GetMD5().ToString("N"); - // } - - // private IEnumerable ApplyPaging(IEnumerable items, InternalChannelItemQuery query) - // { - // if (query.StartIndex.HasValue) - // { - // items = items.Skip(query.StartIndex.Value); - // } - - // if (query.Limit.HasValue) - // { - // items = items.Take(query.Limit.Value); - // } - - // return items; - // } - // public Task GetChannelImage(ImageType type, CancellationToken cancellationToken) // { - // // TODO: Implement - // return Task.FromResult(new DynamicImageResponse - // { - // HasImage = false - // }); + // throw new NotImplementedException(); // } // public IEnumerable GetSupportedChannelImages() // { // return new List + // { + // ImageType.Primary + // }; + // } + + // public void Start(Func> localServersLookup) + // { + // _localServersLookup = localServersLookup; + + // _deviceDiscovery.DeviceDiscovered -= deviceDiscovery_DeviceDiscovered; + // _deviceDiscovery.DeviceLeft -= deviceDiscovery_DeviceLeft; + + // _deviceDiscovery.DeviceDiscovered += deviceDiscovery_DeviceDiscovered; + // _deviceDiscovery.DeviceLeft += deviceDiscovery_DeviceLeft; + // } + + // public async Task GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken) + // { + // if (string.IsNullOrWhiteSpace(query.FolderId)) // { - // ImageType.Primary - // }; + // return await GetServers(query, cancellationToken).ConfigureAwait(false); + // } + + // return new ChannelItemResult(); + + // //var idParts = query.FolderId.Split('|'); + // //var folderId = idParts.Length == 2 ? idParts[1] : null; + + // //var result = await new ContentDirectoryBrowser(_httpClient, _logger).Browse(new ContentDirectoryBrowseRequest + // //{ + // // Limit = query.Limit, + // // StartIndex = query.StartIndex, + // // ParentId = folderId, + // // ContentDirectoryUrl = ControlUrl + + // //}, cancellationToken).ConfigureAwait(false); + + // //items = result.Items.ToList(); + + // //var list = items.ToList(); + // //var count = list.Count; + + // //list = ApplyPaging(list, query).ToList(); + + // //return new ChannelItemResult + // //{ + // // Items = list, + // // TotalRecordCount = count + // //}; + // } + + // public async Task GetServers(InternalChannelItemQuery query, CancellationToken cancellationToken) + // { + // await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + // try + // { + // var items = _servers.Select(i => + // { + // var service = i.Properties.Services + // .FirstOrDefault(s => string.Equals(s.ServiceType, "urn:schemas-upnp-org:service:ContentDirectory:1", StringComparison.OrdinalIgnoreCase)); + + // var controlUrl = service == null ? null : (_servers[0].Properties.BaseUrl.TrimEnd('/') + "/" + service.ControlUrl.TrimStart('/')); + + // if (string.IsNullOrWhiteSpace(controlUrl)) + // { + // return null; + // } + + // return new ChannelItemInfo + // { + // Id = i.Properties.UUID, + // Name = i.Properties.Name, + // Type = ChannelItemType.Folder + // }; + + // }).Where(i => i != null).ToList(); + + // return new ChannelItemResult + // { + // TotalRecordCount = items.Count, + // Items = items + // }; + // } + // finally + // { + // _syncLock.Release(); + // } + // } + + // async void deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) + // { + // string usn; + // if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + + // string nt; + // if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + + // string location; + // if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty; + + // if (!IsValid(nt, usn)) + // { + // return; + // } + + // if (_localServersLookup != null) + // { + // if (_localServersLookup().Any(i => usn.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) + // { + // // Don't add the local Dlna server to this + // return; + // } + // } + + // await _syncLock.WaitAsync().ConfigureAwait(false); + + // var serverList = _servers.ToList(); + + // try + // { + // if (GetExistingServers(serverList, usn).Any()) + // { + // return; + // } + + // var device = await Device.CreateuPnpDeviceAsync(new Uri(location), _httpClient, _config, _logger) + // .ConfigureAwait(false); + + // if (!serverList.Any(i => string.Equals(i.Properties.UUID, device.Properties.UUID, StringComparison.OrdinalIgnoreCase))) + // { + // serverList.Add(device); + // } + // } + // catch (Exception ex) + // { + + // } + // finally + // { + // _syncLock.Release(); + // } + // } + + // async void deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e) + // { + // string usn; + // if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty; + + // string nt; + // if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty; + + // if (!IsValid(nt, usn)) + // { + // return; + // } + + // await _syncLock.WaitAsync().ConfigureAwait(false); + + // try + // { + // var serverList = _servers.ToList(); + + // var matchingServers = GetExistingServers(serverList, usn); + // if (matchingServers.Count > 0) + // { + // foreach (var device in matchingServers) + // { + // serverList.Remove(device); + // } + + // _servers = serverList; + // } + // } + // finally + // { + // _syncLock.Release(); + // } + // } + + // private bool IsValid(string nt, string usn) + // { + // // It has to report that it's a media renderer + // if (usn.IndexOf("ContentDirectory:", StringComparison.OrdinalIgnoreCase) == -1 && + // nt.IndexOf("ContentDirectory:", StringComparison.OrdinalIgnoreCase) == -1 && + // usn.IndexOf("MediaServer:", StringComparison.OrdinalIgnoreCase) == -1 && + // nt.IndexOf("MediaServer:", StringComparison.OrdinalIgnoreCase) == -1) + // { + // return false; + // } + + // return true; + // } + + // private List GetExistingServers(List allDevices, string usn) + // { + // return allDevices + // .Where(i => usn.IndexOf(i.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1) + // .ToList(); + // } + + // public void Dispose() + // { + // _deviceDiscovery.DeviceDiscovered -= deviceDiscovery_DeviceDiscovered; + // _deviceDiscovery.DeviceLeft -= deviceDiscovery_DeviceLeft; // } //} } diff --git a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs index 21289970e2..093b37df3e 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs @@ -12,6 +12,7 @@ using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Dlna.ContentDirectory { @@ -27,6 +28,7 @@ namespace MediaBrowser.Dlna.ContentDirectory private readonly IChannelManager _channelManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly IUserViewManager _userViewManager; + private readonly Func _mediaEncoder; public ContentDirectory(IDlnaManager dlna, IUserDataManager userDataManager, @@ -35,7 +37,7 @@ namespace MediaBrowser.Dlna.ContentDirectory IServerConfigurationManager config, IUserManager userManager, ILogger logger, - IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager) + IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func mediaEncoder) : base(logger, httpClient) { _dlna = dlna; @@ -48,6 +50,7 @@ namespace MediaBrowser.Dlna.ContentDirectory _channelManager = channelManager; _mediaSourceManager = mediaSourceManager; _userViewManager = userViewManager; + _mediaEncoder = mediaEncoder; } private int SystemUpdateId @@ -89,7 +92,8 @@ namespace MediaBrowser.Dlna.ContentDirectory _localization, _channelManager, _mediaSourceManager, - _userViewManager) + _userViewManager, + _mediaEncoder()) .ProcessControlRequest(request); } diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs index 01c7c33b6f..bc9fa1d7e7 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -23,6 +23,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Dlna.ContentDirectory { @@ -34,6 +35,7 @@ namespace MediaBrowser.Dlna.ContentDirectory private readonly IServerConfigurationManager _config; private readonly User _user; private readonly IUserViewManager _userViewManager; + private readonly IMediaEncoder _mediaEncoder; private const string NS_DC = "http://purl.org/dc/elements/1.1/"; private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; @@ -47,7 +49,7 @@ namespace MediaBrowser.Dlna.ContentDirectory private readonly DeviceProfile _profile; - public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager) + public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder) : base(config, logger) { _libraryManager = libraryManager; @@ -56,10 +58,11 @@ namespace MediaBrowser.Dlna.ContentDirectory _systemUpdateId = systemUpdateId; _channelManager = channelManager; _userViewManager = userViewManager; + _mediaEncoder = mediaEncoder; _profile = profile; _config = config; - _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager); + _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, _mediaEncoder); } protected override IEnumerable> GetResult(string methodName, Headers methodParams) @@ -108,7 +111,7 @@ namespace MediaBrowser.Dlna.ContentDirectory var newbookmark = int.Parse(sparams["PosSecond"], _usCulture); - var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + var userdata = _userDataManager.GetUserData(user, item); userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks; diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 89d00eb32b..af833a85cf 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -19,6 +19,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Xml; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; namespace MediaBrowser.Dlna.Didl @@ -42,8 +43,9 @@ namespace MediaBrowser.Dlna.Didl private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; + private readonly IMediaEncoder _mediaEncoder; - public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager) + public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager, IMediaEncoder mediaEncoder) { _profile = profile; _imageProcessor = imageProcessor; @@ -53,6 +55,7 @@ namespace MediaBrowser.Dlna.Didl _mediaSourceManager = mediaSourceManager; _logger = logger; _libraryManager = libraryManager; + _mediaEncoder = mediaEncoder; _accessToken = accessToken; _user = user; } @@ -142,7 +145,7 @@ namespace MediaBrowser.Dlna.Didl { var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); - streamInfo = new StreamBuilder(GetStreamBuilderLogger(options)).BuildVideoItem(new VideoOptions + streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildVideoItem(new VideoOptions { ItemId = GetClientId(video), MediaSources = sources, @@ -385,7 +388,7 @@ namespace MediaBrowser.Dlna.Didl { var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); - streamInfo = new StreamBuilder(GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions + streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions { ItemId = GetClientId(audio), MediaSources = sources, diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index 37584f006f..22f308979f 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -14,6 +14,9 @@ using MediaBrowser.Dlna.Ssdp; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Dlna.Channels; namespace MediaBrowser.Dlna.Main { @@ -34,11 +37,13 @@ namespace MediaBrowser.Dlna.Main private readonly IUserDataManager _userDataManager; private readonly ILocalizationManager _localization; private readonly IMediaSourceManager _mediaSourceManager; + private readonly IMediaEncoder _mediaEncoder; private readonly SsdpHandler _ssdpHandler; private readonly IDeviceDiscovery _deviceDiscovery; private readonly List _registeredServerIds = new List(); + private bool _ssdpHandlerStarted; private bool _dlnaServerStarted; public DlnaEntryPoint(IServerConfigurationManager config, @@ -54,7 +59,7 @@ namespace MediaBrowser.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery) + ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder) { _config = config; _appHost = appHost; @@ -69,18 +74,29 @@ namespace MediaBrowser.Dlna.Main _localization = localization; _mediaSourceManager = mediaSourceManager; _deviceDiscovery = deviceDiscovery; + _mediaEncoder = mediaEncoder; _ssdpHandler = (SsdpHandler)ssdpHandler; _logger = logManager.GetLogger("Dlna"); } public void Run() { - StartSsdpHandler(); ReloadComponents(); + _config.ConfigurationUpdated += _config_ConfigurationUpdated; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; } + private bool _lastEnableUpnP; + void _config_ConfigurationUpdated(object sender, EventArgs e) + { + if (_lastEnableUpnP != _config.Configuration.EnableUPnP) + { + ReloadComponents(); + } + _lastEnableUpnP = _config.Configuration.EnableUPnP; + } + void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) { if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) @@ -91,10 +107,27 @@ namespace MediaBrowser.Dlna.Main private void ReloadComponents() { - var isServerStarted = _dlnaServerStarted; - var options = _config.GetDlnaConfiguration(); + if (!options.EnableServer && !options.EnablePlayTo && !_config.Configuration.EnableUPnP) + { + if (_ssdpHandlerStarted) + { + // Sat/ip live tv depends on device discovery, as well as hd homerun detection + // In order to allow this to be disabled, we need a modular way of knowing if there are + // any parts of the system that are dependant on it + // DisposeSsdpHandler(); + } + return; + } + + if (!_ssdpHandlerStarted) + { + StartSsdpHandler(); + } + + var isServerStarted = _dlnaServerStarted; + if (options.EnableServer && !isServerStarted) { StartDlnaServer(); @@ -121,8 +154,9 @@ namespace MediaBrowser.Dlna.Main try { _ssdpHandler.Start(); + _ssdpHandlerStarted = true; - ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler); + StartDeviceDiscovery(); } catch (Exception ex) { @@ -130,6 +164,50 @@ namespace MediaBrowser.Dlna.Main } } + private void StartDeviceDiscovery() + { + try + { + ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler); + + //DlnaChannel.Current.Start(() => _registeredServerIds.ToList()); + } + catch (Exception ex) + { + _logger.ErrorException("Error starting device discovery", ex); + } + } + + private void DisposeDeviceDiscovery() + { + try + { + ((DeviceDiscovery)_deviceDiscovery).Dispose(); + } + catch (Exception ex) + { + _logger.ErrorException("Error stopping device discovery", ex); + } + } + + private void DisposeSsdpHandler() + { + DisposeDeviceDiscovery(); + + try + { + ((DeviceDiscovery)_deviceDiscovery).Dispose(); + + _ssdpHandler.Dispose(); + + _ssdpHandlerStarted = false; + } + catch (Exception ex) + { + _logger.ErrorException("Error stopping ssdp handlers", ex); + } + } + public void StartDlnaServer() { try @@ -196,7 +274,8 @@ namespace MediaBrowser.Dlna.Main _config, _userDataManager, _localization, - _mediaSourceManager); + _mediaSourceManager, + _mediaEncoder); _manager.Start(); } @@ -230,6 +309,7 @@ namespace MediaBrowser.Dlna.Main { DisposeDlnaServer(); DisposePlayToManager(); + DisposeSsdpHandler(); } public void DisposeDlnaServer() diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index db5e0ee293..80bb756ef5 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Dlna.PlayTo { @@ -35,6 +36,7 @@ namespace MediaBrowser.Dlna.PlayTo private readonly ILocalizationManager _localization; private readonly IMediaSourceManager _mediaSourceManager; private readonly IConfigurationManager _config; + private readonly IMediaEncoder _mediaEncoder; private readonly IDeviceDiscovery _deviceDiscovery; private readonly string _serverAddress; @@ -74,7 +76,7 @@ namespace MediaBrowser.Dlna.PlayTo get { return IsSessionActive; } } - public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IConfigurationManager config) + public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder) { _session = session; _sessionManager = sessionManager; @@ -88,6 +90,7 @@ namespace MediaBrowser.Dlna.PlayTo _localization = localization; _mediaSourceManager = mediaSourceManager; _config = config; + _mediaEncoder = mediaEncoder; _accessToken = accessToken; _logger = logger; _creationTime = DateTime.UtcNow; @@ -478,7 +481,7 @@ namespace MediaBrowser.Dlna.PlayTo playlistItem.StreamUrl = playlistItem.StreamInfo.ToDlnaUrl(_serverAddress, _accessToken); - var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager) + var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager, _mediaEncoder) .GetItemDidl(_config.GetDlnaConfiguration(), item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); playlistItem.Didl = itemXml; @@ -550,7 +553,7 @@ namespace MediaBrowser.Dlna.PlayTo { return new PlaylistItem { - StreamInfo = new StreamBuilder(GetStreamBuilderLogger()).BuildVideoItem(new VideoOptions + StreamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger()).BuildVideoItem(new VideoOptions { ItemId = item.Id.ToString("N"), MediaSources = mediaSources, @@ -570,7 +573,7 @@ namespace MediaBrowser.Dlna.PlayTo { return new PlaylistItem { - StreamInfo = new StreamBuilder(GetStreamBuilderLogger()).BuildAudioItem(new AudioOptions + StreamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger()).BuildAudioItem(new AudioOptions { ItemId = item.Id.ToString("N"), MediaSources = mediaSources, diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index 18daef331a..bbb9bf6de9 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Dlna.PlayTo { @@ -32,11 +33,12 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IDeviceDiscovery _deviceDiscovery; private readonly IMediaSourceManager _mediaSourceManager; + private readonly IMediaEncoder _mediaEncoder; private readonly List _nonRendererUrls = new List(); private DateTime _lastRendererClear; - public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager) + public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) { _logger = logger; _sessionManager = sessionManager; @@ -51,6 +53,7 @@ namespace MediaBrowser.Dlna.PlayTo _userDataManager = userDataManager; _localization = localization; _mediaSourceManager = mediaSourceManager; + _mediaEncoder = mediaEncoder; } public void Start() @@ -132,7 +135,8 @@ namespace MediaBrowser.Dlna.PlayTo _userDataManager, _localization, _mediaSourceManager, - _config); + _config, + _mediaEncoder); controller.Init(device); diff --git a/MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs b/MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs index a9af85346d..8f3ad82ba4 100644 --- a/MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs +++ b/MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs @@ -1,5 +1,5 @@ -using System.Xml.Serialization; -using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dlna; +using System.Xml.Serialization; namespace MediaBrowser.Dlna.Profiles { @@ -10,8 +10,6 @@ namespace MediaBrowser.Dlna.Profiles { Name = "BubbleUPnp"; - TimelineOffsetSeconds = 5; - Identification = new DeviceIdentification { ModelName = "BubbleUPnp", @@ -27,16 +25,18 @@ namespace MediaBrowser.Dlna.Profiles new TranscodingProfile { Container = "mp3", - Type = DlnaProfileType.Audio, - AudioCodec = "mp3" + AudioCodec = "mp3", + Type = DlnaProfileType.Audio }, + new TranscodingProfile { Container = "ts", Type = DlnaProfileType.Video, - VideoCodec = "h264", - AudioCodec = "aac" + AudioCodec = "aac", + VideoCodec = "h264" }, + new TranscodingProfile { Container = "jpeg", @@ -48,21 +48,20 @@ namespace MediaBrowser.Dlna.Profiles { new DirectPlayProfile { - Container = "avi,mpeg,mkv,ts,mp4,mov,m4v,asf,webm,ogg,ogv,iso", + Container = "", Type = DlnaProfileType.Video }, new DirectPlayProfile { - Container = "mp3,flac,asf,off,oga,aac", + Container = "", Type = DlnaProfileType.Audio }, new DirectPlayProfile { + Container = "", Type = DlnaProfileType.Photo, - - Container = "jpeg,png,gif,bmp,tiff" } }; @@ -71,6 +70,77 @@ namespace MediaBrowser.Dlna.Profiles ContainerProfiles = new ContainerProfile[] { }; CodecProfiles = new CodecProfile[] { }; + + SubtitleProfiles = new[] + { + new SubtitleProfile + { + Format = "srt", + Method = SubtitleDeliveryMethod.External, + }, + + new SubtitleProfile + { + Format = "sub", + Method = SubtitleDeliveryMethod.External, + }, + + new SubtitleProfile + { + Format = "srt", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "ass", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "ssa", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "smi", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "dvdsub", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "pgs", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "pgssub", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "sub", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + } + }; } } } diff --git a/MediaBrowser.Dlna/Profiles/DefaultProfile.cs b/MediaBrowser.Dlna/Profiles/DefaultProfile.cs index 386a4eb1e9..76797c0e3a 100644 --- a/MediaBrowser.Dlna/Profiles/DefaultProfile.cs +++ b/MediaBrowser.Dlna/Profiles/DefaultProfile.cs @@ -30,8 +30,8 @@ namespace MediaBrowser.Dlna.Profiles MaxIconWidth = 48; MaxIconHeight = 48; - MaxStreamingBitrate = 15000000; - MaxStaticBitrate = 15000000; + MaxStreamingBitrate = 20000000; + MaxStaticBitrate = 20000000; MusicStreamingTranscodingBitrate = 192000; MusicSyncBitrate = 192000; diff --git a/MediaBrowser.Dlna/Profiles/VlcProfile.cs b/MediaBrowser.Dlna/Profiles/VlcProfile.cs index 5b3f7c0d1c..09d290f3ae 100644 --- a/MediaBrowser.Dlna/Profiles/VlcProfile.cs +++ b/MediaBrowser.Dlna/Profiles/VlcProfile.cs @@ -1,5 +1,5 @@ -using System.Xml.Serialization; -using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dlna; +using System.Xml.Serialization; namespace MediaBrowser.Dlna.Profiles { @@ -10,6 +10,7 @@ namespace MediaBrowser.Dlna.Profiles { Name = "Vlc"; + TimelineOffsetSeconds = 5; Identification = new DeviceIdentification @@ -27,16 +28,18 @@ namespace MediaBrowser.Dlna.Profiles new TranscodingProfile { Container = "mp3", - Type = DlnaProfileType.Audio, - AudioCodec = "mp3" + AudioCodec = "mp3", + Type = DlnaProfileType.Audio }, + new TranscodingProfile { Container = "ts", Type = DlnaProfileType.Video, - VideoCodec = "h264", - AudioCodec = "aac" + AudioCodec = "aac", + VideoCodec = "h264" }, + new TranscodingProfile { Container = "jpeg", @@ -48,21 +51,20 @@ namespace MediaBrowser.Dlna.Profiles { new DirectPlayProfile { - Container = "avi,mpeg,mkv,ts,mp4,mov,m4v,asf,webm,ogg,ogv,iso", + Container = "", Type = DlnaProfileType.Video }, new DirectPlayProfile { - Container = "mp3,flac,asf,off,oga,aac", + Container = "", Type = DlnaProfileType.Audio }, new DirectPlayProfile { + Container = "", Type = DlnaProfileType.Photo, - - Container = "jpeg,png,gif,bmp,tiff" } }; @@ -71,6 +73,77 @@ namespace MediaBrowser.Dlna.Profiles ContainerProfiles = new ContainerProfile[] { }; CodecProfiles = new CodecProfile[] { }; + + SubtitleProfiles = new[] + { + new SubtitleProfile + { + Format = "srt", + Method = SubtitleDeliveryMethod.External, + }, + + new SubtitleProfile + { + Format = "sub", + Method = SubtitleDeliveryMethod.External, + }, + + new SubtitleProfile + { + Format = "srt", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "ass", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "ssa", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "smi", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "dvdsub", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "pgs", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "pgssub", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + }, + + new SubtitleProfile + { + Format = "sub", + Method = SubtitleDeliveryMethod.Embed, + DidlMode = "", + } + }; } } } diff --git a/MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml b/MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml index 2b67b0eaff..38b7414549 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml @@ -22,30 +22,41 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 - 5 + 0 false false false false - - - + + + - - - + + + - + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Default.xml b/MediaBrowser.Dlna/Profiles/Xml/Default.xml index 779c800e56..9364f464b6 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Default.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Default.xml @@ -16,8 +16,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -33,9 +33,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml b/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml index 89f6c03c7d..5b8ff5d684 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml @@ -21,8 +21,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -37,9 +37,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml b/MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml index 437e6bdb43..561abe0e5c 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml @@ -22,8 +22,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -39,8 +39,8 @@ - - + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml b/MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml index 62cc5d81e8..b2d2676575 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -43,9 +43,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Kodi.xml b/MediaBrowser.Dlna/Profiles/Xml/Kodi.xml index 73cb54c707..d0985e1350 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Kodi.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Kodi.xml @@ -40,9 +40,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml index 9649b5e687..404bbbb206 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml @@ -22,8 +22,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -42,9 +42,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml b/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml index 102e05d93b..1e6db99b12 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml @@ -20,8 +20,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -37,9 +37,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml b/MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml index 588752d91f..679aa26bd6 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml @@ -22,8 +22,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -43,9 +43,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml index 20a112b672..256443093f 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -50,9 +50,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml b/MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml index b6b420ba2f..3d50b1724c 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml @@ -16,8 +16,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -38,9 +38,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml index 1f36923221..b09c25afa9 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml @@ -22,8 +22,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -50,9 +50,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml index 673affa70d..49f4759b98 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml @@ -22,8 +22,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -48,9 +48,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml index 8780229c52..41a996b663 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml @@ -24,8 +24,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -47,9 +47,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml index 1ea6276f88..ed66118d32 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -45,9 +45,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml index 012b6ca73e..88ff6047fd 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -48,9 +48,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml index 52a3e196c3..fb06ab0acf 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -50,9 +50,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml index f060f6ef48..67526f5f20 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -55,9 +55,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml index de418f8be7..8502377565 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -55,9 +55,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml index f147d75b62..11dc332398 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -45,9 +45,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml index bc83d488e1..5a763006b1 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -45,9 +45,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Vlc.xml b/MediaBrowser.Dlna/Profiles/Xml/Vlc.xml index 69ee8a002c..a007a4aa32 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Vlc.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Vlc.xml @@ -22,8 +22,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -35,17 +35,28 @@ false - - - + + + - - - + + + - + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml index 2c812f98c0..6f245202d4 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -51,9 +51,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml b/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml index 9dbefc0e47..bb937101d3 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml @@ -24,8 +24,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -46,9 +46,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml b/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml index ad08adf0d4..3816769442 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml @@ -23,8 +23,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -46,9 +46,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml b/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml index 12eba1e352..ebc5e83664 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml @@ -22,8 +22,8 @@ 480 48 48 - 15000000 - 15000000 + 20000000 + 20000000 192000 192000 DMS-1.50 @@ -43,9 +43,9 @@ - - - + + + diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs index a2eac41ec3..1eda79f022 100644 --- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs +++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs @@ -109,30 +109,26 @@ namespace MediaBrowser.Dlna.Ssdp var endPoint = new IPEndPoint(localIp, 1900); - var socket = GetMulticastSocket(localIp, endPoint); - - var receiveBuffer = new byte[64000]; - - CreateNotifier(localIp); - - while (!_tokenSource.IsCancellationRequested) + using (var socket = GetMulticastSocket(localIp, endPoint)) { - var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000); + var receiveBuffer = new byte[64000]; - if (receivedBytes > 0) + CreateNotifier(localIp); + + while (!_tokenSource.IsCancellationRequested) { - var args = SsdpHelper.ParseSsdpResponse(receiveBuffer); - args.EndPoint = endPoint; - args.LocalEndPoint = new IPEndPoint(localIp, 0); + var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000); - if (_ssdpHandler.IgnoreMessage(args, true)) + if (receivedBytes > 0) { - return; + var args = SsdpHelper.ParseSsdpResponse(receiveBuffer); + args.EndPoint = endPoint; + args.LocalEndPoint = new IPEndPoint(localIp, 0); + + _ssdpHandler.LogMessageReceived(args, true); + + TryCreateDevice(args); } - - _ssdpHandler.LogMessageReceived(args, true); - - TryCreateDevice(args); } } diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs index 3cdeb1afd9..0e791eb981 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs @@ -479,7 +479,7 @@ namespace MediaBrowser.Dlna.Ssdp var msg = new SsdpMessageBuilder().BuildMessage(header, values); - SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true, 1); + SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true, 2); //SendUnicastRequest(msg, 1); } diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index 4fc1d210d3..4b64295ea6 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -8,7 +8,7 @@ using CommonIO; namespace MediaBrowser.LocalMetadata { - public abstract class BaseXmlProvider : ILocalMetadataProvider, IHasChangeMonitor, IHasOrder + public abstract class BaseXmlProvider : ILocalMetadataProvider, IHasItemChangeMonitor, IHasOrder where T : IHasMetadata, new() { protected IFileSystem FileSystem; @@ -56,7 +56,7 @@ namespace MediaBrowser.LocalMetadata protected abstract FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService); - public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService) { var file = GetXmlFile(new ItemInfo(item), directoryService); @@ -65,7 +65,7 @@ namespace MediaBrowser.LocalMetadata return false; } - return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > date; + return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved; } public string Name diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs index 968d703bef..2d5225344e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs @@ -41,19 +41,36 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - const string vn = " -vn"; - var threads = GetNumberOfThreads(state, false); var inputModifier = GetInputModifier(state); - return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"", + var albumCoverInput = string.Empty; + var mapArgs = string.Empty; + var metadata = string.Empty; + var vn = string.Empty; + + if (!string.IsNullOrWhiteSpace(state.AlbumCoverPath)) + { + albumCoverInput = " -i \"" + state.AlbumCoverPath + "\""; + mapArgs = " -map 0:a -map 1:v -c:v copy"; + metadata = " -metadata:s:v title=\"Album cover\" -metadata:s:v comment=\"Cover(Front)\""; + } + else + { + vn = " -vn"; + } + + return string.Format("{0} {1}{6}{7} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{8} -y \"{5}\"", inputModifier, GetInputArgument(state), threads, vn, string.Join(" ", audioTranscodeParams.ToArray()), - state.OutputFilePath).Trim(); + state.OutputFilePath, + albumCoverInput, + mapArgs, + metadata).Trim(); } protected override string GetOutputFileExtension(EncodingJob state) diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index 263efbb629..d551d5c8c0 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -366,9 +366,14 @@ namespace MediaBrowser.MediaEncoding.Encoder /// System.String. protected string GetVideoDecoder(EncodingJob state) { - if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) + return null; + } + + if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) + { + if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { switch (state.MediaSource.VideoStream.Codec.ToLower()) { @@ -376,7 +381,8 @@ namespace MediaBrowser.MediaEncoding.Encoder case "h264": if (MediaEncoder.SupportsDecoder("h264_qsv")) { - return "-c:v h264_qsv "; + // Seeing stalls and failures with decoding. Not worth it compared to encoding. + //return "-c:v h264_qsv "; } break; case "mpeg2video": @@ -1002,7 +1008,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Boost volume to 200% when downsampling from 6ch to 2ch if (channels.HasValue && channels.Value <= 2) { - if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) + if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !GetEncodingOptions().DownMixAudioBoost.Equals(1)) { volParam = ",volume=" + GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index b23bd16f35..490a51128d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -64,6 +64,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public long? InputFileSize { get; set; } public string OutputAudioSync = "1"; public string OutputVideoSync = "vfr"; + public string AlbumCoverPath { get; set; } public string GetMimeType(string outputPath) { diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs index 070aae3a70..1544a78b66 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs @@ -60,6 +60,14 @@ namespace MediaBrowser.MediaEncoding.Encoder state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); + var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? + item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); + + if (primaryImage != null) + { + state.AlbumCoverPath = primaryImage.Path; + } + var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false); var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) @@ -575,6 +583,14 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } + if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value) + { + return false; + } + } + // If client is requesting a specific video profile, it must match the source if (!string.IsNullOrEmpty(request.Profile)) { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 01fb31d0c5..399fdead9c 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -96,15 +96,23 @@ namespace MediaBrowser.MediaEncoding.Encoder FFMpegPath = ffMpegPath; } + private List _encoders = new List(); public void SetAvailableEncoders(List list) { - + _encoders = list.ToList(); + //_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray())); } private List _decoders = new List(); public void SetAvailableDecoders(List list) { _decoders = list.ToList(); + //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); + } + + public bool SupportsEncoder(string decoder) + { + return _encoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); } public bool SupportsDecoder(string decoder) @@ -112,6 +120,20 @@ namespace MediaBrowser.MediaEncoding.Encoder return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); } + public bool CanEncodeToAudioCodec(string codec) + { + if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) + { + codec = "libopus"; + } + else if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) + { + codec = "libmp3lame"; + } + + return SupportsEncoder(codec); + } + /// /// Gets the encoder path. /// @@ -296,7 +318,7 @@ namespace MediaBrowser.MediaEncoding.Encoder formats.Contains("ts", StringComparer.OrdinalIgnoreCase) || formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) || formats.Contains("wtv", StringComparer.OrdinalIgnoreCase); - + // If it's mpeg based, assume true if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1) { diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs index b8efedf09d..3ab55168d5 100644 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs @@ -75,7 +75,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - Logger.Debug("Enabling h264_mp4toannexb due to nal_length_size of {0}", state.VideoStream.NalLengthSize); args += " -bsf:v h264_mp4toannexb"; } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 38528d8456..44c69d4c1b 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -411,6 +411,17 @@ namespace MediaBrowser.MediaEncoding.Probing NalLengthSize = streamInfo.nal_length_size }; + if (string.Equals(streamInfo.is_avc, "true", StringComparison.OrdinalIgnoreCase) || + string.Equals(streamInfo.is_avc, "1", StringComparison.OrdinalIgnoreCase)) + { + stream.IsAVC = true; + } + else if (string.Equals(streamInfo.is_avc, "false", StringComparison.OrdinalIgnoreCase) || + string.Equals(streamInfo.is_avc, "0", StringComparison.OrdinalIgnoreCase)) + { + stream.IsAVC = false; + } + // Filter out junk if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) { @@ -421,6 +432,7 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.Language = GetDictionaryValue(streamInfo.tags, "language"); stream.Comment = GetDictionaryValue(streamInfo.tags, "comment"); + stream.Title = GetDictionaryValue(streamInfo.tags, "title"); } if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) @@ -529,9 +541,25 @@ namespace MediaBrowser.MediaEncoding.Probing stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); } + NormalizeStreamTitle(stream); + return stream; } + private void NormalizeStreamTitle(MediaStream stream) + { + if (string.Equals(stream.Title, "sdh", StringComparison.OrdinalIgnoreCase) || + string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase)) + { + stream.Title = null; + } + + if (stream.Type == MediaStreamType.EmbeddedImage) + { + stream.Title = null; + } + } + /// /// Gets a string from an FFProbeResult tags dictionary /// diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 54fdc64005..ba4d1b59e8 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -662,9 +662,6 @@ FileOrganization\TvFileOrganizationOptions.cs - - Games\GameSystem.cs - Globalization\CountryInfo.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 473186a46e..f5d538cc47 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -636,9 +636,6 @@ FileOrganization\TvFileOrganizationOptions.cs - - Games\GameSystem.cs - Globalization\CountryInfo.cs diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index ba3362332e..b115440d60 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -201,6 +201,7 @@ namespace MediaBrowser.Model.Configuration public string[] Migrations { get; set; } public int MigrationVersion { get; set; } + public int SchemaVersion { get; set; } public bool DownloadImagesInAdvance { get; set; } diff --git a/MediaBrowser.Model/Connect/ConnectUser.cs b/MediaBrowser.Model/Connect/ConnectUser.cs index 383261a6b7..da290da120 100644 --- a/MediaBrowser.Model/Connect/ConnectUser.cs +++ b/MediaBrowser.Model/Connect/ConnectUser.cs @@ -8,6 +8,5 @@ namespace MediaBrowser.Model.Connect public string Email { get; set; } public bool IsActive { get; set; } public string ImageUrl { get; set; } - public bool IsSupporter { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/ILocalPlayer.cs b/MediaBrowser.Model/Dlna/ILocalPlayer.cs index 55e11ec4b7..9de360023e 100644 --- a/MediaBrowser.Model/Dlna/ILocalPlayer.cs +++ b/MediaBrowser.Model/Dlna/ILocalPlayer.cs @@ -23,4 +23,17 @@ namespace MediaBrowser.Model.Dlna /// true if this instance [can access URL] the specified URL; otherwise, false. bool CanAccessUrl(string url, bool requiresCustomRequestHeaders); } + + public interface ITranscoderSupport + { + bool CanEncodeToAudioCodec(string codec); + } + + public class FullTranscoderSupport : ITranscoderSupport + { + public bool CanEncodeToAudioCodec(string codec) + { + return true; + } + } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 1e6b7c729d..7721bfd150 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -13,15 +13,27 @@ namespace MediaBrowser.Model.Dlna { private readonly ILocalPlayer _localPlayer; private readonly ILogger _logger; + private readonly ITranscoderSupport _transcoderSupport; - public StreamBuilder(ILocalPlayer localPlayer, ILogger logger) + public StreamBuilder(ILocalPlayer localPlayer, ITranscoderSupport transcoderSupport, ILogger logger) { + _transcoderSupport = transcoderSupport; _localPlayer = localPlayer; _logger = logger; } + public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger) + : this(new NullLocalPlayer(), transcoderSupport, logger) + { + } + + public StreamBuilder(ILocalPlayer localPlayer, ILogger logger) + : this(localPlayer, new FullTranscoderSupport(), logger) + { + } + public StreamBuilder(ILogger logger) - : this(new NullLocalPlayer(), logger) + : this(new NullLocalPlayer(), new FullTranscoderSupport(), logger) { } @@ -185,8 +197,11 @@ namespace MediaBrowser.Model.Dlna { if (i.Type == playlistItem.MediaType && i.Context == options.Context) { - transcodingProfile = i; - break; + if (_transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container)) + { + transcodingProfile = i; + break; + } } } @@ -444,6 +459,15 @@ namespace MediaBrowser.Model.Dlna playlistItem.VideoCodec = transcodingProfile.VideoCodec; playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps; playlistItem.ForceLiveStream = transcodingProfile.ForceLiveStream; + + if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels)) + { + int transcodingMaxAudioChannels; + if (IntHelper.TryParseCultureInvariant(transcodingProfile.MaxAudioChannels, out transcodingMaxAudioChannels)) + { + playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels; + } + } playlistItem.SubProtocol = transcodingProfile.Protocol; playlistItem.AudioStreamIndex = audioStreamIndex; @@ -1038,6 +1062,18 @@ namespace MediaBrowser.Model.Dlna } } + // Check audio codec + List audioCodecs = profile.GetAudioCodecs(); + if (audioCodecs.Count > 0) + { + // Check audio codecs + string audioCodec = audioStream == null ? null : audioStream.Codec; + if (string.IsNullOrEmpty(audioCodec) || !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec)) + { + return false; + } + } + return true; } @@ -1073,6 +1109,7 @@ namespace MediaBrowser.Model.Dlna } } + // Check audio codec List audioCodecs = profile.GetAudioCodecs(); if (audioCodecs.Count > 0) { diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 644f732a74..313c30e2c5 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -38,6 +38,7 @@ namespace MediaBrowser.Model.Dlna public int? SubtitleStreamIndex { get; set; } + public int? TranscodingMaxAudioChannels { get; set; } public int? MaxAudioChannels { get; set; } public int? AudioBitrate { get; set; } @@ -237,7 +238,9 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString().ToLower())); list.Add(new NameValuePair("ForceLiveStream", item.ForceLiveStream.ToString().ToLower())); list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty)); - + + list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.TranscodingMaxAudioChannels.Value) : string.Empty)); + return list; } diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index bf6bf092f3..d1314c17bf 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("protocol")] public string Protocol { get; set; } - + [XmlAttribute("estimateContentLength")] public bool EstimateContentLength { get; set; } @@ -38,6 +38,9 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("forceLiveStream")] public bool ForceLiveStream { get; set; } + [XmlAttribute("maxAudioChannels")] + public string MaxAudioChannels { get; set; } + public List GetAudioCodecs() { List list = new List(); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 7928d0bf94..146fcc74e8 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -26,6 +26,8 @@ namespace MediaBrowser.Model.Dto /// The name. public string Name { get; set; } + public string OriginalTitle { get; set; } + /// /// Gets or sets the server identifier. /// @@ -295,7 +297,8 @@ namespace MediaBrowser.Model.Dto /// /// The number. public string Number { get; set; } - + public string ChannelNumber { get; set; } + /// /// Gets or sets the index number. /// diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index fa7a51291c..9b814c5ccc 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Model.Dlna; +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Extensions; using System.Diagnostics; @@ -34,6 +36,91 @@ namespace MediaBrowser.Model.Entities /// The comment. public string Comment { get; set; } + public string Title { get; set; } + + public string DisplayTitle + { + get + { + if (!string.IsNullOrEmpty(Title)) + { + return Title; + } + + if (Type == MediaStreamType.Audio) + { + List attributes = new List(); + + if (!string.IsNullOrEmpty(Language)) + { + attributes.Add(Language); + } + if (!string.IsNullOrEmpty(Codec) && !StringHelper.EqualsIgnoreCase(Codec, "dca")) + { + attributes.Add(Codec); + } + if (!string.IsNullOrEmpty(Profile) && !StringHelper.EqualsIgnoreCase(Profile, "lc")) + { + attributes.Add(Profile); + } + + if (Channels.HasValue) + { + attributes.Add(StringHelper.ToStringCultureInvariant(Channels.Value) + " ch"); + } + + string name = string.Join(" ", attributes.ToArray()); + + if (IsDefault) + { + name += " (D)"; + } + + return name; + } + + if (Type == MediaStreamType.Subtitle) + { + List attributes = new List(); + + if (!string.IsNullOrEmpty(Language)) + { + attributes.Add(Language); + } + if (!string.IsNullOrEmpty(Codec)) + { + attributes.Add(Codec); + } + + string name = string.Join(" ", attributes.ToArray()); + + if (IsDefault) + { + name += " (D)"; + } + + if (IsForced) + { + name += " (F)"; + } + + if (IsExternal) + { + name += " (EXT)"; + } + + return name; + } + + if (Type == MediaStreamType.Video) + { + + } + + return null; + } + } + public string NalLengthSize { get; set; } /// @@ -42,6 +129,8 @@ namespace MediaBrowser.Model.Entities /// true if this instance is interlaced; otherwise, false. public bool IsInterlaced { get; set; } + public bool? IsAVC { get; set; } + /// /// Gets or sets the channel layout. /// diff --git a/MediaBrowser.Model/Games/GameSystem.cs b/MediaBrowser.Model/Games/GameSystem.cs deleted file mode 100644 index ab8f1efa75..0000000000 --- a/MediaBrowser.Model/Games/GameSystem.cs +++ /dev/null @@ -1,48 +0,0 @@ - -namespace MediaBrowser.Model.Games -{ - public class GameSystem - { - public const string Nintendo = "Nintendo"; - public const string SuperNintendo = "Super Nintendo"; - public const string Panasonic3DO = "3DO"; - public const string Amiga = "Amiga"; - public const string Arcade = "Arcade"; - public const string Atari2600 = "Atari 2600"; - public const string Atari5200 = "Atari 5200"; - public const string Atari7800 = "Atari 7800"; - public const string AtariXE = "Atari XE"; - public const string AtariJaguar = "Atari Jaguar"; - public const string AtariJaguarCD = "Atari Jaguar CD"; - public const string Colecovision = "Colecovision"; - public const string Commodore64 = "Commodore 64"; - public const string CommodoreVic20 = "Commodore Vic-20"; - public const string Intellivision = "Intellivision"; - public const string MicrosoftXBox = "Xbox"; - public const string NeoGeo = "Neo Geo"; - public const string Nintendo64 = "Nintendo 64"; - public const string NintendoDS = "Nintendo DS"; - public const string NintendoGameBoy = "Game Boy"; - public const string NintendoGameBoyAdvance = "Game Boy Advance"; - public const string NintendoGameBoyColor = "Game Boy Color"; - public const string NintendoGameCube = "Gamecube"; - public const string VirtualBoy = "Virtual Boy"; - public const string Wii = "Nintendo Wii"; - public const string DOS = "DOS"; - public const string Windows = "Windows"; - public const string Sega32X = "Sega 32X"; - public const string SegaCD = "Sega CD"; - public const string SegaDreamcast = "Dreamcast"; - public const string SegaGameGear = "Game Gear"; - public const string SegaGenesis = "Sega Genesis"; - public const string SegaMasterSystem = "Sega Master System"; - public const string SegaMegaDrive = "Sega Mega Drive"; - public const string SegaSaturn = "Sega Saturn"; - public const string SonyPlaystation = "Sony Playstation"; - public const string SonyPlaystation2 = "PS2"; - public const string SonyPSP = "PSP"; - public const string TurboGrafx16 = "TurboGrafx 16"; - public const string TurboGrafxCD = "TurboGrafx CD"; - public const string ZxSpectrum = "ZX Spectrum"; - } -} diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 46f630fe04..4211fbd59f 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -7,8 +7,12 @@ namespace MediaBrowser.Model.LiveTv public int? GuideDays { get; set; } public bool EnableMovieProviders { get; set; } public string RecordingPath { get; set; } + public string MovieRecordingPath { get; set; } + public string SeriesRecordingPath { get; set; } public bool EnableAutoOrganize { get; set; } public bool EnableRecordingEncoding { get; set; } + public bool EnableRecordingSubfolders { get; set; } + public bool EnableOriginalAudioWithEncodedRecordings { get; set; } public List TunerHosts { get; set; } public List ListingProviders { get; set; } @@ -19,6 +23,7 @@ namespace MediaBrowser.Model.LiveTv public LiveTvOptions() { EnableMovieProviders = true; + EnableRecordingSubfolders = true; TunerHosts = new List(); ListingProviders = new List(); } diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs index 7a877e3561..0141191c18 100644 --- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs @@ -14,8 +14,11 @@ namespace MediaBrowser.Model.LiveTv ChannelIds = new string[] { }; SortBy = new string[] { }; Genres = new string[] { }; + EnableTotalRecordCount = true; } + public bool EnableTotalRecordCount { get; set; } + /// /// Fields to return within the items, in addition to basic information /// diff --git a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs index e83a8fda65..0e6d081a1d 100644 --- a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs @@ -13,7 +13,14 @@ namespace MediaBrowser.Model.LiveTv public bool? EnableImages { get; set; } public int? ImageTypeLimit { get; set; } public ImageType[] EnableImageTypes { get; set; } - + + public bool EnableTotalRecordCount { get; set; } + + public RecommendedProgramQuery() + { + EnableTotalRecordCount = true; + } + /// /// Gets or sets the user identifier. /// diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 41952963c6..7c469b9fb9 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -287,7 +287,6 @@ - diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 1540f178a4..cea638a395 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -130,6 +130,8 @@ /// Metascore, + OriginalTitle, + /// /// The item overview /// diff --git a/MediaBrowser.Model/Sync/SyncJobItem.cs b/MediaBrowser.Model/Sync/SyncJobItem.cs index 77464be58d..1c72ccd523 100644 --- a/MediaBrowser.Model/Sync/SyncJobItem.cs +++ b/MediaBrowser.Model/Sync/SyncJobItem.cs @@ -102,6 +102,8 @@ namespace MediaBrowser.Model.Sync /// The index of the job item. public int JobItemIndex { get; set; } + public long ItemDateModifiedTicks { get; set; } + public SyncJobItem() { AdditionalFiles = new List(); diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs index ff3d5a5b24..b3e73740d9 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.BoxSets { var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; return GetImages(mainResult, language, tmdbImageUrl); } diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs index 2cac9b0638..ab05c959e8 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.BoxSets { public class MovieDbBoxSetProvider : IRemoteMetadataProvider { - private const string GetCollectionInfo3 = @"http://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images"; + private const string GetCollectionInfo3 = @"https://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images"; internal static MovieDbBoxSetProvider Current; @@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.BoxSets var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; var result = new RemoteSearchResult { diff --git a/MediaBrowser.Providers/Folders/DefaultImageProvider.cs b/MediaBrowser.Providers/Folders/DefaultImageProvider.cs index afd8c583d8..08b4f64610 100644 --- a/MediaBrowser.Providers/Folders/DefaultImageProvider.cs +++ b/MediaBrowser.Providers/Folders/DefaultImageProvider.cs @@ -117,7 +117,7 @@ namespace MediaBrowser.Providers.Folders } if (string.IsNullOrWhiteSpace(viewType)) { - return urlPrefix + "generic.jpg"; + //return urlPrefix + "generic.jpg"; } return null; diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index eeec4ea56a..a0c6bd889a 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -295,7 +295,12 @@ namespace MediaBrowser.Providers.Manager return true; } - if (item is BoxSet || item is IItemByName || item is Playlist) + if (!(item is Audio) && !(item is Video)) + { + return true; + } + + if (item is IItemByName) { return true; } @@ -305,12 +310,7 @@ namespace MediaBrowser.Providers.Manager return true; } - if (item is ICollectionFolder) - { - return true; - } - - if (!(item is Audio) && !(item is Video)) + if (item is MusicVideo) { return true; } @@ -395,7 +395,6 @@ namespace MediaBrowser.Providers.Manager return _cachedTask; } - private readonly Task _cachedResult = Task.FromResult(ItemUpdateType.None); /// /// Befores the save. /// @@ -403,9 +402,60 @@ namespace MediaBrowser.Providers.Manager /// if set to true [is full refresh]. /// Type of the current update. /// ItemUpdateType. - protected virtual Task BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected virtual async Task BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) { - return _cachedResult; + var updateType = ItemUpdateType.None; + + updateType |= SaveCumulativeRunTimeTicks(item, isFullRefresh, currentUpdateType); + updateType |= SaveDateLastMediaAdded(item, isFullRefresh, currentUpdateType); + + return updateType; + } + + private ItemUpdateType SaveCumulativeRunTimeTicks(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = ItemUpdateType.None; + + if (isFullRefresh || currentUpdateType > ItemUpdateType.None) + { + var folder = item as Folder; + if (folder != null && folder.SupportsCumulativeRunTimeTicks) + { + var items = folder.GetRecursiveChildren(i => !i.IsFolder).ToList(); + var ticks = items.Select(i => i.RunTimeTicks ?? 0).Sum(); + + if (!folder.RunTimeTicks.HasValue || folder.RunTimeTicks.Value != ticks) + { + folder.RunTimeTicks = ticks; + updateType = ItemUpdateType.MetadataEdit; + } + } + } + + return updateType; + } + + private ItemUpdateType SaveDateLastMediaAdded(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = ItemUpdateType.None; + + if (isFullRefresh || currentUpdateType > ItemUpdateType.None) + { + var folder = item as Folder; + if (folder != null && folder.SupportsDateLastMediaAdded) + { + var items = folder.GetRecursiveChildren(i => !i.IsFolder).Select(i => i.DateCreated).ToList(); + var date = items.Count == 0 ? (DateTime?)null : items.Max(); + + if ((!folder.DateLastMediaAdded.HasValue && date.HasValue) || folder.DateLastMediaAdded != date) + { + folder.DateLastMediaAdded = date; + updateType = ItemUpdateType.MetadataEdit; + } + } + } + + return updateType; } /// @@ -425,7 +475,7 @@ namespace MediaBrowser.Providers.Manager : status.DateLastMetadataRefresh ?? default(DateTime); // Run all if either of these flags are true - var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || dateLastRefresh == default(DateTime); + var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || dateLastRefresh == default(DateTime) || item.RequiresRefresh(); if (!runAllProviders) { @@ -435,18 +485,18 @@ namespace MediaBrowser.Providers.Manager var providersWithChanges = providers .Where(i => { - var hasChangeMonitor = i as IHasChangeMonitor; - if (hasChangeMonitor != null) - { - return HasChanged(item, hasChangeMonitor, currentItem.DateLastSaved, options.DirectoryService); - } - var hasFileChangeMonitor = i as IHasItemChangeMonitor; if (hasFileChangeMonitor != null) { return HasChanged(item, hasFileChangeMonitor, options.DirectoryService); } + var hasChangeMonitor = i as IHasChangeMonitor; + if (hasChangeMonitor != null) + { + return HasChanged(item, hasChangeMonitor, currentItem.DateLastSaved, options.DirectoryService); + } + return false; }) .ToList(); diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 278d8ed710..59a2da4603 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -40,6 +40,15 @@ namespace MediaBrowser.Providers.Manager } } + if (replaceData || string.IsNullOrEmpty(target.OriginalTitle)) + { + // Safeguard against incoming data having an emtpy name + if (!string.IsNullOrWhiteSpace(source.OriginalTitle)) + { + target.OriginalTitle = source.OriginalTitle; + } + } + if (replaceData || !target.CommunityRating.HasValue) { target.CommunityRating = source.CommunityRating; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 9db4ab96e9..b89b0b9256 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -30,6 +30,7 @@ namespace MediaBrowser.Providers.MediaInfo ICustomMetadataProvider, ICustomMetadataProvider, ICustomMetadataProvider, + ICustomMetadataProvider, ICustomMetadataProvider