From ddbbe9ce4e054565272a69e1ea709f8df4d96479 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 1 Apr 2015 00:23:34 -0400 Subject: [PATCH 01/54] post release housecleaning --- MediaBrowser.Api/ApiEntryPoint.cs | 2 +- MediaBrowser.Api/ConfigurationService.cs | 2 +- .../Playback/TranscodingThrottler.cs | 2 +- MediaBrowser.Controller/Channels/Channel.cs | 15 ++-------- MediaBrowser.Controller/Entities/BaseItem.cs | 29 ------------------- MediaBrowser.Controller/Entities/Folder.cs | 17 ++--------- .../Entities/LinkedChild.cs | 3 -- .../Providers/BaseItemXmlParser.cs | 14 +-------- MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 2 +- .../Savers/XmlSaverHelpers.cs | 5 ---- .../Configuration/ServerConfiguration.cs | 5 ++-- .../Configuration/UserConfiguration.cs | 7 ----- MediaBrowser.Model/Dlna/StreamInfo.cs | 5 ++++ .../Notifications/NotificationOptions.cs | 5 ++-- MediaBrowser.Model/Users/UserPolicy.cs | 2 -- .../HttpServer/Security/AuthService.cs | 2 +- .../Library/UserManager.cs | 26 ++--------------- .../Notifications/NotificationManager.cs | 2 +- .../FFMpeg/FFMpegDownloadInfo.cs | 18 ++++++------ .../Migrations/RenameXmlOptions.cs | 7 ++++- .../Savers/BaseNfoSaver.cs | 10 ------- 21 files changed, 39 insertions(+), 141 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 08ac5671d5..0db56e299f 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -296,7 +296,7 @@ namespace MediaBrowser.Api // TODO: Lower this hls timeout var timerDuration = job.Type == TranscodingJobType.Progressive ? 1000 : - 7200000; + 1800000; if (job.KillTimer == null) { diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index d0abd18c28..9f6c07dd21 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Api public void Post(AutoSetMetadataOptions request) { - _configurationManager.DisableMetadataService("Media Browser Xml"); + _configurationManager.DisableMetadataService("Emby Xml"); _configurationManager.SaveConfiguration(); } diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs index 58cfa086e3..ff79bb48f4 100644 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback var options = GetOptions(); - if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) + if (/*options.EnableThrottling &&*/ IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) { PauseTranscoding(); } diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index b6514ca0a6..f746d87fff 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Channels { @@ -15,19 +14,9 @@ namespace MediaBrowser.Controller.Channels public override bool IsVisible(User user) { - if (user.Policy.BlockedChannels != null) + if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) { - if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - else - { - if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } + return false; } return base.IsVisible(user); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cdb52ec668..94fc761253 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1219,18 +1219,6 @@ namespace MediaBrowser.Controller.Entities private BaseItem FindLinkedChild(LinkedChild info) { - if (!string.IsNullOrWhiteSpace(info.ItemName)) - { - if (string.Equals(info.ItemType, "musicgenre", StringComparison.OrdinalIgnoreCase)) - { - return LibraryManager.GetMusicGenre(info.ItemName); - } - if (string.Equals(info.ItemType, "musicartist", StringComparison.OrdinalIgnoreCase)) - { - return LibraryManager.GetArtist(info.ItemName); - } - } - if (!string.IsNullOrEmpty(info.Path)) { var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); @@ -1243,23 +1231,6 @@ namespace MediaBrowser.Controller.Entities return itemByPath; } - if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType)) - { - return LibraryManager.RootFolder.GetRecursiveChildren(i => - { - if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - - }).FirstOrDefault(); - } - return null; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index cffc0989a5..14095f7ffc 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -334,22 +334,9 @@ namespace MediaBrowser.Controller.Entities { if (this is ICollectionFolder && !(this is BasePluginFolder)) { - if (user.Policy.BlockedMediaFolders != null) + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) { - if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) || - - // Backwards compatibility - user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - else - { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } + return false; } } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 949c9741b1..ac13657b94 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -9,9 +9,6 @@ namespace MediaBrowser.Controller.Entities public string Path { get; set; } public LinkedChildType Type { get; set; } - public string ItemName { get; set; } - public string ItemType { get; set; } - [IgnoreDataMember] public string Id { get; set; } diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 6facc1074a..13f83c0fc9 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -1404,24 +1404,12 @@ namespace MediaBrowser.Controller.Providers { switch (reader.Name) { - case "Name": - { - linkedItem.ItemName = reader.ReadElementContentAsString(); - break; - } - case "Path": { linkedItem.Path = reader.ReadElementContentAsString(); break; } - case "Type": - { - linkedItem.ItemType = reader.ReadElementContentAsString(); - break; - } - default: reader.Skip(); break; @@ -1435,7 +1423,7 @@ namespace MediaBrowser.Controller.Providers return linkedItem; } - return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; + return null; } diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index afe4b5799b..154d026008 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.LocalMetadata { get { - return "Media Browser Xml"; + return "Emby Xml"; } } diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs index c59d574bf4..1b98e75bef 100644 --- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs @@ -756,11 +756,6 @@ namespace MediaBrowser.LocalMetadata.Savers { builder.Append("<" + singularNodeName + ">"); - if (!string.IsNullOrWhiteSpace(link.ItemType)) - { - builder.Append("" + SecurityElement.Escape(link.ItemType) + ""); - } - if (!string.IsNullOrWhiteSpace(link.Path)) { builder.Append("" + SecurityElement.Escape((link.Path)) + ""); diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index c06aedb500..bc167333ae 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -200,7 +200,7 @@ namespace MediaBrowser.Model.Configuration public PeopleMetadataOptions PeopleMetadataOptions { get; set; } public bool FindInternetTrailers { get; set; } - public string[] InsecureApps8 { get; set; } + public string[] InsecureApps9 { get; set; } public bool SaveMetadataHidden { get; set; } @@ -257,7 +257,7 @@ namespace MediaBrowser.Model.Configuration PeopleMetadataOptions = new PeopleMetadataOptions(); - InsecureApps8 = new[] + InsecureApps9 = new[] { "Chromecast", "iOS", @@ -266,7 +266,6 @@ namespace MediaBrowser.Model.Configuration "Media Portal", "iPad", "iPhone", - "Roku", "Windows Phone" }; diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 98641636ae..a361da18b9 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -6,12 +6,6 @@ namespace MediaBrowser.Model.Configuration /// public class UserConfiguration { - /// - /// Gets or sets a value indicating whether this instance is administrator. - /// - /// true if this instance is administrator; otherwise, false. - public bool IsAdministrator { get; set; } - /// /// Gets or sets the audio language preference. /// @@ -53,7 +47,6 @@ namespace MediaBrowser.Model.Configuration public string[] LatestItemsExcludes { get; set; } - public bool HasMigratedToPolicy { get; set; } public bool HidePlayedInLatest { get; set; } /// diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 3977a9ab07..c3e868cd49 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -217,6 +217,11 @@ namespace MediaBrowser.Model.Dlna return list; } + public List GetExternalSubtitles(bool includeSelectedTrackOnly, string baseUrl, string accessToken) + { + return GetExternalSubtitles(includeSelectedTrackOnly, false, baseUrl, accessToken); + } + public List GetExternalSubtitles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) { List list = GetSubtitleProfiles(includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken); diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index e57955c9e2..683f1a76c8 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -1,5 +1,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Users; namespace MediaBrowser.Model.Notifications { @@ -106,7 +107,7 @@ namespace MediaBrowser.Model.Notifications !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId); } - public bool IsEnabledToSendToUser(string type, string userId, UserConfiguration userConfig) + public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy) { NotificationOption opt = GetOptions(type); @@ -117,7 +118,7 @@ namespace MediaBrowser.Model.Notifications return true; } - if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator) + if (opt.SendToUserMode == SendToUserType.Admins && userPolicy.IsAdministrator) { return true; } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 7efc2cf6f6..738f5bffa9 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -32,8 +32,6 @@ namespace MediaBrowser.Model.Users public bool EnableUserPreferenceAccess { get; set; } public AccessSchedule[] AccessSchedules { get; set; } public UnratedItem[] BlockUnratedItems { get; set; } - public string[] BlockedMediaFolders { get; set; } - public string[] BlockedChannels { get; set; } public bool EnableRemoteControlOfOtherUsers { get; set; } public bool EnableSharedDeviceControl { get; set; } diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 3903c62b1e..b4da407026 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security return true; } - return _config.Configuration.InsecureApps8.Contains(auth.Client ?? string.Empty, + return _config.Configuration.InsecureApps9.Contains(auth.Client ?? string.Empty, StringComparer.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index b101f6ae1a..03471a8e9b 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -166,11 +166,6 @@ namespace MediaBrowser.Server.Implementations.Library var users = Users.ToList(); - foreach (var user in users) - { - await DoPolicyMigration(user).ConfigureAwait(false); - } - // If there are no local users with admin rights, make them all admins if (!users.Any(i => i.Policy.IsAdministrator)) { @@ -286,10 +281,10 @@ namespace MediaBrowser.Server.Implementations.Library if (newValue >= maxCount) { - //_logger.Debug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); - //user.Policy.IsDisabled = true; + _logger.Debug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); + user.Policy.IsDisabled = true; - //fireLockout = true; + fireLockout = true; } await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false); @@ -366,19 +361,6 @@ namespace MediaBrowser.Server.Implementations.Library return users; } - private async Task DoPolicyMigration(User user) - { - if (!user.Configuration.HasMigratedToPolicy) - { - user.Policy.IsAdministrator = user.Configuration.IsAdministrator; - - await UpdateUserPolicy(user, user.Policy, false); - - user.Configuration.HasMigratedToPolicy = true; - await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false); - } - } - public UserDto GetUserDto(User user, string remoteEndPoint = null) { if (user == null) @@ -953,8 +935,6 @@ namespace MediaBrowser.Server.Implementations.Library user.Policy = userPolicy; } - user.Configuration.IsAdministrator = user.Policy.IsAdministrator; - await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false); } diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs index 5e02b2bacb..1ff928cd5b 100644 --- a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs +++ b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs @@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Notifications var config = GetConfiguration(); return _userManager.Users - .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Configuration)) + .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy)) .Select(i => i.Id.ToString("N")); } diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs index cabb8dc839..4d2ed067be 100644 --- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs +++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg var info = new FFMpegDownloadInfo(); // Windows builds: http://ffmpeg.zeranoe.com/builds/ - // Linux builds: http://ffmpeg.gusari.org/static/ + // Linux builds: http://johnvansickle.com/ffmpeg/ // OS X builds: http://ffmpegmac.net/ // OS X x64: http://www.evermeet.cx/ffmpeg/ @@ -33,7 +33,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg case OperatingSystem.Linux: info.ArchiveType = "7z"; - info.Version = "20150124"; + info.Version = "20150331"; break; case OperatingSystem.Osx: @@ -54,7 +54,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg info.FFMpegFilename = "ffmpeg.exe"; info.FFProbeFilename = "ffprobe.exe"; - info.Version = "20150110"; + info.Version = "20150331"; info.ArchiveType = "7z"; switch (environment.SystemArchitecture) @@ -83,14 +83,14 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg case Architecture.X86_X64: return new[] { - "http://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20150110-git-4df01d5-win64-static.7z", - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20150110-git-4df01d5-win64-static.7z" + "http://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20150331-git-5cba529-win64-static.7z", + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20150331-git-5cba529-win64-static.7z" }; case Architecture.X86: return new[] { - "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20150110-git-4df01d5-win32-static.7z", - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20150110-git-4df01d5-win32-static.7z" + "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20150331-git-5cba529-win32-static.7z", + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20150331-git-5cba529-win32-static.7z" }; } break; @@ -119,12 +119,12 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg case Architecture.X86_X64: return new[] { - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg-2.5.3-64bit-static.7z" + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg-2.6.1-64bit-static.7z" }; case Architecture.X86: return new[] { - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg-2.5.3-32bit-static.7z" + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg-2.6.1-32bit-static.7z" }; } break; diff --git a/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs b/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs index be8ae2f814..49114b96f3 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs @@ -44,7 +44,12 @@ namespace MediaBrowser.Server.Startup.Common.Migrations { if (string.Equals(options[i], "Media Browser Legacy Xml", StringComparison.OrdinalIgnoreCase)) { - options[i] = "Media Browser Xml"; + options[i] = "Emby Xml"; + changed = true; + } + else if (string.Equals(options[i], "Media Browser Xml", StringComparison.OrdinalIgnoreCase)) + { + options[i] = "Emby Xml"; changed = true; } } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 5c4319d228..9cde958c46 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -856,16 +856,6 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteStartElement("collectionitem"); - if (!string.IsNullOrWhiteSpace(link.ItemName)) - { - writer.WriteElementString("name", link.ItemName); - } - - if (!string.IsNullOrWhiteSpace(link.ItemType)) - { - writer.WriteElementString("type", link.ItemType); - } - if (!string.IsNullOrWhiteSpace(link.Path)) { writer.WriteElementString("path", link.Path); From b9f3944b7f2cbfba1dadffd0ac0594ad3cd81f38 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 1 Apr 2015 17:56:32 -0400 Subject: [PATCH 02/54] rework media source methods --- .../Library/IMediaSourceManager.cs | 10 +------ MediaBrowser.Dlna/Didl/DidlBuilder.cs | 4 +-- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 2 +- .../Library/MediaSourceManager.cs | 29 ++++--------------- SharedVersion.cs | 4 +-- 5 files changed, 12 insertions(+), 37 deletions(-) diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 9cbbabc8db..5bcc5f3136 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -63,15 +63,7 @@ namespace MediaBrowser.Controller.Library /// if set to true [enable path substitution]. /// The user. /// IEnumerable<MediaSourceInfo>. - IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user); - - /// - /// Gets the static media sources. - /// - /// The item. - /// if set to true [enable path substitution]. - /// IEnumerable<MediaSourceInfo>. - IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution); + IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null); /// /// Gets the static media source. diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 3b1cdb5428..19dab2246d 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -124,7 +124,7 @@ namespace MediaBrowser.Dlna.Didl { if (streamInfo == null) { - var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(video, true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); + var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions { @@ -351,7 +351,7 @@ namespace MediaBrowser.Dlna.Didl if (streamInfo == null) { - var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(audio, true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); + var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions { diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 38c0f71cc7..5b129243c4 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -470,7 +470,7 @@ namespace MediaBrowser.Dlna.PlayTo var hasMediaSources = item as IHasMediaSources; var mediaSources = hasMediaSources != null - ? (user == null ? _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true) : _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList() + ? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList() : new List(); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 4fab95263f..27a7d4ea97 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -235,7 +235,7 @@ namespace MediaBrowser.Server.Implementations.Library return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - public IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution) + public IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null) { if (item == null) { @@ -247,31 +247,14 @@ namespace MediaBrowser.Server.Implementations.Library return item.GetMediaSources(enablePathSubstitution); } - return item.GetMediaSources(enablePathSubstitution); - } - - public IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - if (!(item is Video)) - { - return item.GetMediaSources(enablePathSubstitution); - } - - if (user == null) - { - throw new ArgumentNullException("user"); - } - var sources = item.GetMediaSources(enablePathSubstitution).ToList(); - foreach (var source in sources) + if (user != null) { - SetUserProperties(source, user); + foreach (var source in sources) + { + SetUserProperties(source, user); + } } return sources; diff --git a/SharedVersion.cs b/SharedVersion.cs index e530e59cb7..11c0c6b5e6 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -//[assembly: AssemblyVersion("3.0.*")] -[assembly: AssemblyVersion("3.0.5569.0")] +[assembly: AssemblyVersion("3.0.*")] +//[assembly: AssemblyVersion("3.0.5569.0")] From 967751d2a59db10dd91aa3cf31b0922a19269b5c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 1 Apr 2015 21:34:14 -0400 Subject: [PATCH 03/54] rework hls timer --- MediaBrowser.Api/ApiEntryPoint.cs | 58 +++++++++++++++---- .../UserLibrary/PlaystateService.cs | 10 ++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 0db56e299f..ed5fa5bfd7 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -286,27 +286,65 @@ namespace MediaBrowser.Api job.DisposeKillTimer(); } - + public void OnTranscodeEndRequest(TranscodingJob job) { job.ActiveRequestCount--; if (job.ActiveRequestCount == 0) { - // TODO: Lower this hls timeout - var timerDuration = job.Type == TranscodingJobType.Progressive ? - 1000 : - 1800000; + PingTimer(job, true); + } + } + internal void PingTranscodingJob(string deviceId, string playSessionId) + { + if (string.IsNullOrEmpty(deviceId)) + { + throw new ArgumentNullException("deviceId"); + } - if (job.KillTimer == null) + var jobs = new List(); + + lock (_activeTranscodingJobs) + { + // This is really only needed for HLS. + // Progressive streams can stop on their own reliably + jobs = jobs.Where(j => { - job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); - } - else + if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)) + { + return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase); + } + + return false; + + }).ToList(); + } + + foreach (var job in jobs) + { + PingTimer(job, false); + } + } + + private void PingTimer(TranscodingJob job, bool startTimerIfNeeded) + { + // TODO: Lower this hls timeout + var timerDuration = job.Type == TranscodingJobType.Progressive ? + 1000 : + 1800000; + + if (job.KillTimer == null) + { + if (startTimerIfNeeded) { - job.KillTimer.Change(timerDuration, Timeout.Infinite); + job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); } } + else + { + job.KillTimer.Change(timerDuration, Timeout.Infinite); + } } /// diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index 55e1681e0d..6c767596ea 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -294,6 +294,11 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackProgress request) { + if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) + { + ApiEntryPoint.Instance.PingTranscodingJob(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId); + } + request.SessionId = GetSession().Result.Id; var task = _sessionManager.OnPlaybackProgress(request); @@ -317,6 +322,11 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackStopped request) { + if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) + { + ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); + } + request.SessionId = GetSession().Result.Id; var task = _sessionManager.OnPlaybackStopped(request); From 74adff0d8d63c4c764be0ccd7b1935480a2a2e0a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 12:26:42 -0400 Subject: [PATCH 04/54] combine movie fixes --- .../Playback/Dash/MpegDashService.cs | 14 +------------- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 16 +++++++++++++++- .../Playback/Hls/DynamicHlsService.cs | 3 +-- .../Localization/Server/server.json | 1 + MediaBrowser.WebDashboard/Api/PackageCreator.cs | 1 - .../MediaBrowser.WebDashboard.csproj | 6 ------ 6 files changed, 18 insertions(+), 23 deletions(-) diff --git a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs index ba3f172579..0692c4863e 100644 --- a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs +++ b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; @@ -518,25 +517,14 @@ namespace MediaBrowser.Api.Playback.Dash private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken) { - var tmpPath = playlist + ".tmp"; - var segmentFilename = Path.GetFileName(segment); Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist); while (true) { - FileStream fileStream; - try - { - fileStream = FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); - } - catch (IOException) - { - fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); - } // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written - using (fileStream) + using (var fileStream = GetPlaylistFileStream(playlist)) { using (var reader = new StreamReader(fileStream)) { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 207bc2f679..657a97ac15 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -186,7 +186,7 @@ namespace MediaBrowser.Api.Playback.Hls while (true) { // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written - using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var fileStream = GetPlaylistFileStream(playlist)) { using (var reader = new StreamReader(fileStream)) { @@ -212,6 +212,20 @@ namespace MediaBrowser.Api.Playback.Hls } } + protected Stream GetPlaylistFileStream(string path) + { + var tmpPath = path + ".tmp"; + + try + { + return FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); + } + catch (IOException) + { + return FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); + } + } + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 4f043d3217..dc5f9d4e0a 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; @@ -300,7 +299,7 @@ namespace MediaBrowser.Api.Playback.Hls var segmentFilename = Path.GetFileName(segmentPath); - using (var fileStream = FileSystem.GetFileStream(playlistPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var fileStream = GetPlaylistFileStream(playlistPath)) { using (var reader = new StreamReader(fileStream)) { diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 0cff99c5d1..d27403dda5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -168,6 +168,7 @@ "MessageNothingHere": "Nothing here.", "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", "TabSuggested": "Suggested", + "TabSuggestions": "Suggestions", "TabLatest": "Latest", "TabUpcoming": "Upcoming", "TabShows": "Shows", diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 8982b5739c..5138b157f1 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -451,7 +451,6 @@ namespace MediaBrowser.WebDashboard.Api "moviegenres.js", "moviecollections.js", "movies.js", - "movieslatest.js", "moviepeople.js", "moviesrecommended.js", "moviestudios.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 2725d63ad8..88a2a8ae1b 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -556,9 +556,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -889,9 +886,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest From 285805d84a3d636b359e1a16f0d6b94fef6fc258 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 12:58:52 -0400 Subject: [PATCH 05/54] create square collages --- .../Photos/BaseDynamicImageProvider.cs | 34 +++++----- .../UserViews/DynamicImageProvider.cs | 3 +- .../UserViews/StripCollageBuilder.cs | 64 +++++++++++++++++++ 3 files changed, 80 insertions(+), 21 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 1063fde530..0b5ffc903b 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -13,6 +13,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Server.Implementations.UserViews; namespace MediaBrowser.Server.Implementations.Photos { @@ -134,30 +135,25 @@ namespace MediaBrowser.Server.Implementations.Photos return parts.GetMD5().ToString("N"); } - protected Task GetThumbCollage(List items) + protected Task GetThumbCollage(IHasImages primaryItem, List items) { - var files = items - .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToList(); + var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(items), primaryItem.Name, 960, 540); - return DynamicImageHelpers.GetThumbCollage(files, - FileSystem, - 1600, - 900, - ApplicationPaths); + return Task.FromResult(stream); } - protected Task GetSquareCollage(List items) + private IEnumerable GetStripCollageImagePaths(IEnumerable items) { - var files = items + return items .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToList(); + .Where(i => !string.IsNullOrWhiteSpace(i)); + } + + protected Task GetSquareCollage(IHasImages primaryItem, List items) + { + var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), primaryItem.Name, 800, 800); - return DynamicImageHelpers.GetSquareCollage(files, - FileSystem, - 800, ApplicationPaths); + return Task.FromResult(stream); } public string Name @@ -176,8 +172,8 @@ namespace MediaBrowser.Server.Implementations.Photos } return imageType == ImageType.Thumb ? - await GetThumbCollage(itemsWithImages).ConfigureAwait(false) : - await GetSquareCollage(itemsWithImages).ConfigureAwait(false); + await GetThumbCollage(item, itemsWithImages).ConfigureAwait(false) : + await GetSquareCollage(item, itemsWithImages).ConfigureAwait(false); } public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index d6a94210cf..84bab19752 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -248,8 +248,7 @@ namespace MediaBrowser.Server.Implementations.UserViews return null; } - var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), item.Name, 960, 540); - return stream; + return new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), item.Name, 960, 540); } return await base.CreateImageAsync(item, itemsWithImages, imageType, imageIndex); diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index 0bf4d8e4ac..10173c9f89 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -18,6 +18,14 @@ namespace MediaBrowser.Server.Implementations.UserViews _appPaths = appPaths; } + public Stream BuildSquareCollage(IEnumerable paths, string text, int width, int height) + { + using (var wand = BuildSquareCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } + } + public Stream BuildThumbCollage(IEnumerable paths, string text, int width, int height) { using (var wand = BuildThumbCollageWand(paths, width, height)) @@ -172,6 +180,62 @@ namespace MediaBrowser.Server.Implementations.UserViews } } + private MagickWand BuildSquareCollageWand(IEnumerable paths, int width, int height) + { + var inputPaths = ProjectPaths(paths, 4); + using (var wandImages = new MagickWand(inputPaths)) + { + var wand = new MagickWand(width, height); + wand.OpenImage("gradient:#111111-#111111"); + using (var draw = new DrawingWand()) + { + var iSlice = Convert.ToInt32(width * 0.2333333334); + int iTrans = Convert.ToInt32(height * .25); + int iHeight = Convert.ToInt32(height * .65); + var horizontalImagePadding = Convert.ToInt32(width * 0.0125); + + foreach (var element in wandImages.ImageList) + { + int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height); + element.Gravity = GravityType.CenterGravity; + element.BackgroundColor = ColorName.Black; + element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter); + int ix = (int)Math.Abs((iWidth - iSlice) / 2); + element.CropImage(iSlice, iHeight, ix, 0); + + element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0); + } + + wandImages.SetFirstIterator(); + using (var wandList = wandImages.AppendImages()) + { + wandList.CurrentImage.TrimImage(1); + using (var mwr = wandList.CloneMagickWand()) + { + mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1); + mwr.CurrentImage.FlipImage(); + + mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel; + mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey70); + + using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans)) + { + mwg.OpenImage("gradient:black-none"); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing); + + wandList.AddImage(mwr); + int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .05)); + } + } + } + } + + return wand; + } + } + private string MontserratLightFont { get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths); } From 527f4887e91f33009be814a29313891c0827c8a7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 13:44:44 -0400 Subject: [PATCH 06/54] add new dynamic images --- .../Photos/BaseDynamicImageProvider.cs | 33 ++++-- .../UserViews/DynamicImageProvider.cs | 2 +- .../UserViews/StripCollageBuilder.cs | 105 ++++++++++++++++-- 3 files changed, 123 insertions(+), 17 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 0b5ffc903b..401234ac50 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -3,17 +3,17 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; +using MediaBrowser.Server.Implementations.UserViews; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Server.Implementations.UserViews; namespace MediaBrowser.Server.Implementations.Photos { @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "5"; + private const string Version = "9"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + @@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected Task GetThumbCollage(IHasImages primaryItem, List items) { - var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(items), primaryItem.Name, 960, 540); + var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(items), 960, 540, true, primaryItem.Name); return Task.FromResult(stream); } @@ -149,9 +149,16 @@ namespace MediaBrowser.Server.Implementations.Photos .Where(i => !string.IsNullOrWhiteSpace(i)); } + protected Task GetPosterCollage(IHasImages primaryItem, List items) + { + var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), 800, 800, true, primaryItem.Name); + + return Task.FromResult(stream); + } + protected Task GetSquareCollage(IHasImages primaryItem, List items) { - var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), primaryItem.Name, 800, 800); + var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), 800, 800, true, primaryItem.Name); return Task.FromResult(stream); } @@ -171,9 +178,19 @@ namespace MediaBrowser.Server.Implementations.Photos return null; } - return imageType == ImageType.Thumb ? - await GetThumbCollage(item, itemsWithImages).ConfigureAwait(false) : - await GetSquareCollage(item, itemsWithImages).ConfigureAwait(false); + if (imageType == ImageType.Thumb) + { + return await GetThumbCollage(item, itemsWithImages).ConfigureAwait(false); + } + + if (imageType == ImageType.Primary) + { + return item is PhotoAlbum || item is Playlist ? + await GetSquareCollage(item, itemsWithImages).ConfigureAwait(false) : + await GetPosterCollage(item, itemsWithImages).ConfigureAwait(false); + } + + throw new ArgumentException("Unexpected image type"); } public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index 84bab19752..253bf36a26 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -248,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.UserViews return null; } - return new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), item.Name, 960, 540); + return new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), 960, 540, false, item.Name); } return await base.CreateImageAsync(item, itemsWithImages, imageType, imageIndex); diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index 10173c9f89..7dbfbb0156 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -18,19 +18,39 @@ namespace MediaBrowser.Server.Implementations.UserViews _appPaths = appPaths; } - public Stream BuildSquareCollage(IEnumerable paths, string text, int width, int height) + public Stream BuildSquareCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) { - using (var wand = BuildSquareCollageWand(paths, width, height)) + if (renderWithText) { - return DynamicImageHelpers.GetStream(wand, _appPaths); + using (var wand = BuildSquareCollageWandWithText(paths, text, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } + } + else + { + using (var wand = BuildSquareCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } } } - public Stream BuildThumbCollage(IEnumerable paths, string text, int width, int height) + public Stream BuildThumbCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) { - using (var wand = BuildThumbCollageWand(paths, width, height)) + if (renderWithText) + { + using (var wand = BuildThumbCollageWandWithText(paths, text, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } + } + else { - return DynamicImageHelpers.GetStream(wand, _appPaths); + using (var wand = BuildThumbCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } } } @@ -68,7 +88,7 @@ namespace MediaBrowser.Server.Implementations.UserViews { draw.FillColor = fcolor; draw.Font = MontserratLightFont; - draw.FontSize = 50; + draw.FontSize = 60; draw.FontWeight = FontWeightType.LightStyle; draw.TextAntialias = true; } @@ -192,7 +212,7 @@ namespace MediaBrowser.Server.Implementations.UserViews var iSlice = Convert.ToInt32(width * 0.2333333334); int iTrans = Convert.ToInt32(height * .25); int iHeight = Convert.ToInt32(height * .65); - var horizontalImagePadding = Convert.ToInt32(width * 0.0125); + var horizontalImagePadding = Convert.ToInt32(width * 0.02); foreach (var element in wandImages.ImageList) { @@ -236,6 +256,75 @@ namespace MediaBrowser.Server.Implementations.UserViews } } + private MagickWand BuildSquareCollageWandWithText(IEnumerable paths, string label, int width, int height) + { + var inputPaths = ProjectPaths(paths, 4); + using (var wandImages = new MagickWand(inputPaths)) + { + var wand = new MagickWand(width, height); + wand.OpenImage("gradient:#111111-#111111"); + using (var draw = new DrawingWand()) + { + using (var fcolor = new PixelWand(ColorName.White)) + { + draw.FillColor = fcolor; + draw.Font = MontserratLightFont; + draw.FontSize = 60; + draw.FontWeight = FontWeightType.LightStyle; + draw.TextAntialias = true; + } + + var fontMetrics = wand.QueryFontMetrics(draw, label); + var textContainerY = Convert.ToInt32(height * .165); + wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label); + + var iSlice = Convert.ToInt32(width * 0.2333333334); + int iTrans = Convert.ToInt32(height * 0.2); + int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296); + var horizontalImagePadding = Convert.ToInt32(width * 0.02); + + foreach (var element in wandImages.ImageList) + { + int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height); + element.Gravity = GravityType.CenterGravity; + element.BackgroundColor = new PixelWand("none", 1); + element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter); + int ix = (int)Math.Abs((iWidth - iSlice) / 2); + element.CropImage(iSlice, iHeight, ix, 0); + + element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0); + } + + wandImages.SetFirstIterator(); + using (var wandList = wandImages.AppendImages()) + { + wandList.CurrentImage.TrimImage(1); + using (var mwr = wandList.CloneMagickWand()) + { + mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1); + mwr.CurrentImage.FlipImage(); + + mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel; + mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey60); + + using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans)) + { + mwg.OpenImage("gradient:black-none"); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing); + + wandList.AddImage(mwr); + int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852)); + } + } + } + } + + return wand; + } + } + private string MontserratLightFont { get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths); } From c9c5a9345083c6af1a1ce3733fbc79f385cce0ea Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 17:01:42 -0400 Subject: [PATCH 07/54] add poster dynamic images --- .../Photos/BaseDynamicImageProvider.cs | 4 +- .../UserViews/StripCollageBuilder.cs | 160 ++++++++++++++++-- .../FFMpeg/FFMpegDownloader.cs | 9 +- 3 files changed, 157 insertions(+), 16 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 401234ac50..0909dfec99 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "9"; + private const string Version = "15"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + @@ -151,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected Task GetPosterCollage(IHasImages primaryItem, List items) { - var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), 800, 800, true, primaryItem.Name); + var stream = new StripCollageBuilder(ApplicationPaths).BuildPosterCollage(GetStripCollageImagePaths(items), 600, 900, true, primaryItem.Name); return Task.FromResult(stream); } diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index 7dbfbb0156..de9587286f 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -18,22 +18,34 @@ namespace MediaBrowser.Server.Implementations.UserViews _appPaths = appPaths; } - public Stream BuildSquareCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) + public Stream BuildPosterCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) { if (renderWithText) { - using (var wand = BuildSquareCollageWandWithText(paths, text, width, height)) + using (var wand = BuildPosterCollageWandWithText(paths, text, width, height)) { return DynamicImageHelpers.GetStream(wand, _appPaths); } } - else + using (var wand = BuildPosterCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } + } + + public Stream BuildSquareCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) + { + if (renderWithText) { - using (var wand = BuildSquareCollageWand(paths, width, height)) + using (var wand = BuildSquareCollageWandWithText(paths, text, width, height)) { return DynamicImageHelpers.GetStream(wand, _appPaths); } } + using (var wand = BuildSquareCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } } public Stream BuildThumbCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) @@ -45,12 +57,9 @@ namespace MediaBrowser.Server.Implementations.UserViews return DynamicImageHelpers.GetStream(wand, _appPaths); } } - else + using (var wand = BuildThumbCollageWand(paths, width, height)) { - using (var wand = BuildThumbCollageWand(paths, width, height)) - { - return DynamicImageHelpers.GetStream(wand, _appPaths); - } + return DynamicImageHelpers.GetStream(wand, _appPaths); } } @@ -144,6 +153,131 @@ namespace MediaBrowser.Server.Implementations.UserViews } } + private MagickWand BuildPosterCollageWand(IEnumerable paths, int width, int height) + { + var inputPaths = ProjectPaths(paths, 3); + using (var wandImages = new MagickWand(inputPaths)) + { + var wand = new MagickWand(width, height); + wand.OpenImage("gradient:#111111-#111111"); + using (var draw = new DrawingWand()) + { + var iSlice = Convert.ToInt32(width * .3); + int iTrans = Convert.ToInt32(height * .25); + int iHeight = Convert.ToInt32(height * .65); + var horizontalImagePadding = Convert.ToInt32(width * 0.025); + + foreach (var element in wandImages.ImageList) + { + int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height); + element.Gravity = GravityType.CenterGravity; + element.BackgroundColor = ColorName.Black; + element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter); + int ix = (int)Math.Abs((iWidth - iSlice) / 2); + element.CropImage(iSlice, iHeight, ix, 0); + + element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0); + } + + wandImages.SetFirstIterator(); + using (var wandList = wandImages.AppendImages()) + { + wandList.CurrentImage.TrimImage(1); + using (var mwr = wandList.CloneMagickWand()) + { + mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1); + mwr.CurrentImage.FlipImage(); + + mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel; + mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey70); + + using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans)) + { + mwg.OpenImage("gradient:black-none"); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing); + + wandList.AddImage(mwr); + int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .05)); + } + } + } + } + + return wand; + } + } + + private MagickWand BuildPosterCollageWandWithText(IEnumerable paths, string label, int width, int height) + { + var inputPaths = ProjectPaths(paths, 3); + using (var wandImages = new MagickWand(inputPaths)) + { + var wand = new MagickWand(width, height); + wand.OpenImage("gradient:#111111-#111111"); + using (var draw = new DrawingWand()) + { + using (var fcolor = new PixelWand(ColorName.White)) + { + draw.FillColor = fcolor; + draw.Font = MontserratLightFont; + draw.FontSize = 60; + draw.FontWeight = FontWeightType.LightStyle; + draw.TextAntialias = true; + } + + var fontMetrics = wand.QueryFontMetrics(draw, label); + var textContainerY = Convert.ToInt32(height * .165); + wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label); + + var iSlice = Convert.ToInt32(width * .3); + int iTrans = Convert.ToInt32(height * 0.2); + int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296); + var horizontalImagePadding = Convert.ToInt32(width * 0.025); + + foreach (var element in wandImages.ImageList) + { + int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height); + element.Gravity = GravityType.CenterGravity; + element.BackgroundColor = new PixelWand("none", 1); + element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter); + int ix = (int)Math.Abs((iWidth - iSlice) / 2); + element.CropImage(iSlice, iHeight, ix, 0); + + element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0); + } + + wandImages.SetFirstIterator(); + using (var wandList = wandImages.AppendImages()) + { + wandList.CurrentImage.TrimImage(1); + using (var mwr = wandList.CloneMagickWand()) + { + mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1); + mwr.CurrentImage.FlipImage(); + + mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel; + mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey60); + + using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans)) + { + mwg.OpenImage("gradient:black-none"); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing); + + wandList.AddImage(mwr); + int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852)); + } + } + } + } + + return wand; + } + } + private MagickWand BuildThumbCollageWand(IEnumerable paths, int width, int height) { var inputPaths = ProjectPaths(paths, 8); @@ -209,9 +343,9 @@ namespace MediaBrowser.Server.Implementations.UserViews wand.OpenImage("gradient:#111111-#111111"); using (var draw = new DrawingWand()) { - var iSlice = Convert.ToInt32(width * 0.2333333334); + var iSlice = Convert.ToInt32(width * .225); int iTrans = Convert.ToInt32(height * .25); - int iHeight = Convert.ToInt32(height * .65); + int iHeight = Convert.ToInt32(height * .63); var horizontalImagePadding = Convert.ToInt32(width * 0.02); foreach (var element in wandImages.ImageList) @@ -246,7 +380,7 @@ namespace MediaBrowser.Server.Implementations.UserViews wandList.AddImage(mwr); int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; - wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .05)); + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .07)); } } } @@ -278,7 +412,7 @@ namespace MediaBrowser.Server.Implementations.UserViews var textContainerY = Convert.ToInt32(height * .165); wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label); - var iSlice = Convert.ToInt32(width * 0.2333333334); + var iSlice = Convert.ToInt32(width * .225); int iTrans = Convert.ToInt32(height * 0.2); int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296); var horizontalImagePadding = Convert.ToInt32(width * 0.02); diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs index d4cefdb10a..fe7cd943a2 100644 --- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs +++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs @@ -202,7 +202,14 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg } } - throw new ApplicationException("Unable to download required components. Please try again later."); + if (downloadinfo.DownloadUrls.Length == 0) + { + throw new ApplicationException("ffmpeg unvailable. Please install it and start the server with two command line arguments: -ffmpeg \"{PATH}\" and -ffprobe \"{PATH}\""); + } + else + { + throw new ApplicationException("Unable to download required components. Please try again later."); + } } private void ExtractFFMpeg(FFMpegDownloadInfo downloadinfo, string tempFile, string targetFolder) From 305398ed9c71ab75a19a20cd7ebcaab5de38892c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 22:54:40 -0400 Subject: [PATCH 08/54] update dynamic images --- .../Photos/BaseDynamicImageProvider.cs | 2 +- .../UserViews/StripCollageBuilder.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 0909dfec99..603ce87f29 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "15"; + private const string Version = "17"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index de9587286f..a9ac0946ac 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -155,17 +155,17 @@ namespace MediaBrowser.Server.Implementations.UserViews private MagickWand BuildPosterCollageWand(IEnumerable paths, int width, int height) { - var inputPaths = ProjectPaths(paths, 3); + var inputPaths = ProjectPaths(paths, 4); using (var wandImages = new MagickWand(inputPaths)) { var wand = new MagickWand(width, height); wand.OpenImage("gradient:#111111-#111111"); using (var draw = new DrawingWand()) { - var iSlice = Convert.ToInt32(width * .3); + var iSlice = Convert.ToInt32(width * 0.225); int iTrans = Convert.ToInt32(height * .25); int iHeight = Convert.ToInt32(height * .65); - var horizontalImagePadding = Convert.ToInt32(width * 0.025); + var horizontalImagePadding = Convert.ToInt32(width * 0.0275); foreach (var element in wandImages.ImageList) { @@ -211,7 +211,7 @@ namespace MediaBrowser.Server.Implementations.UserViews private MagickWand BuildPosterCollageWandWithText(IEnumerable paths, string label, int width, int height) { - var inputPaths = ProjectPaths(paths, 3); + var inputPaths = ProjectPaths(paths, 4); using (var wandImages = new MagickWand(inputPaths)) { var wand = new MagickWand(width, height); @@ -231,10 +231,10 @@ namespace MediaBrowser.Server.Implementations.UserViews var textContainerY = Convert.ToInt32(height * .165); wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label); - var iSlice = Convert.ToInt32(width * .3); + var iSlice = Convert.ToInt32(width * 0.225); int iTrans = Convert.ToInt32(height * 0.2); int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296); - var horizontalImagePadding = Convert.ToInt32(width * 0.025); + var horizontalImagePadding = Convert.ToInt32(width * 0.0275); foreach (var element in wandImages.ImageList) { From 9924019f57a120eafc721bd070d678fb2bd62168 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 11:52:49 -0400 Subject: [PATCH 09/54] restore model properties --- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 1 - MediaBrowser.Model/Querying/ItemQuery.cs | 10 ++++++++++ .../HttpServer/ThrottledStream.cs | 12 ------------ .../Localization/JavaScript/javascript.json | 6 ++++++ .../Localization/Server/server.json | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 657a97ac15..701516b48c 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs index 0cdf5ca7ab..5a88c0d43e 100644 --- a/MediaBrowser.Model/Querying/ItemQuery.cs +++ b/MediaBrowser.Model/Querying/ItemQuery.cs @@ -281,6 +281,13 @@ namespace MediaBrowser.Model.Querying public int? ImageTypeLimit { get; set; } public ImageType[] EnableImageTypes { get; set; } + [Obsolete] + public string[] Artists { get; set; } + [Obsolete] + public string[] Studios { get; set; } + [Obsolete] + public string Person { get; set; } + /// /// Initializes a new instance of the class. /// @@ -299,6 +306,9 @@ namespace MediaBrowser.Model.Querying VideoTypes = new VideoType[] { }; + Artists = new string[] { }; + Studios = new string[] { }; + Genres = new string[] { }; StudioIds = new string[] { }; IncludeItemTypes = new string[] { }; diff --git a/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs b/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs index 4bde30dac3..1c01fa9e04 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs @@ -15,8 +15,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// public const long Infinite = 0; - public Func ThrottleCallback { get; set; } - #region Private members /// /// The base stream. @@ -293,16 +291,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer return false; } - if (ThrottleCallback != null) - { - var val = ThrottleCallback(_maximumBytesPerSecond, _bytesWritten); - - if (val == 0) - { - return false; - } - } - return true; } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index ae20fafbab..9c53eb92e0 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -35,8 +35,11 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "HeaderSupportTheTeam": "Support the Emby Team", + "TextEnjoyBonusFeatures": "Enjoy Bonus Features", "TitleLiveTV": "Live TV", "TitleSync": "Sync", + "ButtonDonate": "Donate", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -175,6 +178,9 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", + "OptionEveryday": "Every day", + "OptionWeekend": "Weekends", + "OptionWeekday": "Weekdays", "HeaderConfirmDeletion": "Confirm Deletion", "MessageConfirmPathSubstitutionDeletion": "Are you sure you wish to delete this path substitution?", "LiveTvUpdateAvailable": "(Update available)", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index d27403dda5..3f9a36560d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -913,8 +913,8 @@ "OptionDefaultSort": "Default", "OptionCommunityMostWatchedSort": "Most Watched", "TabNextUp": "Next Up", + "PlaceholderUsername": "Username", "HeaderBecomeProjectSupporter": "Become an Emby Supporter", - "TextEnjoyBonusFeatures": "Enjoy Bonus Features", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the + button to start creating Collections.", "MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.", From 165423697892764575ed2908c9b66534d920ae81 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 11:59:56 -0400 Subject: [PATCH 10/54] activate item counts --- MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs | 3 +-- MediaBrowser.Server.Implementations/Dto/DtoService.cs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 609c1048f2..b2364ce3c6 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -142,7 +142,7 @@ namespace MediaBrowser.Api.UserLibrary } IEnumerable>> tuples; - if (dtoOptions.Fields.Contains(ItemFields.ItemCounts) || true) + if (dtoOptions.Fields.Contains(ItemFields.ItemCounts)) { tuples = ibnItems.Select(i => new Tuple>(i, i.GetTaggedItems(libraryItems).ToList())); } @@ -177,7 +177,6 @@ namespace MediaBrowser.Api.UserLibrary return true; } - return true; return options.Fields.Contains(ItemFields.ItemCounts); } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index dc6f4a5250..364a47945b 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -97,7 +97,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (byName != null && !(item is LiveTvChannel)) { - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { var itemFilter = byName.GetItemFilter(); @@ -125,7 +125,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (byName != null && !(item is LiveTvChannel)) { - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { var itemFilter = byName.GetItemFilter(); @@ -317,7 +317,7 @@ namespace MediaBrowser.Server.Implementations.Dto { var dto = GetBaseItemDtoInternal(item, options, user); - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { SetItemByNameInfo(item, dto, taggedItems, user); } From 0481fef03bada66c466fd9e7e4c3c5e5a1ed30ad Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 12:31:56 -0400 Subject: [PATCH 11/54] add sports to suggested tv --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 10 +++- MediaBrowser.Model/LiveTv/ProgramQuery.cs | 6 +++ .../LiveTv/RecommendedProgramQuery.cs | 5 ++ MediaBrowser.Model/Querying/ItemSortBy.cs | 1 + .../LiveTv/LiveTvManager.cs | 17 +++++++ .../Localization/Server/server.json | 3 +- ...MediaBrowser.Server.Implementations.csproj | 1 + .../Sorting/StartDateComparer.cs | 47 +++++++++++++++++++ 8 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/Sorting/StartDateComparer.cs diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 24c91e172f..bb6f74f364 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -186,6 +186,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsMovie { get; set; } + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSports { get; set; } + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? StartIndex { get; set; } @@ -218,6 +221,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? HasAired { get; set; } + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSports { get; set; } + [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsMovie { get; set; } } @@ -422,6 +428,7 @@ namespace MediaBrowser.Api.LiveTv query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); query.SortOrder = request.SortOrder; query.IsMovie = request.IsMovie; + query.IsSports = request.IsSports; query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false); @@ -437,7 +444,8 @@ namespace MediaBrowser.Api.LiveTv IsAiring = request.IsAiring, Limit = request.Limit, HasAired = request.HasAired, - IsMovie = request.IsMovie + IsMovie = request.IsMovie, + IsSports = request.IsSports }; var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs index bbd396c33f..c19ba54bd1 100644 --- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs @@ -53,6 +53,12 @@ namespace MediaBrowser.Model.LiveTv /// If set to null, all programs will be returned public bool? IsMovie { get; set; } + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// null if [is sports] contains no value, true if [is sports]; otherwise, false. + public bool? IsSports { get; set; } + /// /// Skips over a given number of items within the results. Use for paging. /// diff --git a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs index 9ba8e0e5fc..4a8ae2365b 100644 --- a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs @@ -31,5 +31,10 @@ /// /// null if [is movie] contains no value, true if [is movie]; otherwise, false. public bool? IsMovie { get; set; } + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// null if [is sports] contains no value, true if [is sports]; otherwise, false. + public bool? IsSports { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index fcc7e39a19..9c2926b542 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -43,6 +43,7 @@ namespace MediaBrowser.Model.Querying /// The premiere date /// public const string PremiereDate = "PremiereDate"; + public const string StartDate = "StartDate"; /// /// The sort name /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index a39781d6a3..cb9bb77111 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -761,6 +761,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(p => p.IsMovie == query.IsMovie); } + if (query.IsSports.HasValue) + { + programs = programs.Where(p => p.IsSports == query.IsSports); + } + programs = _libraryManager.Sort(programs, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending) .Cast(); @@ -826,6 +831,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(p => p.IsMovie == query.IsMovie.Value); } + if (query.IsSports.HasValue) + { + programs = programs.Where(p => p.IsSports == query.IsSports.Value); + } + var programList = programs.ToList(); var genres = programList.SelectMany(i => i.Genres) @@ -996,6 +1006,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv innerProgress = new ActionableProgress(); innerProgress.RegisterAction(p => progress.Report(90 + (p * .1))); await CleanDatabaseInternal(progress, cancellationToken).ConfigureAwait(false); + + foreach (var program in _programs.Values + .Where(i => (i.StartDate - DateTime.UtcNow).TotalDays <= 1) + .ToList()) + { + RefreshIfNeeded(program); + } } finally { diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 3f9a36560d..835e9b3beb 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -913,7 +913,7 @@ "OptionDefaultSort": "Default", "OptionCommunityMostWatchedSort": "Most Watched", "TabNextUp": "Next Up", - "PlaceholderUsername": "Username", + "PlaceholderUsername": "Username", "HeaderBecomeProjectSupporter": "Become an Emby Supporter", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the + button to start creating Collections.", @@ -1399,6 +1399,7 @@ "LabelEnableInternetMetadataForTvPrograms": "Download internet metadata for:", "OptionTVMovies": "TV Movies", "HeaderUpcomingMovies": "Upcoming Movies", + "HeaderUpcomingSports": "Upcoming Sports", "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index db2397d2f7..dd770b0c89 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -233,6 +233,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Sorting/StartDateComparer.cs b/MediaBrowser.Server.Implementations/Sorting/StartDateComparer.cs new file mode 100644 index 0000000000..7e6f24ec1c --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/StartDateComparer.cs @@ -0,0 +1,47 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class StartDateComparer : IBaseItemComparer + { + /// + /// Compares the specified x. + /// + /// The x. + /// The y. + /// System.Int32. + public int Compare(BaseItem x, BaseItem y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// + /// Gets the date. + /// + /// The x. + /// DateTime. + private DateTime GetDate(BaseItem x) + { + var hasStartDate = x as LiveTvProgram; + + if (hasStartDate != null) + { + return hasStartDate.StartDate; + } + return DateTime.MinValue; + } + + /// + /// Gets the name. + /// + /// The name. + public string Name + { + get { return ItemSortBy.StartDate; } + } + } +} From ef505c8e9e2b8f348aeaa89be6bc446014b72996 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 12:58:12 -0400 Subject: [PATCH 12/54] update images --- .../Photos/BaseDynamicImageProvider.cs | 2 +- .../UserViews/DynamicImageProvider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 603ce87f29..79a1181cab 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "17"; + private const string Version = "18"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index 253bf36a26..7bcbbd6a8a 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -107,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.UserViews User = _userManager.GetUserById(view.UserId.Value), CollapseBoxSetItems = false, Recursive = recursive, - ExcludeItemTypes = new[] { "UserView", "CollectionFolder" } + ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Playlist" } }).ConfigureAwait(false); From 3c3b63f03bfd56937b4eef2614d488fc5bb26e4e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 22:18:47 -0400 Subject: [PATCH 13/54] sync fixes --- .../Dto/DtoService.cs | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 364a47945b..a35549671b 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -85,7 +85,7 @@ namespace MediaBrowser.Server.Implementations.Dto public IEnumerable GetBaseItemDtos(IEnumerable items, DtoOptions options, User user = null, BaseItem owner = null) { - var itemIdsWithSyncJobs = GetItemIdsWithSyncJobs(options).ToList(); + var tuple = GetItemIdsWithSyncJobs(options); var list = new List(); @@ -97,7 +97,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (byName != null && !(item is LiveTvChannel)) { - if (options.Fields.Contains(ItemFields.ItemCounts)) + //if (options.Fields.Contains(ItemFields.ItemCounts)) { var itemFilter = byName.GetItemFilter(); @@ -109,7 +109,7 @@ namespace MediaBrowser.Server.Implementations.Dto } } - FillSyncInfo(dto, item, itemIdsWithSyncJobs, options, user); + FillSyncInfo(dto, item, tuple.Item1, tuple.Item2, options, user); list.Add(dto); } @@ -125,7 +125,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (byName != null && !(item is LiveTvChannel)) { - if (options.Fields.Contains(ItemFields.ItemCounts)) + //if (options.Fields.Contains(ItemFields.ItemCounts)) { var itemFilter = byName.GetItemFilter(); @@ -145,29 +145,29 @@ namespace MediaBrowser.Server.Implementations.Dto return dto; } - private IEnumerable GetItemIdsWithSyncJobs(DtoOptions options) + private Tuple, IEnumerable> GetItemIdsWithSyncJobs(DtoOptions options) { if (!options.Fields.Contains(ItemFields.SyncInfo)) { - return new List(); + return new Tuple, IEnumerable>(new List(), new List()); } var deviceId = options.DeviceId; if (string.IsNullOrWhiteSpace(deviceId)) { - return new List(); + return new Tuple, IEnumerable>(new List(), new List()); } var caps = _deviceManager().GetCapabilities(deviceId); if (caps == null || !caps.SupportsSync) { - return new List(); + return new Tuple, IEnumerable>(new List(), new List()); } - var result = _syncManager.GetLibraryItemIds(new SyncJobItemQuery + var result1 = _syncManager.GetLibraryItemIds(new SyncJobItemQuery { TargetId = deviceId, - Statuses = new SyncJobItemStatus[] + Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Queued, @@ -176,7 +176,16 @@ namespace MediaBrowser.Server.Implementations.Dto } }); - return result.Items; + var result2 = _syncManager.GetLibraryItemIds(new SyncJobItemQuery + { + TargetId = deviceId, + Statuses = new[] + { + SyncJobItemStatus.Synced + } + }); + + return new Tuple, IEnumerable>(result1.Items, result2.Items); } private void FillSyncInfo(BaseItemDto dto, BaseItem item, DtoOptions options, User user) @@ -189,11 +198,14 @@ namespace MediaBrowser.Server.Implementations.Dto if (dto.SupportsSync ?? false) { - dto.HasSyncJob = GetItemIdsWithSyncJobs(options).Contains(dto.Id, StringComparer.OrdinalIgnoreCase); + var tuple = GetItemIdsWithSyncJobs(options); + + dto.HasSyncJob = tuple.Item1.Contains(dto.Id, StringComparer.OrdinalIgnoreCase); + dto.IsSynced = tuple.Item2.Contains(dto.Id, StringComparer.OrdinalIgnoreCase); } } - private void FillSyncInfo(BaseItemDto dto, BaseItem item, IEnumerable itemIdsWithSyncJobs, DtoOptions options, User user) + private void FillSyncInfo(BaseItemDto dto, BaseItem item, IEnumerable itemIdsWithPendingSyncJobs, IEnumerable syncedItemIds, DtoOptions options, User user) { if (options.Fields.Contains(ItemFields.SyncInfo)) { @@ -203,7 +215,8 @@ namespace MediaBrowser.Server.Implementations.Dto if (dto.SupportsSync ?? false) { - dto.HasSyncJob = itemIdsWithSyncJobs.Contains(dto.Id, StringComparer.OrdinalIgnoreCase); + dto.HasSyncJob = itemIdsWithPendingSyncJobs.Contains(dto.Id, StringComparer.OrdinalIgnoreCase); + dto.IsSynced = syncedItemIds.Contains(dto.Id, StringComparer.OrdinalIgnoreCase); } } @@ -317,7 +330,7 @@ namespace MediaBrowser.Server.Implementations.Dto { var dto = GetBaseItemDtoInternal(item, options, user); - if (options.Fields.Contains(ItemFields.ItemCounts)) + //if (options.Fields.Contains(ItemFields.ItemCounts)) { SetItemByNameInfo(item, dto, taggedItems, user); } From f9ab5cfad22afd9e5d330276a15f7a84828dbdb8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 22:20:02 -0400 Subject: [PATCH 14/54] sync version --- SharedVersion.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 11c0c6b5e6..8177f8cca8 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("3.0.*")] -//[assembly: AssemblyVersion("3.0.5569.0")] +//[assembly: AssemblyVersion("3.0.*")] +[assembly: AssemblyVersion("3.0.5572.0")] From 8c61abf6d23510da8eaddf3894f9e14f88dc5f22 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 22:34:53 -0400 Subject: [PATCH 15/54] updated nuget --- MediaBrowser.Server.Implementations/Dto/DtoService.cs | 6 +++--- Nuget/MediaBrowser.Common.Internal.nuspec | 4 ++-- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 8934c9c8fc..612c33d872 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -97,7 +97,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (byName != null && !(item is LiveTvChannel)) { - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { var itemFilter = byName.GetItemFilter(); @@ -125,7 +125,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (byName != null && !(item is LiveTvChannel)) { - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { var itemFilter = byName.GetItemFilter(); @@ -330,7 +330,7 @@ namespace MediaBrowser.Server.Implementations.Dto { var dto = GetBaseItemDtoInternal(item, options, user); - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { SetItemByNameInfo(item, dto, taggedItems, user); } diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index d310716661..9ecfe2b201 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.612 + 3.0.613 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index d4d7de019a..18078aef37 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.612 + 3.0.613 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index de9c503b92..f84c447298 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.612 + 3.0.613 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index b0008f9d03..1d28c22a89 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.612 + 3.0.613 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + From 2b7a80cfb5b9212260734c095a5b3439af7d64e2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 4 Apr 2015 15:35:29 -0400 Subject: [PATCH 16/54] improve direct play of live streams --- MediaBrowser.Api/Playback/MediaInfoService.cs | 2 +- .../MediaBrowser.Controller.csproj | 1 - .../MediaEncoding/IMediaEncoder.cs | 6 +- .../MediaEncoding/MediaEncoderHelpers.cs | 290 +----- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 5 +- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 4 +- .../Encoder/MediaEncoder.cs | 19 +- .../MediaBrowser.MediaEncoding.csproj | 7 + .../Probing}/FFProbeHelpers.cs | 2 +- .../Probing}/InternalMediaInfoResult.cs | 6 +- .../Probing/ProbeResultNormalizer.cs | 882 ++++++++++++++++++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 43 +- MediaBrowser.Model/Dlna/StreamInfo.cs | 1 + MediaBrowser.Model/Dto/BaseItemPerson.cs | 4 +- MediaBrowser.Model/Entities/MediaInfo.cs | 61 +- .../MediaBrowser.Providers.csproj | 5 - .../MediaInfo/FFProbeAudioInfo.cs | 342 +------ .../MediaInfo/FFProbeVideoInfo.cs | 297 ++---- .../LiveTv/LiveTvMediaSourceProvider.cs | 81 +- .../Sync/SyncJobProcessor.cs | 4 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- SharedVersion.cs | 4 +- 25 files changed, 1200 insertions(+), 878 deletions(-) rename {MediaBrowser.Providers/MediaInfo => MediaBrowser.MediaEncoding/Probing}/FFProbeHelpers.cs (98%) rename {MediaBrowser.Controller/MediaEncoding => MediaBrowser.MediaEncoding/Probing}/InternalMediaInfoResult.cs (99%) create mode 100644 MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 08c5d56db2..abd0278c26 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -223,7 +223,7 @@ namespace MediaBrowser.Api.Playback int? subtitleStreamIndex, string playSessionId) { - var streamBuilder = new StreamBuilder(); + var streamBuilder = new StreamBuilder(Logger); var options = new VideoOptions { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 2fa62f79f2..9a4a2cb62c 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -211,7 +211,6 @@ - diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 47544f972b..bb56748642 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -63,16 +63,18 @@ namespace MediaBrowser.Controller.MediaEncoding string filenamePrefix, int? maxWidth, CancellationToken cancellationToken); - + /// /// Gets the media info. /// /// The input files. + /// The primary path. /// The protocol. /// if set to true [is audio]. + /// if set to true [extract chapters]. /// The cancellation token. /// Task. - Task GetMediaInfo(string[] inputFiles, MediaProtocol protocol, bool isAudio, CancellationToken cancellationToken); + Task GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio, bool extractChapters, CancellationToken cancellationToken); /// /// Gets the probe size argument. diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 530c127da7..da9dd4dfd2 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -1,9 +1,7 @@ -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; +using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; @@ -46,291 +44,5 @@ namespace MediaBrowser.Controller.MediaEncoding .Where(f => !string.IsNullOrEmpty(f)) .ToList(); } - - public static MediaInfo GetMediaInfo(InternalMediaInfoResult data) - { - var internalStreams = data.streams ?? new MediaStreamInfo[] { }; - - var info = new MediaInfo - { - MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format)) - .Where(i => i != null) - .ToList() - }; - - if (data.format != null) - { - info.Format = data.format.format_name; - - if (!string.IsNullOrEmpty(data.format.bit_rate)) - { - info.TotalBitrate = int.Parse(data.format.bit_rate, UsCulture); - } - } - - return info; - } - - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// - /// Converts ffprobe stream info to our MediaStream class - /// - /// The stream info. - /// The format info. - /// MediaStream. - private static MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo) - { - var stream = new MediaStream - { - Codec = streamInfo.codec_name, - Profile = streamInfo.profile, - Level = streamInfo.level, - Index = streamInfo.index, - PixelFormat = streamInfo.pix_fmt - }; - - if (streamInfo.tags != null) - { - stream.Language = GetDictionaryValue(streamInfo.tags, "language"); - } - - if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Audio; - - stream.Channels = streamInfo.channels; - - if (!string.IsNullOrEmpty(streamInfo.sample_rate)) - { - stream.SampleRate = int.Parse(streamInfo.sample_rate, UsCulture); - } - - stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout); - } - else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Subtitle; - } - else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1 - ? MediaStreamType.EmbeddedImage - : MediaStreamType.Video; - - stream.Width = streamInfo.width; - stream.Height = streamInfo.height; - stream.AspectRatio = GetAspectRatio(streamInfo); - - stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); - stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); - - stream.BitDepth = GetBitDepth(stream.PixelFormat); - - //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || - // string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) || - // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); - - stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase); - } - else - { - return null; - } - - // Get stream bitrate - var bitrate = 0; - - if (!string.IsNullOrEmpty(streamInfo.bit_rate)) - { - bitrate = int.Parse(streamInfo.bit_rate, UsCulture); - } - else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video) - { - // If the stream info doesn't have a bitrate get the value from the media format info - bitrate = int.Parse(formatInfo.bit_rate, UsCulture); - } - - if (bitrate > 0) - { - stream.BitRate = bitrate; - } - - if (streamInfo.disposition != null) - { - var isDefault = GetDictionaryValue(streamInfo.disposition, "default"); - var isForced = GetDictionaryValue(streamInfo.disposition, "forced"); - - stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase); - - stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); - } - - return stream; - } - - private static int? GetBitDepth(string pixelFormat) - { - var eightBit = new List - { - "yuv420p", - "yuv411p", - "yuvj420p", - "uyyvyy411", - "nv12", - "nv21", - "rgb444le", - "rgb444be", - "bgr444le", - "bgr444be", - "yuvj411p" - }; - - if (!string.IsNullOrEmpty(pixelFormat)) - { - if (eightBit.Contains(pixelFormat, StringComparer.OrdinalIgnoreCase)) - { - return 8; - } - } - - return null; - } - - /// - /// Gets a string from an FFProbeResult tags dictionary - /// - /// The tags. - /// The key. - /// System.String. - private static string GetDictionaryValue(Dictionary tags, string key) - { - if (tags == null) - { - return null; - } - - string val; - - tags.TryGetValue(key, out val); - return val; - } - - private static string ParseChannelLayout(string input) - { - if (string.IsNullOrEmpty(input)) - { - return input; - } - - return input.Split('(').FirstOrDefault(); - } - - private static string GetAspectRatio(MediaStreamInfo info) - { - var original = info.display_aspect_ratio; - - int height; - int width; - - var parts = (original ?? string.Empty).Split(':'); - if (!(parts.Length == 2 && - int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) && - int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) && - width > 0 && - height > 0)) - { - width = info.width; - height = info.height; - } - - if (width > 0 && height > 0) - { - double ratio = width; - ratio /= height; - - if (IsClose(ratio, 1.777777778, .03)) - { - return "16:9"; - } - - if (IsClose(ratio, 1.3333333333, .05)) - { - return "4:3"; - } - - if (IsClose(ratio, 1.41)) - { - return "1.41:1"; - } - - if (IsClose(ratio, 1.5)) - { - return "1.5:1"; - } - - if (IsClose(ratio, 1.6)) - { - return "1.6:1"; - } - - if (IsClose(ratio, 1.66666666667)) - { - return "5:3"; - } - - if (IsClose(ratio, 1.85, .02)) - { - return "1.85:1"; - } - - if (IsClose(ratio, 2.35, .025)) - { - return "2.35:1"; - } - - if (IsClose(ratio, 2.4, .025)) - { - return "2.40:1"; - } - } - - return original; - } - - private static bool IsClose(double d1, double d2, double variance = .005) - { - return Math.Abs(d1 - d2) <= variance; - } - - /// - /// Gets a frame rate from a string value in ffprobe output - /// This could be a number or in the format of 2997/125. - /// - /// The value. - /// System.Nullable{System.Single}. - private static float? GetFrameRate(string value) - { - if (!string.IsNullOrEmpty(value)) - { - var parts = value.Split('/'); - - float result; - - if (parts.Length == 2) - { - result = float.Parse(parts[0], UsCulture) / float.Parse(parts[1], UsCulture); - } - else - { - result = float.Parse(parts[0], UsCulture); - } - - return float.IsNaN(result) ? (float?)null : result; - } - - return null; - } - } } diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 19dab2246d..629b95f67e 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -12,6 +12,7 @@ using MediaBrowser.Dlna.ContentDirectory; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using System; using System.Globalization; @@ -126,7 +127,7 @@ namespace MediaBrowser.Dlna.Didl { var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); - streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions + streamInfo = new StreamBuilder(new NullLogger()).BuildVideoItem(new VideoOptions { ItemId = GetClientId(video), MediaSources = sources, @@ -353,7 +354,7 @@ namespace MediaBrowser.Dlna.Didl { var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); - streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions + streamInfo = new StreamBuilder(new NullLogger()).BuildAudioItem(new AudioOptions { ItemId = GetClientId(audio), MediaSources = sources, diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 5b129243c4..d88adc8c62 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -542,7 +542,7 @@ namespace MediaBrowser.Dlna.PlayTo { return new PlaylistItem { - StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions + StreamInfo = new StreamBuilder(_logger).BuildVideoItem(new VideoOptions { ItemId = item.Id.ToString("N"), MediaSources = mediaSources, @@ -562,7 +562,7 @@ namespace MediaBrowser.Dlna.PlayTo { return new PlaylistItem { - StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions + StreamInfo = new StreamBuilder(_logger).BuildAudioItem(new AudioOptions { ItemId = item.Id.ToString("N"), MediaSources = mediaSources, diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4258898073..18d9ccece9 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Session; +using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -103,15 +104,17 @@ namespace MediaBrowser.MediaEncoding.Encoder /// Gets the media info. /// /// The input files. + /// The primary path. /// The protocol. /// if set to true [is audio]. + /// if set to true [extract chapters]. /// The cancellation token. /// Task. - public Task GetMediaInfo(string[] inputFiles, MediaProtocol protocol, bool isAudio, - CancellationToken cancellationToken) + public Task GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio, + bool extractChapters, CancellationToken cancellationToken) { - return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), !isAudio, - GetProbeSizeArgument(inputFiles, protocol), cancellationToken); + return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), primaryPath, protocol, !isAudio && extractChapters, + GetProbeSizeArgument(inputFiles, protocol), isAudio, cancellationToken); } /// @@ -141,13 +144,17 @@ namespace MediaBrowser.MediaEncoding.Encoder /// Gets the media info internal. /// /// The input path. + /// The primary path. + /// The protocol. /// if set to true [extract chapters]. /// The probe size argument. + /// if set to true [is audio]. /// The cancellation token. /// Task{MediaInfoResult}. /// - private async Task GetMediaInfoInternal(string inputPath, bool extractChapters, + private async Task GetMediaInfoInternal(string inputPath, string primaryPath, MediaProtocol protocol, bool extractChapters, string probeSizeArgument, + bool isAudio, CancellationToken cancellationToken) { var args = extractChapters @@ -244,7 +251,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return result; + return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, isAudio, primaryPath, protocol); } /// diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 72dc0feac5..c3038cadb3 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -68,6 +68,9 @@ + + + @@ -91,6 +94,10 @@ {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} MediaBrowser.Controller + + {6e4145e4-c6d4-4e4d-94f2-87188db6e239} + MediaBrowser.MediaInfo + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} MediaBrowser.Model diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs similarity index 98% rename from MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs rename to MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index 2044979e4e..859f389501 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; -namespace MediaBrowser.Providers.MediaInfo +namespace MediaBrowser.MediaEncoding.Probing { public static class FFProbeHelpers { diff --git a/MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs similarity index 99% rename from MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs rename to MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs index 796fdb723a..9b95e83e74 100644 --- a/MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs +++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace MediaBrowser.Controller.MediaEncoding +namespace MediaBrowser.MediaEncoding.Probing { /// /// Class MediaInfoResult @@ -89,7 +89,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// The channel_layout. public string channel_layout { get; set; } - + /// /// Gets or sets the avg_frame_rate. /// @@ -317,7 +317,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// The probe_score. public int probe_score { get; set; } - + /// /// Gets or sets the tags. /// diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs new file mode 100644 index 0000000000..48e8b6ee20 --- /dev/null +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -0,0 +1,882 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.MediaInfo; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.MediaEncoding.Probing +{ + public class ProbeResultNormalizer + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + } + + public Model.Entities.MediaInfo GetMediaInfo(InternalMediaInfoResult data, bool isAudio, string path, MediaProtocol protocol) + { + var info = new Model.Entities.MediaInfo + { + Path = path, + Protocol = protocol + }; + + FFProbeHelpers.NormalizeFFProbeResult(data); + SetSize(data, info); + + var internalStreams = data.streams ?? new MediaStreamInfo[] { }; + + info.MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format)) + .Where(i => i != null) + .ToList(); + + if (data.format != null) + { + info.Container = data.format.format_name; + + if (!string.IsNullOrEmpty(data.format.bit_rate)) + { + info.Bitrate = int.Parse(data.format.bit_rate, _usCulture); + } + } + + if (isAudio) + { + SetAudioRuntimeTicks(data, info); + + if (data.format != null && data.format.tags != null) + { + SetAudioInfoFromTags(info, data.format.tags); + } + } + else + { + FetchWtvInfo(info, data); + + if (data.Chapters != null) + { + info.Chapters = data.Chapters.Select(GetChapterInfo).ToList(); + } + + ExtractTimestamp(info); + + var videoStream = info.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + + if (videoStream != null) + { + UpdateFromMediaInfo(info, videoStream); + } + } + + return info; + } + + /// + /// Converts ffprobe stream info to our MediaStream class + /// + /// The stream info. + /// The format info. + /// MediaStream. + private MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo) + { + var stream = new MediaStream + { + Codec = streamInfo.codec_name, + Profile = streamInfo.profile, + Level = streamInfo.level, + Index = streamInfo.index, + PixelFormat = streamInfo.pix_fmt + }; + + if (streamInfo.tags != null) + { + stream.Language = GetDictionaryValue(streamInfo.tags, "language"); + } + + if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) + { + stream.Type = MediaStreamType.Audio; + + stream.Channels = streamInfo.channels; + + if (!string.IsNullOrEmpty(streamInfo.sample_rate)) + { + stream.SampleRate = int.Parse(streamInfo.sample_rate, _usCulture); + } + + stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout); + } + else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) + { + stream.Type = MediaStreamType.Subtitle; + } + else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) + { + stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1 + ? MediaStreamType.EmbeddedImage + : MediaStreamType.Video; + + stream.Width = streamInfo.width; + stream.Height = streamInfo.height; + stream.AspectRatio = GetAspectRatio(streamInfo); + + stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); + stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); + + stream.BitDepth = GetBitDepth(stream.PixelFormat); + + //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || + // string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) || + // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); + + stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase); + } + else + { + return null; + } + + // Get stream bitrate + var bitrate = 0; + + if (!string.IsNullOrEmpty(streamInfo.bit_rate)) + { + bitrate = int.Parse(streamInfo.bit_rate, _usCulture); + } + else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video) + { + // If the stream info doesn't have a bitrate get the value from the media format info + bitrate = int.Parse(formatInfo.bit_rate, _usCulture); + } + + if (bitrate > 0) + { + stream.BitRate = bitrate; + } + + if (streamInfo.disposition != null) + { + var isDefault = GetDictionaryValue(streamInfo.disposition, "default"); + var isForced = GetDictionaryValue(streamInfo.disposition, "forced"); + + stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase); + + stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); + } + + return stream; + } + + private int? GetBitDepth(string pixelFormat) + { + var eightBit = new List + { + "yuv420p", + "yuv411p", + "yuvj420p", + "uyyvyy411", + "nv12", + "nv21", + "rgb444le", + "rgb444be", + "bgr444le", + "bgr444be", + "yuvj411p" + }; + + if (!string.IsNullOrEmpty(pixelFormat)) + { + if (eightBit.Contains(pixelFormat, StringComparer.OrdinalIgnoreCase)) + { + return 8; + } + } + + return null; + } + + /// + /// Gets a string from an FFProbeResult tags dictionary + /// + /// The tags. + /// The key. + /// System.String. + private string GetDictionaryValue(Dictionary tags, string key) + { + if (tags == null) + { + return null; + } + + string val; + + tags.TryGetValue(key, out val); + return val; + } + + private string ParseChannelLayout(string input) + { + if (string.IsNullOrEmpty(input)) + { + return input; + } + + return input.Split('(').FirstOrDefault(); + } + + private string GetAspectRatio(MediaStreamInfo info) + { + var original = info.display_aspect_ratio; + + int height; + int width; + + var parts = (original ?? string.Empty).Split(':'); + if (!(parts.Length == 2 && + int.TryParse(parts[0], NumberStyles.Any, _usCulture, out width) && + int.TryParse(parts[1], NumberStyles.Any, _usCulture, out height) && + width > 0 && + height > 0)) + { + width = info.width; + height = info.height; + } + + if (width > 0 && height > 0) + { + double ratio = width; + ratio /= height; + + if (IsClose(ratio, 1.777777778, .03)) + { + return "16:9"; + } + + if (IsClose(ratio, 1.3333333333, .05)) + { + return "4:3"; + } + + if (IsClose(ratio, 1.41)) + { + return "1.41:1"; + } + + if (IsClose(ratio, 1.5)) + { + return "1.5:1"; + } + + if (IsClose(ratio, 1.6)) + { + return "1.6:1"; + } + + if (IsClose(ratio, 1.66666666667)) + { + return "5:3"; + } + + if (IsClose(ratio, 1.85, .02)) + { + return "1.85:1"; + } + + if (IsClose(ratio, 2.35, .025)) + { + return "2.35:1"; + } + + if (IsClose(ratio, 2.4, .025)) + { + return "2.40:1"; + } + } + + return original; + } + + private bool IsClose(double d1, double d2, double variance = .005) + { + return Math.Abs(d1 - d2) <= variance; + } + + /// + /// Gets a frame rate from a string value in ffprobe output + /// This could be a number or in the format of 2997/125. + /// + /// The value. + /// System.Nullable{System.Single}. + private float? GetFrameRate(string value) + { + if (!string.IsNullOrEmpty(value)) + { + var parts = value.Split('/'); + + float result; + + if (parts.Length == 2) + { + result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture); + } + else + { + result = float.Parse(parts[0], _usCulture); + } + + return float.IsNaN(result) ? (float?)null : result; + } + + return null; + } + + private void SetAudioRuntimeTicks(InternalMediaInfoResult result, Model.Entities.MediaInfo data) + { + if (result.streams != null) + { + // Get the first audio stream + var stream = result.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); + + if (stream != null) + { + // Get duration from stream properties + var duration = stream.duration; + + // If it's not there go into format properties + if (string.IsNullOrEmpty(duration)) + { + duration = result.format.duration; + } + + // If we got something, parse it + if (!string.IsNullOrEmpty(duration)) + { + data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks; + } + } + } + } + + private void SetSize(InternalMediaInfoResult data, Model.Entities.MediaInfo info) + { + if (data.format != null) + { + if (!string.IsNullOrEmpty(data.format.size)) + { + info.Size = long.Parse(data.format.size, _usCulture); + } + else + { + info.Size = null; + } + } + } + + private void SetAudioInfoFromTags(Model.Entities.MediaInfo audio, Dictionary tags) + { + var title = FFProbeHelpers.GetDictionaryValue(tags, "title"); + + // Only set Name if title was found in the dictionary + if (!string.IsNullOrEmpty(title)) + { + audio.Title = title; + } + + var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer"); + + if (!string.IsNullOrWhiteSpace(composer)) + { + foreach (var person in Split(composer, false)) + { + audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer }); + } + } + + audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); + + var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists"); + + if (!string.IsNullOrWhiteSpace(artists)) + { + audio.Artists = artists.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + else + { + var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); + if (string.IsNullOrWhiteSpace(artist)) + { + audio.Artists.Clear(); + } + else + { + audio.Artists = SplitArtists(artist) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + } + + var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist"); + if (string.IsNullOrWhiteSpace(albumArtist)) + { + albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist"); + } + if (string.IsNullOrWhiteSpace(albumArtist)) + { + albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist"); + } + + if (string.IsNullOrWhiteSpace(albumArtist)) + { + audio.AlbumArtists = new List(); + } + else + { + audio.AlbumArtists = SplitArtists(albumArtist) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + } + + // Track number + audio.IndexNumber = GetDictionaryDiscValue(tags, "track"); + + // Disc number + audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc"); + + audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date"); + + // Several different forms of retaildate + audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? + FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? + FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? + FFProbeHelpers.GetDictionaryDateTime(tags, "date"); + + // If we don't have a ProductionYear try and get it from PremiereDate + if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) + { + audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year; + } + + FetchGenres(audio, tags); + + // There's several values in tags may or may not be present + FetchStudios(audio, tags, "organization"); + FetchStudios(audio, tags, "ensemble"); + FetchStudios(audio, tags, "publisher"); + + // These support mulitple values, but for now we only store the first. + audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id"))); + audio.SetProviderId(MetadataProviders.MusicBrainzArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id"))); + + audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id"))); + audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id"))); + audio.SetProviderId(MetadataProviders.MusicBrainzTrack, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id"))); + } + + private string GetMultipleMusicBrainzId(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } + + return value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) + .Select(i => i.Trim()) + .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + } + + private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' }; + + /// + /// Splits the specified val. + /// + /// The val. + /// if set to true [allow comma delimiter]. + /// System.String[][]. + private IEnumerable Split(string val, bool allowCommaDelimiter) + { + // Only use the comma as a delimeter if there are no slashes or pipes. + // We want to be careful not to split names that have commas in them + var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? + _nameDelimiters : + new[] { ',' }; + + return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => i.Trim()); + } + + private const string ArtistReplaceValue = " | "; + + private IEnumerable SplitArtists(string val) + { + val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase) + .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase); + + var artistsFound = new List(); + + foreach (var whitelistArtist in GetSplitWhitelist()) + { + var originalVal = val; + val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase); + + if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase)) + { + artistsFound.Add(whitelistArtist); + } + } + + // Only use the comma as a delimeter if there are no slashes or pipes. + // We want to be careful not to split names that have commas in them + var delimeter = _nameDelimiters; + + var artists = val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => i.Trim()); + + artistsFound.AddRange(artists); + return artistsFound; + } + + + private List _splitWhiteList = null; + + private IEnumerable GetSplitWhitelist() + { + if (_splitWhiteList == null) + { + var file = GetType().Namespace + ".whitelist.txt"; + + using (var stream = GetType().Assembly.GetManifestResourceStream(file)) + { + using (var reader = new StreamReader(stream)) + { + var list = new List(); + + while (!reader.EndOfStream) + { + var val = reader.ReadLine(); + + if (!string.IsNullOrWhiteSpace(val)) + { + list.Add(val); + } + } + + _splitWhiteList = list; + } + } + } + + return _splitWhiteList; + } + + /// + /// Gets the studios from the tags collection + /// + /// The audio. + /// The tags. + /// Name of the tag. + private void FetchStudios(Model.Entities.MediaInfo audio, Dictionary tags, string tagName) + { + var val = FFProbeHelpers.GetDictionaryValue(tags, tagName); + + if (!string.IsNullOrEmpty(val)) + { + var studios = Split(val, true); + + foreach (var studio in studios) + { + // Sometimes the artist name is listed here, account for that + if (audio.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + if (audio.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + audio.Studios.Add(studio); + } + + audio.Studios = audio.Studios + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + } + + /// + /// Gets the genres from the tags collection + /// + /// The information. + /// The tags. + private void FetchGenres(Model.Entities.MediaInfo info, Dictionary tags) + { + var val = FFProbeHelpers.GetDictionaryValue(tags, "genre"); + + if (!string.IsNullOrEmpty(val)) + { + foreach (var genre in Split(val, true)) + { + info.Genres.Add(genre); + } + + info.Genres = info.Genres + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + } + + /// + /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3' + /// + /// The tags. + /// Name of the tag. + /// System.Nullable{System.Int32}. + private int? GetDictionaryDiscValue(Dictionary tags, string tagName) + { + var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName); + + if (!string.IsNullOrEmpty(disc)) + { + disc = disc.Split('/')[0]; + + int num; + + if (int.TryParse(disc, out num)) + { + return num; + } + } + + return null; + } + + private ChapterInfo GetChapterInfo(MediaChapter chapter) + { + var info = new ChapterInfo(); + + if (chapter.tags != null) + { + string name; + if (chapter.tags.TryGetValue("title", out name)) + { + info.Name = name; + } + } + + // Limit accuracy to milliseconds to match xml saving + var secondsString = chapter.start_time; + double seconds; + + if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds)) + { + var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds); + info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks; + } + + return info; + } + + private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames) + + private void FetchWtvInfo(Model.Entities.MediaInfo video, InternalMediaInfoResult data) + { + if (data.format == null || data.format.tags == null) + { + return; + } + + var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre"); + + if (!string.IsNullOrWhiteSpace(genres)) + { + //genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre"); + } + + if (!string.IsNullOrWhiteSpace(genres)) + { + video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => i.Trim()) + .ToList(); + } + + var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); + + if (!string.IsNullOrWhiteSpace(officialRating)) + { + video.OfficialRating = officialRating; + } + + var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); + + if (!string.IsNullOrEmpty(people)) + { + video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonType.Actor }) + .ToList(); + } + + var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); + if (!string.IsNullOrWhiteSpace(year)) + { + int val; + + if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val)) + { + video.ProductionYear = val; + } + } + + var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime"); + if (!string.IsNullOrWhiteSpace(premiereDateString)) + { + DateTime val; + + // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ + // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None) + if (DateTime.TryParse(year, null, DateTimeStyles.None, out val)) + { + video.PremiereDate = val.ToUniversalTime(); + } + } + + var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); + + var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle"); + + // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ + + // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910 + // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION + // OR -> COMMENT. SUBTITLE: DESCRIPTION + // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S] + // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S] + if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename + { + string[] parts = description.Split(':'); + if (parts.Length > 0) + { + string subtitle = parts[0]; + try + { + if (subtitle.Contains("/")) // It contains a episode number and season number + { + string[] numbers = subtitle.Split(' '); + video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]); + int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]); + + description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it + } + else + throw new Exception(); // Switch to default parsing + } + catch // Default parsing + { + if (subtitle.Contains(".")) // skip the comment, keep the subtitle + description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first + else + description = subtitle.Trim(); // Clean up whitespaces and save it + } + } + } + + if (!string.IsNullOrWhiteSpace(description)) + { + video.Overview = description; + } + } + + private void ExtractTimestamp(Model.Entities.MediaInfo video) + { + if (video.VideoType == VideoType.VideoFile) + { + if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) || + string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) || + string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase)) + { + try + { + video.Timestamp = GetMpegTimestamp(video.Path); + + _logger.Debug("Video has {0} timestamp", video.Timestamp); + } + catch (Exception ex) + { + _logger.ErrorException("Error extracting timestamp info from {0}", ex, video.Path); + video.Timestamp = null; + } + } + } + } + + private TransportStreamTimestamp GetMpegTimestamp(string path) + { + var packetBuffer = new byte['Å']; + + using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + fs.Read(packetBuffer, 0, packetBuffer.Length); + } + + if (packetBuffer[0] == 71) + { + return TransportStreamTimestamp.None; + } + + if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71)) + { + if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0)) + { + return TransportStreamTimestamp.Zero; + } + + return TransportStreamTimestamp.Valid; + } + + return TransportStreamTimestamp.None; + } + + private void UpdateFromMediaInfo(MediaSourceInfo video, MediaStream videoStream) + { + if (video.VideoType == VideoType.VideoFile && video.Protocol == MediaProtocol.File) + { + if (videoStream != null) + { + try + { + var result = new MediaInfoLib().GetVideoInfo(video.Path); + + videoStream.IsCabac = result.IsCabac ?? videoStream.IsCabac; + videoStream.IsInterlaced = result.IsInterlaced ?? videoStream.IsInterlaced; + videoStream.BitDepth = result.BitDepth ?? videoStream.BitDepth; + videoStream.RefFrames = result.RefFrames; + } + catch (Exception ex) + { + _logger.ErrorException("Error running MediaInfo on {0}", ex, video.Path); + } + } + } + } + } +} diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index bc9f07d04a..daeb50225a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1,6 +1,7 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; using System; @@ -11,13 +12,16 @@ namespace MediaBrowser.Model.Dlna public class StreamBuilder { private readonly ILocalPlayer _localPlayer; + private readonly ILogger _logger; - public StreamBuilder(ILocalPlayer localPlayer) + public StreamBuilder(ILocalPlayer localPlayer, ILogger logger) { _localPlayer = localPlayer; + _logger = logger; } - public StreamBuilder() - : this(new NullLocalPlayer()) + + public StreamBuilder(ILogger logger) + : this(new NullLocalPlayer(), logger) { } @@ -353,6 +357,12 @@ namespace MediaBrowser.Model.Dlna bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options); bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options); + _logger.Debug("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", + options.Profile.Name ?? "Unknown Profile", + item.Path ?? "Unknown path", + isEligibleForDirectPlay, + isEligibleForDirectStream); + if (isEligibleForDirectPlay || isEligibleForDirectStream) { // See if it can be direct played @@ -504,6 +514,10 @@ namespace MediaBrowser.Model.Dlna if (directPlay == null) { + _logger.Debug("Profile: {0}, No direct play profiles found for Path: {1}", + profile.Name ?? "Unknown Profile", + mediaSource.Path ?? "Unknown path"); + return null; } @@ -550,6 +564,11 @@ namespace MediaBrowser.Model.Dlna { if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams)) { + _logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoContainerProfile.{1} Path: {2}", + profile.Name ?? "Unknown Profile", + i.Property, + mediaSource.Path ?? "Unknown path"); + return null; } } @@ -558,6 +577,10 @@ namespace MediaBrowser.Model.Dlna if (string.IsNullOrEmpty(videoCodec)) { + _logger.Debug("Profile: {0}, DirectPlay=false. Reason=Unknown video codec. Path: {1}", + profile.Name ?? "Unknown Profile", + mediaSource.Path ?? "Unknown path"); + return null; } @@ -577,6 +600,11 @@ namespace MediaBrowser.Model.Dlna { if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams)) { + _logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoCodecProfile.{1} Path: {2}", + profile.Name ?? "Unknown Profile", + i.Property, + mediaSource.Path ?? "Unknown path"); + return null; } } @@ -587,6 +615,10 @@ namespace MediaBrowser.Model.Dlna if (string.IsNullOrEmpty(audioCodec)) { + _logger.Debug("Profile: {0}, DirectPlay=false. Reason=Unknown audio codec. Path: {1}", + profile.Name ?? "Unknown Profile", + mediaSource.Path ?? "Unknown path"); + return null; } @@ -607,6 +639,11 @@ namespace MediaBrowser.Model.Dlna bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream); if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio)) { + _logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoAudioCodecProfile.{1} Path: {2}", + profile.Name ?? "Unknown Profile", + i.Property, + mediaSource.Path ?? "Unknown path"); + return null; } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index a908c78506..f6ff79b115 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -70,6 +70,7 @@ namespace MediaBrowser.Model.Dlna public string SubtitleFormat { get; set; } public string PlaySessionId { get; set; } + public List AllMediaSources { get; set; } public string MediaSourceId { diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs index 46485316e0..8e77505627 100644 --- a/MediaBrowser.Model/Dto/BaseItemPerson.cs +++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs @@ -1,7 +1,7 @@ -using System.ComponentModel; +using MediaBrowser.Model.Extensions; +using System.ComponentModel; using System.Diagnostics; using System.Runtime.Serialization; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Dto { diff --git a/MediaBrowser.Model/Entities/MediaInfo.cs b/MediaBrowser.Model/Entities/MediaInfo.cs index ef26cfa148..67efe31087 100644 --- a/MediaBrowser.Model/Entities/MediaInfo.cs +++ b/MediaBrowser.Model/Entities/MediaInfo.cs @@ -1,26 +1,65 @@ +using MediaBrowser.Model.Dto; +using System; using System.Collections.Generic; namespace MediaBrowser.Model.Entities { - public class MediaInfo + public class MediaInfo : MediaSourceInfo, IHasProviderIds { + public List Chapters { get; set; } + /// - /// Gets or sets the media streams. + /// Gets or sets the title. /// - /// The media streams. - public List MediaStreams { get; set; } - + /// The title. + public string Title { get; set; } /// - /// Gets or sets the format. + /// Gets or sets the album. /// - /// The format. - public string Format { get; set; } - - public int? TotalBitrate { get; set; } + /// The album. + public string Album { get; set; } + /// + /// Gets or sets the artists. + /// + /// The artists. + public List Artists { get; set; } + /// + /// Gets or sets the album artists. + /// + /// The album artists. + public List AlbumArtists { get; set; } + /// + /// Gets or sets the studios. + /// + /// The studios. + public List Studios { get; set; } + public List Genres { get; set; } + public int? IndexNumber { get; set; } + public int? ParentIndexNumber { get; set; } + public int? ProductionYear { get; set; } + public DateTime? PremiereDate { get; set; } + public List People { get; set; } + public Dictionary ProviderIds { get; set; } + /// + /// Gets or sets the official rating. + /// + /// The official rating. + public string OfficialRating { get; set; } + /// + /// Gets or sets the overview. + /// + /// The overview. + public string Overview { get; set; } public MediaInfo() { - MediaStreams = new List(); + Chapters = new List(); + Artists = new List(); + AlbumArtists = new List(); + Studios = new List(); + Genres = new List(); + People = new List(); + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); } } } \ No newline at end of file diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 3b5103f209..b58c6648ab 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -102,7 +102,6 @@ - @@ -198,10 +197,6 @@ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2} MediaBrowser.Controller - - {6e4145e4-c6d4-4e4d-94f2-87188db6e239} - MediaBrowser.MediaInfo - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index ea191dd08f..b1bed73109 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -8,8 +7,6 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -49,18 +46,14 @@ namespace MediaBrowser.Providers.MediaInfo cancellationToken.ThrowIfCancellationRequested(); - FFProbeHelpers.NormalizeFFProbeResult(result); - - cancellationToken.ThrowIfCancellationRequested(); - await Fetch(item, cancellationToken, result).ConfigureAwait(false); return ItemUpdateType.MetadataImport; } - private const string SchemaVersion = "1"; + private const string SchemaVersion = "2"; - private async Task GetMediaInfo(BaseItem item, CancellationToken cancellationToken) + private async Task GetMediaInfo(BaseItem item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -71,7 +64,7 @@ namespace MediaBrowser.Providers.MediaInfo try { - return _json.DeserializeFromFile(cachePath); + return _json.DeserializeFromFile(cachePath); } catch (FileNotFoundException) { @@ -83,7 +76,7 @@ namespace MediaBrowser.Providers.MediaInfo var inputPath = new[] { item.Path }; - var result = await _mediaEncoder.GetMediaInfo(inputPath, MediaProtocol.File, false, cancellationToken).ConfigureAwait(false); + var result = await _mediaEncoder.GetMediaInfo(inputPath, item.Path, MediaProtocol.File, true, false, cancellationToken).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); _json.SerializeToFile(result, cachePath); @@ -96,61 +89,23 @@ namespace MediaBrowser.Providers.MediaInfo /// /// The audio. /// The cancellation token. - /// The data. + /// The media information. /// Task. - protected Task Fetch(Audio audio, CancellationToken cancellationToken, InternalMediaInfoResult data) + protected Task Fetch(Audio audio, CancellationToken cancellationToken, Model.Entities.MediaInfo mediaInfo) { - var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data); var mediaStreams = mediaInfo.MediaStreams; - audio.FormatName = mediaInfo.Format; - audio.TotalBitrate = mediaInfo.TotalBitrate; + audio.FormatName = mediaInfo.Container; + audio.TotalBitrate = mediaInfo.Bitrate; audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.EmbeddedImage); - if (data.streams != null) - { - // Get the first audio stream - var stream = data.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); + audio.RunTimeTicks = mediaInfo.RunTimeTicks; + audio.Size = mediaInfo.Size; - if (stream != null) - { - // Get duration from stream properties - var duration = stream.duration; + var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.'); + audio.Container = extension; - // If it's not there go into format properties - if (string.IsNullOrEmpty(duration)) - { - duration = data.format.duration; - } - - // If we got something, parse it - if (!string.IsNullOrEmpty(duration)) - { - audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks; - } - } - } - - if (data.format != null) - { - var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.'); - - audio.Container = extension; - - if (!string.IsNullOrEmpty(data.format.size)) - { - audio.Size = long.Parse(data.format.size, _usCulture); - } - else - { - audio.Size = null; - } - - if (data.format.tags != null) - { - FetchDataFromTags(audio, data.format.tags); - } - } + FetchDataFromTags(audio, mediaInfo); return _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken); } @@ -159,92 +114,36 @@ namespace MediaBrowser.Providers.MediaInfo /// Fetches data from the tags dictionary /// /// The audio. - /// The tags. - private void FetchDataFromTags(Audio audio, Dictionary tags) + /// The data. + private void FetchDataFromTags(Audio audio, Model.Entities.MediaInfo data) { - var title = FFProbeHelpers.GetDictionaryValue(tags, "title"); - // Only set Name if title was found in the dictionary - if (!string.IsNullOrEmpty(title)) + if (!string.IsNullOrEmpty(data.Title)) { - audio.Name = title; + audio.Name = data.Title; } if (!audio.LockedFields.Contains(MetadataFields.Cast)) { audio.People.Clear(); - var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer"); - - if (!string.IsNullOrWhiteSpace(composer)) + foreach (var person in data.People) { - foreach (var person in Split(composer, false)) + audio.AddPerson(new PersonInfo { - audio.AddPerson(new PersonInfo { Name = person, Type = PersonType.Composer }); - } - } - } - - audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); - - var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists"); - - if (!string.IsNullOrWhiteSpace(artists)) - { - audio.Artists = artists.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - else - { - var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); - if (string.IsNullOrWhiteSpace(artist)) - { - audio.Artists.Clear(); + Name = person.Name, + Type = person.Type, + Role = person.Role + }); } - else - { - audio.Artists = SplitArtists(artist) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - } - - var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist"); - if (string.IsNullOrWhiteSpace(albumArtist)) - { - albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist"); - } - if (string.IsNullOrWhiteSpace(albumArtist)) - { - albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist"); } - if (string.IsNullOrWhiteSpace(albumArtist)) - { - audio.AlbumArtists = new List(); - } - else - { - audio.AlbumArtists = SplitArtists(albumArtist) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - } - - // Track number - audio.IndexNumber = GetDictionaryDiscValue(tags, "track"); - - // Disc number - audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc"); - - audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date"); - - // Several different forms of retaildate - audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "date"); + audio.Album = data.Album; + audio.Artists = data.Artists; + audio.AlbumArtists = data.AlbumArtists; + audio.IndexNumber = data.IndexNumber; + audio.ProductionYear = data.ProductionYear; + audio.PremiereDate = data.PremiereDate; // If we don't have a ProductionYear try and get it from PremiereDate if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) @@ -253,193 +152,30 @@ namespace MediaBrowser.Providers.MediaInfo } if (!audio.LockedFields.Contains(MetadataFields.Genres)) - { - FetchGenres(audio, tags); - } - - if (!audio.LockedFields.Contains(MetadataFields.Studios)) - { - audio.Studios.Clear(); - - // There's several values in tags may or may not be present - FetchStudios(audio, tags, "organization"); - FetchStudios(audio, tags, "ensemble"); - FetchStudios(audio, tags, "publisher"); - } - - // These support mulitple values, but for now we only store the first. - audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id"))); - audio.SetProviderId(MetadataProviders.MusicBrainzArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id"))); - - audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id"))); - audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id"))); - audio.SetProviderId(MetadataProviders.MusicBrainzTrack, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id"))); - } - - private string GetMultipleMusicBrainzId(string value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return null; - } - - return value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) - .Select(i => i.Trim()) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); - } - - private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' }; - - /// - /// Splits the specified val. - /// - /// The val. - /// if set to true [allow comma delimiter]. - /// System.String[][]. - private IEnumerable Split(string val, bool allowCommaDelimiter) - { - // Only use the comma as a delimeter if there are no slashes or pipes. - // We want to be careful not to split names that have commas in them - var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? - _nameDelimiters : - new[] { ',' }; - - return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()); - } - - private const string ArtistReplaceValue = " | "; - - private IEnumerable SplitArtists(string val) - { - val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase) - .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase); - - var artistsFound = new List(); - - foreach (var whitelistArtist in GetSplitWhitelist()) - { - var originalVal = val; - val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase); - - if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase)) - { - artistsFound.Add(whitelistArtist); - } - } - - // Only use the comma as a delimeter if there are no slashes or pipes. - // We want to be careful not to split names that have commas in them - var delimeter = _nameDelimiters; - - var artists = val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()); - - artistsFound.AddRange(artists); - return artistsFound; - } - - - private List _splitWhiteList = null; - - private IEnumerable GetSplitWhitelist() - { - if (_splitWhiteList == null) - { - var file = GetType().Namespace + ".whitelist.txt"; - - using (var stream = GetType().Assembly.GetManifestResourceStream(file)) - { - using (var reader = new StreamReader(stream)) - { - var list = new List(); - - while (!reader.EndOfStream) - { - var val = reader.ReadLine(); - - if (!string.IsNullOrWhiteSpace(val)) - { - list.Add(val); - } - } - - _splitWhiteList = list; - } - } - } - - return _splitWhiteList; - } - - /// - /// Gets the studios from the tags collection - /// - /// The audio. - /// The tags. - /// Name of the tag. - private void FetchStudios(Audio audio, Dictionary tags, string tagName) - { - var val = FFProbeHelpers.GetDictionaryValue(tags, tagName); - - if (!string.IsNullOrEmpty(val)) - { - // Sometimes the artist name is listed here, account for that - var studios = Split(val, true).Where(i => !audio.HasAnyArtist(i)); - - foreach (var studio in studios) - { - audio.AddStudio(studio); - } - } - } - - /// - /// Gets the genres from the tags collection - /// - /// The audio. - /// The tags. - private void FetchGenres(Audio audio, Dictionary tags) - { - var val = FFProbeHelpers.GetDictionaryValue(tags, "genre"); - - if (!string.IsNullOrEmpty(val)) { audio.Genres.Clear(); - foreach (var genre in Split(val, true)) + foreach (var genre in data.Genres) { audio.AddGenre(genre); } } - } - - /// - /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3' - /// - /// The tags. - /// Name of the tag. - /// System.Nullable{System.Int32}. - private int? GetDictionaryDiscValue(Dictionary tags, string tagName) - { - var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName); - if (!string.IsNullOrEmpty(disc)) + if (!audio.LockedFields.Contains(MetadataFields.Studios)) { - disc = disc.Split('/')[0]; - - int num; + audio.Studios.Clear(); - if (int.TryParse(disc, out num)) + foreach (var studio in data.Studios) { - return num; + audio.AddStudio(studio); } } - return null; + audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, data.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist)); + audio.SetProviderId(MetadataProviders.MusicBrainzArtist, data.GetProviderId(MetadataProviders.MusicBrainzArtist)); + audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, data.GetProviderId(MetadataProviders.MusicBrainzAlbum)); + audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, data.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup)); + audio.SetProviderId(MetadataProviders.MusicBrainzTrack, data.GetProviderId(MetadataProviders.MusicBrainzTrack)); } - } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index ca11f858af..cec66f3c14 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -13,7 +13,6 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; -using MediaBrowser.MediaInfo; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -116,10 +115,6 @@ namespace MediaBrowser.Providers.MediaInfo cancellationToken.ThrowIfCancellationRequested(); - FFProbeHelpers.NormalizeFFProbeResult(result); - - cancellationToken.ThrowIfCancellationRequested(); - await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, options).ConfigureAwait(false); } @@ -136,7 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo private const string SchemaVersion = "1"; - private async Task GetMediaInfo(Video item, + private async Task GetMediaInfo(Video item, IIsoMount isoMount, CancellationToken cancellationToken) { @@ -149,7 +144,7 @@ namespace MediaBrowser.Providers.MediaInfo try { - return _json.DeserializeFromFile(cachePath); + return _json.DeserializeFromFile(cachePath); } catch (FileNotFoundException) { @@ -165,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, protocol, isoMount, item.PlayableStreamFileNames); - var result = await _mediaEncoder.GetMediaInfo(inputPath, protocol, false, cancellationToken).ConfigureAwait(false); + var result = await _mediaEncoder.GetMediaInfo(inputPath, item.Path, protocol, false, true, cancellationToken).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); _json.SerializeToFile(result, cachePath); @@ -175,52 +170,37 @@ namespace MediaBrowser.Providers.MediaInfo protected async Task Fetch(Video video, CancellationToken cancellationToken, - InternalMediaInfoResult data, + Model.Entities.MediaInfo mediaInfo, IIsoMount isoMount, BlurayDiscInfo blurayInfo, MetadataRefreshOptions options) { - var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data); var mediaStreams = mediaInfo.MediaStreams; - video.TotalBitrate = mediaInfo.TotalBitrate; - video.FormatName = (mediaInfo.Format ?? string.Empty) + video.TotalBitrate = mediaInfo.Bitrate; + video.FormatName = (mediaInfo.Container ?? string.Empty) .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); - if (data.format != null) - { - // For dvd's this may not always be accurate, so don't set the runtime if the item already has one - var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0; - - if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration)) - { - video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks; - } + // For dvd's this may not always be accurate, so don't set the runtime if the item already has one + var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0; - if (video.VideoType == VideoType.VideoFile) - { - var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.'); + if (needToSetRuntime) + { + video.RunTimeTicks = mediaInfo.RunTimeTicks; + } - video.Container = extension; - } - else - { - video.Container = null; - } + if (video.VideoType == VideoType.VideoFile) + { + var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.'); - if (!string.IsNullOrEmpty(data.format.size)) - { - video.Size = long.Parse(data.format.size, _usCulture); - } - else - { - video.Size = null; - } + video.Container = extension; + } + else + { + video.Container = null; } - var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList(); - var chapters = mediaChapters.Select(GetChapterInfo).ToList(); - + var chapters = mediaInfo.Chapters ?? new List(); if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay)) { FetchBdInfo(video, chapters, mediaStreams, blurayInfo); @@ -228,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - FetchWtvInfo(video, data); + FetchEmbeddedInfo(video, mediaInfo); video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270); @@ -238,9 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index; video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); - - ExtractTimestamp(video); - UpdateFromMediaInfo(video, videoStream); + video.Timestamp = mediaInfo.Timestamp; await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false); @@ -283,29 +261,6 @@ namespace MediaBrowser.Providers.MediaInfo } } - private void UpdateFromMediaInfo(Video video, MediaStream videoStream) - { - if (video.VideoType == VideoType.VideoFile && video.LocationType != LocationType.Remote && video.LocationType != LocationType.Virtual) - { - if (videoStream != null) - { - try - { - var result = new MediaInfoLib().GetVideoInfo(video.Path); - - videoStream.IsCabac = result.IsCabac ?? videoStream.IsCabac; - videoStream.IsInterlaced = result.IsInterlaced ?? videoStream.IsInterlaced; - videoStream.BitDepth = result.BitDepth ?? videoStream.BitDepth; - videoStream.RefFrames = result.RefFrames; - } - catch (Exception ex) - { - _logger.ErrorException("Error running MediaInfo on {0}", ex, video.Path); - } - } - } - } - private void NormalizeChapterNames(List chapters) { var index = 1; @@ -325,32 +280,6 @@ namespace MediaBrowser.Providers.MediaInfo } } - private ChapterInfo GetChapterInfo(MediaChapter chapter) - { - var info = new ChapterInfo(); - - if (chapter.tags != null) - { - string name; - if (chapter.tags.TryGetValue("title", out name)) - { - info.Name = name; - } - } - - // Limit accuracy to milliseconds to match xml saving - var secondsString = chapter.start_time; - double seconds; - - if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds)) - { - var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds); - info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks; - } - - return info; - } - private void FetchBdInfo(BaseItem item, List chapters, List mediaStreams, BlurayDiscInfo blurayInfo) { var video = (Video)item; @@ -419,129 +348,79 @@ namespace MediaBrowser.Providers.MediaInfo return _blurayExaminer.GetDiscInfo(path); } - public const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames) - - private void FetchWtvInfo(Video video, InternalMediaInfoResult data) + private void FetchEmbeddedInfo(Video video, Model.Entities.MediaInfo data) { - if (data.format == null || data.format.tags == null) - { - return; - } - - if (!video.LockedFields.Contains(MetadataFields.Genres)) - { - var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre"); - - if (!string.IsNullOrWhiteSpace(genres)) - { - //genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre"); - } - - if (!string.IsNullOrWhiteSpace(genres)) - { - video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()) - .ToList(); - } - } - if (!video.LockedFields.Contains(MetadataFields.OfficialRating)) { - var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); - - if (!string.IsNullOrWhiteSpace(officialRating)) + if (!string.IsNullOrWhiteSpace(data.OfficialRating)) { - video.OfficialRating = officialRating; + video.OfficialRating = data.OfficialRating; } } if (!video.LockedFields.Contains(MetadataFields.Cast)) { - var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); + video.People.Clear(); - if (!string.IsNullOrEmpty(people)) + foreach (var person in data.People) { - video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor }) - .ToList(); + video.AddPerson(new PersonInfo + { + Name = person.Name, + Type = person.Type, + Role = person.Role + }); } } - var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); - if (!string.IsNullOrWhiteSpace(year)) + if (!video.LockedFields.Contains(MetadataFields.Genres)) { - int val; + video.Genres.Clear(); - if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val)) + foreach (var genre in data.Genres) { - video.ProductionYear = val; + video.AddGenre(genre); } } - var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime"); - if (!string.IsNullOrWhiteSpace(premiereDateString)) + if (!video.LockedFields.Contains(MetadataFields.Studios)) { - DateTime val; + video.Studios.Clear(); - // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ - // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None) - if (DateTime.TryParse(year, null, DateTimeStyles.None, out val)) + foreach (var studio in data.Studios) { - video.PremiereDate = val.ToUniversalTime(); + video.AddStudio(studio); } } - var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); - - var episode = video as Episode; - if (episode != null) + if (data.ProductionYear.HasValue) { - var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle"); - - // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ - - // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910 - // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION - // OR -> COMMENT. SUBTITLE: DESCRIPTION - // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S] - // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S] - if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename - { - string[] parts = description.Split(':'); - if (parts.Length > 0) - { - string subtitle = parts[0]; - try - { - if (subtitle.Contains("/")) // It contains a episode number and season number - { - string[] numbers = subtitle.Split(' '); - episode.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]); - int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]); + video.ProductionYear = data.ProductionYear; + } + if (data.PremiereDate.HasValue) + { + video.PremiereDate = data.PremiereDate; + } + if (data.IndexNumber.HasValue) + { + video.IndexNumber = data.IndexNumber; + } + if (data.ParentIndexNumber.HasValue) + { + video.ParentIndexNumber = data.ParentIndexNumber; + } - description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it - } - else - throw new Exception(); // Switch to default parsing - } - catch // Default parsing - { - if (subtitle.Contains(".")) // skip the comment, keep the subtitle - description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first - else - description = subtitle.Trim(); // Clean up whitespaces and save it - } - } - } + // If we don't have a ProductionYear try and get it from PremiereDate + if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue) + { + video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year; } if (!video.LockedFields.Contains(MetadataFields.Overview)) { - if (!string.IsNullOrWhiteSpace(description)) + if (!string.IsNullOrWhiteSpace(data.Overview)) { - video.Overview = description; + video.Overview = data.Overview; } } } @@ -709,56 +588,6 @@ namespace MediaBrowser.Providers.MediaInfo } } - private void ExtractTimestamp(Video video) - { - if (video.VideoType == VideoType.VideoFile) - { - if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) || - string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) || - string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase)) - { - try - { - video.Timestamp = GetMpegTimestamp(video.Path); - - _logger.Debug("Video has {0} timestamp", video.Timestamp); - } - catch (Exception ex) - { - _logger.ErrorException("Error extracting timestamp info from {0}", ex, video.Path); - video.Timestamp = null; - } - } - } - } - - private TransportStreamTimestamp GetMpegTimestamp(string path) - { - var packetBuffer = new byte['Å']; - - using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - fs.Read(packetBuffer, 0, packetBuffer.Length); - } - - if (packetBuffer[0] == 71) - { - return TransportStreamTimestamp.None; - } - - if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71)) - { - if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0)) - { - return TransportStreamTimestamp.Zero; - } - - return TransportStreamTimestamp.Valid; - } - - return TransportStreamTimestamp.None; - } - private void FetchFromDvdLib(Video item, IIsoMount mount) { var path = mount == null ? item.Path : mount.MountedPath; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index d549cad461..18cc172ccb 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; @@ -18,12 +19,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; private readonly IMediaSourceManager _mediaSourceManager; + private readonly IMediaEncoder _mediaEncoder; - public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager) + public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) { _liveTvManager = liveTvManager; _jsonSerializer = jsonSerializer; _mediaSourceManager = mediaSourceManager; + _mediaEncoder = mediaEncoder; _logger = logManager.GetLogger(GetType().Name); } @@ -90,14 +93,86 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task OpenMediaSource(string openToken, CancellationToken cancellationToken) { + MediaSourceInfo stream; + var isAudio = false; + var keys = openToken.Split(new[] { '|' }, 2); if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase)) { - return await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false); + stream = await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false); + } + else + { + stream = await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false); + } + + try + { + await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error probing live tv stream", ex); + } + + return stream; + } + + private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) + { + var inputPaths = new[] { mediaSource.Path }; + + var info = await _mediaEncoder.GetMediaInfo(inputPaths, mediaSource.Path, mediaSource.Protocol, isAudio, false, cancellationToken) + .ConfigureAwait(false); + + mediaSource.Bitrate = info.Bitrate; + mediaSource.Container = info.Container; + mediaSource.Formats = info.Formats; + mediaSource.MediaStreams = info.MediaStreams; + mediaSource.RunTimeTicks = info.RunTimeTicks; + mediaSource.Size = info.Size; + mediaSource.Timestamp = info.Timestamp; + mediaSource.Video3DFormat = info.Video3DFormat; + mediaSource.VideoType = info.VideoType; + + mediaSource.DefaultSubtitleStreamIndex = null; + + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Audio); + + if (audioStream == null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; } - return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false); + // Try to estimate this + if (!mediaSource.Bitrate.HasValue) + { + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Video); + if (videoStream != null) + { + var width = videoStream.Width ?? 1920; + + if (width >= 1900) + { + mediaSource.Bitrate = 10000000; + } + + else if (width >= 1260) + { + mediaSource.Bitrate = 6000000; + } + + else if (width >= 700) + { + mediaSource.Bitrate = 4000000; + } + } + } } public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index 185c3464ea..d8fc425d1a 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -493,7 +493,7 @@ namespace MediaBrowser.Server.Implementations.Sync conversionOptions.ItemId = item.Id.ToString("N"); conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); - var streamInfo = new StreamBuilder().BuildVideoItem(conversionOptions); + var streamInfo = new StreamBuilder(_logger).BuildVideoItem(conversionOptions); var mediaSource = streamInfo.MediaSource; // No sense creating external subs if we're already burning one into the video @@ -690,7 +690,7 @@ namespace MediaBrowser.Server.Implementations.Sync conversionOptions.ItemId = item.Id.ToString("N"); conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); - var streamInfo = new StreamBuilder().BuildAudioItem(conversionOptions); + var streamInfo = new StreamBuilder(_logger).BuildAudioItem(conversionOptions); var mediaSource = streamInfo.MediaSource; jobItem.MediaSourceId = streamInfo.MediaSourceId; diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 9ecfe2b201..bfc95a2969 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.613 + 3.0.615 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 18078aef37..77d5242a98 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.613 + 3.0.615 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index f84c447298..12b5eb442c 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.613 + 3.0.615 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 1d28c22a89..cbf4e95798 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.613 + 3.0.615 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + diff --git a/SharedVersion.cs b/SharedVersion.cs index 8177f8cca8..405e1c708a 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -//[assembly: AssemblyVersion("3.0.*")] -[assembly: AssemblyVersion("3.0.5572.0")] +[assembly: AssemblyVersion("3.0.*")] +//[assembly: AssemblyVersion("3.0.5572.0")] From 30104bd8de62715d127823e69dc0de9e65d99840 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 5 Apr 2015 11:01:57 -0400 Subject: [PATCH 17/54] probe live streams after opening --- MediaBrowser.Api/Sync/SyncService.cs | 11 ++-- .../Entities/Audio/MusicArtist.cs | 1 + MediaBrowser.Controller/Entities/Folder.cs | 3 +- .../Entities/Movies/BoxSet.cs | 18 +++--- .../Entities/PhotoAlbum.cs | 36 +++++++++++- .../Entities/UserViewBuilder.cs | 2 +- .../IO}/ThrottledStream.cs | 2 +- .../MediaBrowser.Controller.csproj | 3 + .../MediaEncoding/IMediaEncoder.cs | 8 +-- .../MediaEncoding/MediaInfoRequest.cs | 24 ++++++++ .../Sync/IRemoteSyncProvider.cs | 10 ++++ MediaBrowser.Controller/Sync/ISyncManager.cs | 14 +++++ .../ContentDirectory/ControlHandler.cs | 8 +-- .../Encoder/MediaEncoder.cs | 20 +++---- .../Probing/ProbeResultNormalizer.cs | 18 +++--- .../MediaBrowser.Model.Portable.csproj | 6 +- .../MediaBrowser.Model.net35.csproj | 6 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- .../{Entities => MediaInfo}/MediaInfo.cs | 3 +- MediaBrowser.Model/Sync/SyncOptions.cs | 1 + MediaBrowser.Model/Sync/SyncQualityOption.cs | 5 ++ MediaBrowser.Model/Users/UserPolicy.cs | 10 +++- .../BoxSets/BoxSetMetadataService.cs | 6 +- .../MediaInfo/FFProbeAudioInfo.cs | 17 ++++-- .../MediaInfo/FFProbeVideoInfo.cs | 23 +++++--- .../Collections/ManualCollectionsFolder.cs | 4 +- .../Library/MediaSourceManager.cs | 27 ++++++--- .../Library/Resolvers/Movies/MovieResolver.cs | 2 +- .../LiveTv/LiveTvMediaSourceProvider.cs | 10 +++- .../Localization/Server/server.json | 8 ++- ...MediaBrowser.Server.Implementations.csproj | 1 - .../Photos/PhotoAlbumImageProvider.cs | 2 +- .../Sync/AppSyncProvider.cs | 2 +- .../Sync/MediaSync.cs | 27 ++++++--- .../Sync/MultiProviderSync.cs | 9 ++- .../Sync/ServerSyncScheduledTask.cs | 9 ++- .../Sync/SyncManager.cs | 56 +++++++++++++------ Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 41 files changed, 294 insertions(+), 132 deletions(-) rename {MediaBrowser.Server.Implementations/HttpServer => MediaBrowser.Controller/IO}/ThrottledStream.cs (99%) create mode 100644 MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs create mode 100644 MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs rename MediaBrowser.Model/{Entities => MediaInfo}/MediaInfo.cs (96%) diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs index b9dbf59469..d5f88e6a43 100644 --- a/MediaBrowser.Api/Sync/SyncService.cs +++ b/MediaBrowser.Api/Sync/SyncService.cs @@ -248,6 +248,9 @@ namespace MediaBrowser.Api.Sync result.Targets = _syncManager.GetSyncTargets(request.UserId) .ToList(); + var auth = AuthorizationContext.GetAuthorizationInfo(Request); + var authenticatedUser = _userManager.GetUserById(auth.UserId); + if (!string.IsNullOrWhiteSpace(request.TargetId)) { result.Targets = result.Targets @@ -255,11 +258,11 @@ namespace MediaBrowser.Api.Sync .ToList(); result.QualityOptions = _syncManager - .GetQualityOptions(request.TargetId) + .GetQualityOptions(request.TargetId, authenticatedUser) .ToList(); result.ProfileOptions = _syncManager - .GetProfileOptions(request.TargetId) + .GetProfileOptions(request.TargetId, authenticatedUser) .ToList(); } @@ -277,10 +280,6 @@ namespace MediaBrowser.Api.Sync } }; - var auth = AuthorizationContext.GetAuthorizationInfo(Request); - - var authenticatedUser = _userManager.GetUserById(auth.UserId); - var items = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(_libraryManager.GetItemById) .Where(i => i != null); diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 4185590ab3..e0c14821e9 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Users; using System; using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 14095f7ffc..61e5acdb3f 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -991,8 +991,9 @@ namespace MediaBrowser.Controller.Entities } var locations = user.RootFolder - .GetChildren(user, true) + .Children .OfType() + .Where(i => i.IsVisible(user)) .SelectMany(i => i.PhysicalLocations) .ToList(); diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 0778643da5..02e9d4cf9e 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -175,19 +175,19 @@ namespace MediaBrowser.Controller.Entities.Movies public override bool IsVisible(User user) { - if (base.IsVisible(user)) - { - var userId = user.Id.ToString("N"); - - // Need to check Count > 0 for boxsets created prior to the introduction of Shares - if (Shares.Count > 0 && !Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase))) - { - //return false; - } + var userId = user.Id.ToString("N"); + // Need to check Count > 0 for boxsets created prior to the introduction of Shares + if (Shares.Count > 0 && Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase))) + { return true; } + if (base.IsVisible(user)) + { + return GetChildren(user, true).Any(); + } + return false; } } diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs index 24ebf88153..5b48a70e9c 100644 --- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs +++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs @@ -1,11 +1,15 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Users; +using System; using System.Linq; using System.Runtime.Serialization; -using MediaBrowser.Model.Users; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities { - public class PhotoAlbum : Folder + public class PhotoAlbum : Folder, IMetadataContainer { public override bool SupportsLocalMetadata { @@ -28,5 +32,31 @@ namespace MediaBrowser.Controller.Entities { return config.BlockUnratedItems.Contains(UnratedItem.Other); } + + public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress progress, CancellationToken cancellationToken) + { + var items = GetRecursiveChildren().ToList(); + + var totalItems = items.Count; + var numComplete = 0; + + // Refresh songs + foreach (var item in items) + { + cancellationToken.ThrowIfCancellationRequested(); + + await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + + numComplete++; + double percent = numComplete; + percent /= totalItems; + progress.Report(percent * 100); + } + + // Refresh current item + await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + + progress.Report(100); + } } } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 0e602dabe2..c01814bce2 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -259,7 +259,7 @@ namespace MediaBrowser.Controller.Entities list.Add(await GetUserView(SpecialFolder.MusicLatest, user, "0", parent).ConfigureAwait(false)); list.Add(await GetUserView(SpecialFolder.MusicAlbums, user, "1", parent).ConfigureAwait(false)); list.Add(await GetUserView(SpecialFolder.MusicAlbumArtists, user, "2", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicArtists, user, "3", parent).ConfigureAwait(false)); + //list.Add(await GetUserView(SpecialFolder.MusicArtists, user, "3", parent).ConfigureAwait(false)); list.Add(await GetUserView(SpecialFolder.MusicSongs, user, "4", parent).ConfigureAwait(false)); list.Add(await GetUserView(SpecialFolder.MusicGenres, user, "5", parent).ConfigureAwait(false)); list.Add(await GetUserView(SpecialFolder.MusicFavorites, user, "6", parent).ConfigureAwait(false)); diff --git a/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs b/MediaBrowser.Controller/IO/ThrottledStream.cs similarity index 99% rename from MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs rename to MediaBrowser.Controller/IO/ThrottledStream.cs index 1c01fa9e04..1df00b45a2 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs +++ b/MediaBrowser.Controller/IO/ThrottledStream.cs @@ -3,7 +3,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace MediaBrowser.Server.Implementations.HttpServer +namespace MediaBrowser.Controller.IO { /// /// Class for streaming data with throttling support. diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 9a4a2cb62c..8c4154966f 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -171,6 +171,7 @@ + @@ -212,6 +213,7 @@ + @@ -393,6 +395,7 @@ + diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index bb56748642..5bec7980aa 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -67,14 +67,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Gets the media info. /// - /// The input files. - /// The primary path. - /// The protocol. - /// if set to true [is audio]. - /// if set to true [extract chapters]. + /// The request. /// The cancellation token. /// Task. - Task GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio, bool extractChapters, CancellationToken cancellationToken); + Task GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken); /// /// Gets the probe size argument. diff --git a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs new file mode 100644 index 0000000000..ca0c2fdbb4 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs @@ -0,0 +1,24 @@ +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.MediaEncoding +{ + public class MediaInfoRequest + { + public string InputPath { get; set; } + public MediaProtocol Protocol { get; set; } + public bool ExtractChapters { get; set; } + public DlnaProfileType MediaType { get; set; } + public IIsoMount MountedIso { get; set; } + public VideoType VideoType { get; set; } + public List PlayableStreamFileNames { get; set; } + + public MediaInfoRequest() + { + PlayableStreamFileNames = new List(); + } + } +} diff --git a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs b/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs new file mode 100644 index 0000000000..aeb7a3bff3 --- /dev/null +++ b/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs @@ -0,0 +1,10 @@ + +namespace MediaBrowser.Controller.Sync +{ + /// + /// A marker interface + /// + public interface IRemoteSyncProvider + { + } +} diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index 3b6e20edc9..97591551c1 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/MediaBrowser.Controller/Sync/ISyncManager.cs @@ -174,6 +174,13 @@ namespace MediaBrowser.Controller.Sync /// The target identifier. /// IEnumerable<SyncQualityOption>. IEnumerable GetQualityOptions(string targetId); + /// + /// Gets the quality options. + /// + /// The target identifier. + /// The user. + /// IEnumerable<SyncQualityOption>. + IEnumerable GetQualityOptions(string targetId, User user); /// /// Gets the profile options. @@ -181,5 +188,12 @@ namespace MediaBrowser.Controller.Sync /// The target identifier. /// IEnumerable<SyncQualityOption>. IEnumerable GetProfileOptions(string targetId); + /// + /// Gets the profile options. + /// + /// The target identifier. + /// The user. + /// IEnumerable<SyncProfileOption>. + IEnumerable GetProfileOptions(string targetId, User user); } } diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs index 5ccea52bad..abd649ad7b 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -223,7 +223,7 @@ namespace MediaBrowser.Dlna.ContentDirectory if (string.Equals(flag, "BrowseMetadata")) { totalCount = 1; - + if (item.IsFolder || serverItem.StubType.HasValue) { var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false)); @@ -350,7 +350,7 @@ namespace MediaBrowser.Dlna.ContentDirectory }; } - private async Task> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) + private Task> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) { var folder = (Folder)item; @@ -389,7 +389,7 @@ namespace MediaBrowser.Dlna.ContentDirectory isFolder = true; } - return await folder.GetItems(new InternalItemsQuery + return folder.GetItems(new InternalItemsQuery { Limit = limit, StartIndex = startIndex, @@ -401,7 +401,7 @@ namespace MediaBrowser.Dlna.ContentDirectory IsFolder = isFolder, MediaTypes = mediaTypes.ToArray() - }).ConfigureAwait(false); + }); } private async Task> GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 18d9ccece9..be636c0baf 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Session; using MediaBrowser.MediaEncoding.Probing; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -103,18 +104,17 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Gets the media info. /// - /// The input files. - /// The primary path. - /// The protocol. - /// if set to true [is audio]. - /// if set to true [extract chapters]. + /// The request. /// The cancellation token. /// Task. - public Task GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio, - bool extractChapters, CancellationToken cancellationToken) + public Task GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken) { - return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), primaryPath, protocol, !isAudio && extractChapters, - GetProbeSizeArgument(inputFiles, protocol), isAudio, cancellationToken); + var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; + + var inputFiles = MediaEncoderHelpers.GetInputArgument(request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames); + + return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters, + GetProbeSizeArgument(inputFiles, request.Protocol), request.MediaType == DlnaProfileType.Audio, cancellationToken); } /// @@ -152,7 +152,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// The cancellation token. /// Task{MediaInfoResult}. /// - private async Task GetMediaInfoInternal(string inputPath, string primaryPath, MediaProtocol protocol, bool extractChapters, + private async Task GetMediaInfoInternal(string inputPath, string primaryPath, MediaProtocol protocol, bool extractChapters, string probeSizeArgument, bool isAudio, CancellationToken cancellationToken) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 48e8b6ee20..7df3cb56f1 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -25,9 +25,9 @@ namespace MediaBrowser.MediaEncoding.Probing _fileSystem = fileSystem; } - public Model.Entities.MediaInfo GetMediaInfo(InternalMediaInfoResult data, bool isAudio, string path, MediaProtocol protocol) + public Model.MediaInfo.MediaInfo GetMediaInfo(InternalMediaInfoResult data, bool isAudio, string path, MediaProtocol protocol) { - var info = new Model.Entities.MediaInfo + var info = new Model.MediaInfo.MediaInfo { Path = path, Protocol = protocol @@ -342,7 +342,7 @@ namespace MediaBrowser.MediaEncoding.Probing return null; } - private void SetAudioRuntimeTicks(InternalMediaInfoResult result, Model.Entities.MediaInfo data) + private void SetAudioRuntimeTicks(InternalMediaInfoResult result, Model.MediaInfo.MediaInfo data) { if (result.streams != null) { @@ -369,7 +369,7 @@ namespace MediaBrowser.MediaEncoding.Probing } } - private void SetSize(InternalMediaInfoResult data, Model.Entities.MediaInfo info) + private void SetSize(InternalMediaInfoResult data, Model.MediaInfo.MediaInfo info) { if (data.format != null) { @@ -384,7 +384,7 @@ namespace MediaBrowser.MediaEncoding.Probing } } - private void SetAudioInfoFromTags(Model.Entities.MediaInfo audio, Dictionary tags) + private void SetAudioInfoFromTags(Model.MediaInfo.MediaInfo audio, Dictionary tags) { var title = FFProbeHelpers.GetDictionaryValue(tags, "title"); @@ -591,7 +591,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// The audio. /// The tags. /// Name of the tag. - private void FetchStudios(Model.Entities.MediaInfo audio, Dictionary tags, string tagName) + private void FetchStudios(Model.MediaInfo.MediaInfo audio, Dictionary tags, string tagName) { var val = FFProbeHelpers.GetDictionaryValue(tags, tagName); @@ -626,7 +626,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The information. /// The tags. - private void FetchGenres(Model.Entities.MediaInfo info, Dictionary tags) + private void FetchGenres(Model.MediaInfo.MediaInfo info, Dictionary tags) { var val = FFProbeHelpers.GetDictionaryValue(tags, "genre"); @@ -697,7 +697,7 @@ namespace MediaBrowser.MediaEncoding.Probing private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames) - private void FetchWtvInfo(Model.Entities.MediaInfo video, InternalMediaInfoResult data) + private void FetchWtvInfo(Model.MediaInfo.MediaInfo video, InternalMediaInfoResult data) { if (data.format == null || data.format.tags == null) { @@ -806,7 +806,7 @@ namespace MediaBrowser.MediaEncoding.Probing } } - private void ExtractTimestamp(Model.Entities.MediaInfo video) + private void ExtractTimestamp(Model.MediaInfo.MediaInfo video) { if (video.VideoType == VideoType.VideoFile) { diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index fdedc51a21..c3ec911de8 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -560,9 +560,6 @@ Entities\MBRegistrationRecord.cs - - Entities\MediaInfo.cs - Entities\MediaStream.cs @@ -809,6 +806,9 @@ MediaInfo\LiveStreamResponse.cs + + MediaInfo\MediaInfo.cs + MediaInfo\MediaProtocol.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 3618aa9728..437fcc8fff 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -525,9 +525,6 @@ Entities\MBRegistrationRecord.cs - - Entities\MediaInfo.cs - Entities\MediaStream.cs @@ -765,6 +762,9 @@ MediaInfo\LiveStreamResponse.cs + + MediaInfo\MediaInfo.cs + MediaInfo\MediaProtocol.cs diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 0673095125..2a210b7a2f 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -226,7 +226,7 @@ - + diff --git a/MediaBrowser.Model/Entities/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs similarity index 96% rename from MediaBrowser.Model/Entities/MediaInfo.cs rename to MediaBrowser.Model/MediaInfo/MediaInfo.cs index 67efe31087..21f258693e 100644 --- a/MediaBrowser.Model/Entities/MediaInfo.cs +++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs @@ -1,8 +1,9 @@ using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; -namespace MediaBrowser.Model.Entities +namespace MediaBrowser.Model.MediaInfo { public class MediaInfo : MediaSourceInfo, IHasProviderIds { diff --git a/MediaBrowser.Model/Sync/SyncOptions.cs b/MediaBrowser.Model/Sync/SyncOptions.cs index 294f7bcef7..765dea86b9 100644 --- a/MediaBrowser.Model/Sync/SyncOptions.cs +++ b/MediaBrowser.Model/Sync/SyncOptions.cs @@ -4,5 +4,6 @@ namespace MediaBrowser.Model.Sync public class SyncOptions { public string TemporaryPath { get; set; } + public long UploadSpeedLimitBytes { get; set; } } } diff --git a/MediaBrowser.Model/Sync/SyncQualityOption.cs b/MediaBrowser.Model/Sync/SyncQualityOption.cs index 597b987270..6eff4b9a4c 100644 --- a/MediaBrowser.Model/Sync/SyncQualityOption.cs +++ b/MediaBrowser.Model/Sync/SyncQualityOption.cs @@ -23,5 +23,10 @@ namespace MediaBrowser.Model.Sync /// /// true if this instance is default; otherwise, false. public bool IsDefault { get; set; } + /// + /// Gets or sets a value indicating whether this instance is original quality. + /// + /// true if this instance is original quality; otherwise, false. + public bool IsOriginalQuality { get; set; } } } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 640f03e2a0..b3c5994966 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -39,6 +39,8 @@ namespace MediaBrowser.Model.Users public bool EnableLiveTvAccess { get; set; } public bool EnableMediaPlayback { get; set; } + public bool EnableMediaPlaybackTranscoding { get; set; } + public bool EnableContentDeletion { get; set; } public bool EnableContentDownloading { get; set; } @@ -47,6 +49,7 @@ namespace MediaBrowser.Model.Users /// /// true if [enable synchronize]; otherwise, false. public bool EnableSync { get; set; } + public bool EnableSyncTranscoding { get; set; } public string[] EnabledDevices { get; set; } public bool EnableAllDevices { get; set; } @@ -62,9 +65,14 @@ namespace MediaBrowser.Model.Users public UserPolicy() { EnableSync = true; - EnableLiveTvManagement = true; + EnableSyncTranscoding = true; + EnableMediaPlayback = true; + EnableMediaPlaybackTranscoding = true; + + EnableLiveTvManagement = true; EnableLiveTvAccess = true; + EnableSharedDeviceControl = true; BlockedTags = new string[] { }; diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index 3ac3cccb3d..92327c9bc3 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -16,11 +15,8 @@ namespace MediaBrowser.Providers.BoxSets { public class BoxSetMetadataService : MetadataService { - private readonly ILocalizationManager _iLocalizationManager; - - public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILocalizationManager iLocalizationManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager) + public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager) { - _iLocalizationManager = iLocalizationManager; } /// diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index b1bed73109..3e816802e9 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; @@ -53,7 +54,7 @@ namespace MediaBrowser.Providers.MediaInfo private const string SchemaVersion = "2"; - private async Task GetMediaInfo(BaseItem item, CancellationToken cancellationToken) + private async Task GetMediaInfo(BaseItem item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -64,7 +65,7 @@ namespace MediaBrowser.Providers.MediaInfo try { - return _json.DeserializeFromFile(cachePath); + return _json.DeserializeFromFile(cachePath); } catch (FileNotFoundException) { @@ -74,9 +75,13 @@ namespace MediaBrowser.Providers.MediaInfo { } - var inputPath = new[] { item.Path }; + var result = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest + { + InputPath = item.Path, + MediaType = DlnaProfileType.Audio, + Protocol = MediaProtocol.File - var result = await _mediaEncoder.GetMediaInfo(inputPath, item.Path, MediaProtocol.File, true, false, cancellationToken).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); _json.SerializeToFile(result, cachePath); @@ -91,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo /// The cancellation token. /// The media information. /// Task. - protected Task Fetch(Audio audio, CancellationToken cancellationToken, Model.Entities.MediaInfo mediaInfo) + protected Task Fetch(Audio audio, CancellationToken cancellationToken, Model.MediaInfo.MediaInfo mediaInfo) { var mediaStreams = mediaInfo.MediaStreams; @@ -115,7 +120,7 @@ namespace MediaBrowser.Providers.MediaInfo /// /// The audio. /// The data. - private void FetchDataFromTags(Audio audio, Model.Entities.MediaInfo data) + private void FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo data) { // Only set Name if title was found in the dictionary if (!string.IsNullOrEmpty(data.Title)) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index cec66f3c14..c433018c08 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -1,5 +1,6 @@ using DvdLib.Ifo; using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Chapters; @@ -129,9 +130,9 @@ namespace MediaBrowser.Providers.MediaInfo return ItemUpdateType.MetadataImport; } - private const string SchemaVersion = "1"; + private const string SchemaVersion = "2"; - private async Task GetMediaInfo(Video item, + private async Task GetMediaInfo(Video item, IIsoMount isoMount, CancellationToken cancellationToken) { @@ -144,7 +145,7 @@ namespace MediaBrowser.Providers.MediaInfo try { - return _json.DeserializeFromFile(cachePath); + return _json.DeserializeFromFile(cachePath); } catch (FileNotFoundException) { @@ -158,9 +159,17 @@ namespace MediaBrowser.Providers.MediaInfo ? MediaProtocol.Http : MediaProtocol.File; - var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, protocol, isoMount, item.PlayableStreamFileNames); + var result = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest + { + PlayableStreamFileNames = item.PlayableStreamFileNames, + MountedIso = isoMount, + ExtractChapters = true, + VideoType = item.VideoType, + MediaType = DlnaProfileType.Video, + InputPath = item.Path, + Protocol = protocol - var result = await _mediaEncoder.GetMediaInfo(inputPath, item.Path, protocol, false, true, cancellationToken).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); _json.SerializeToFile(result, cachePath); @@ -170,7 +179,7 @@ namespace MediaBrowser.Providers.MediaInfo protected async Task Fetch(Video video, CancellationToken cancellationToken, - Model.Entities.MediaInfo mediaInfo, + Model.MediaInfo.MediaInfo mediaInfo, IIsoMount isoMount, BlurayDiscInfo blurayInfo, MetadataRefreshOptions options) @@ -348,7 +357,7 @@ namespace MediaBrowser.Providers.MediaInfo return _blurayExaminer.GetDiscInfo(path); } - private void FetchEmbeddedInfo(Video video, Model.Entities.MediaInfo data) + private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data) { if (!video.LockedFields.Contains(MetadataFields.OfficialRating)) { diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs index bbe37cb506..8f5d8fe9b7 100644 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs @@ -14,9 +14,7 @@ namespace MediaBrowser.Server.Implementations.Collections public override bool IsVisible(User user) { - return base.IsVisible(user) && GetChildren(user, false) - .OfType() - .Any(i => i.IsVisible(user)); + return base.IsVisible(user) && GetChildren(user, false).Any(); } public override bool IsHidden diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 27a7d4ea97..01efe0ab10 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -137,21 +137,16 @@ namespace MediaBrowser.Server.Implementations.Library public async Task> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, CancellationToken cancellationToken) { var item = _libraryManager.GetItemById(id); - IEnumerable mediaSources; var hasMediaSources = (IHasMediaSources)item; User user = null; - if (string.IsNullOrWhiteSpace(userId)) - { - mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution); - } - else + if (!string.IsNullOrWhiteSpace(userId)) { user = _userManager.GetUserById(userId); - mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user); } + var mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user); var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false); var list = new List(); @@ -166,9 +161,11 @@ namespace MediaBrowser.Server.Implementations.Library } if (source.Protocol == MediaProtocol.File) { - source.SupportsDirectStream = File.Exists(source.Path); - // TODO: Path substitution + if (!File.Exists(source.Path)) + { + source.SupportsDirectStream = false; + } } else if (source.Protocol == MediaProtocol.Http) { @@ -183,6 +180,17 @@ namespace MediaBrowser.Server.Implementations.Library list.Add(source); } + foreach (var source in list) + { + if (user != null) + { + if (!user.Policy.EnableMediaPlaybackTranscoding) + { + source.SupportsTranscoding = false; + } + } + } + return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder); } @@ -343,6 +351,7 @@ namespace MediaBrowser.Server.Implementations.Library } var json = _jsonSerializer.SerializeToString(mediaSource); + _logger.Debug("Live stream opened: " + json); var clone = _jsonSerializer.DeserializeFromString(json); if (!string.IsNullOrWhiteSpace(request.UserId)) diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 71daf2b0cf..f88293b2a9 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -64,7 +64,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)) { - return ResolveVideos